More aggressive DCE should honor debugger invariants
- A LINENUMEBER node is "dead" if the corresponding instruction interval contains at least one "dead" bytecode instruction and no live bytecode instructions - Observable local variable lifetimes should be taken into account when determining if a NOP is required for debugger.
This commit is contained in:
+47
-13
@@ -16,11 +16,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.optimization
|
||||
|
||||
import org.jetbrains.kotlin.codegen.inline.remove
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.LabelNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.LineNumberNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
||||
@@ -34,24 +36,56 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
||||
}
|
||||
|
||||
fun removeDeadCodeByFrames(methodNode: MethodNode, frames: Array<out Any?>): Result {
|
||||
val removedNodes = HashSet<AbstractInsnNode>()
|
||||
val insnsToRemove = ArrayList<AbstractInsnNode>()
|
||||
|
||||
val insnList = methodNode.instructions
|
||||
val insnsArray = insnList.toArray()
|
||||
|
||||
// Do not remove not meaningful nodes (labels/linenumbers) because they can be referred
|
||||
// by try/catch blocks or local variables table.
|
||||
insnsArray.zip(frames).filter {
|
||||
it.second == null && it.first.isMeaningful
|
||||
}.forEach {
|
||||
insnList.remove(it.first)
|
||||
removedNodes.add(it.first)
|
||||
val insns = methodNode.instructions.toArray()
|
||||
for (i in insns.indices) {
|
||||
val insn = insns[i]
|
||||
if (shouldRemove(insn, i, frames)) {
|
||||
insnsToRemove.add(insn)
|
||||
}
|
||||
}
|
||||
|
||||
methodNode.remove(insnsToRemove)
|
||||
|
||||
// Remove empty try-catch blocks to make sure we don't break data flow analysis invariants by dead code elimination.
|
||||
methodNode.removeEmptyCatchBlocks()
|
||||
|
||||
return Result(removedNodes)
|
||||
return Result(insnsToRemove.toSet())
|
||||
}
|
||||
|
||||
private fun shouldRemove(insn: AbstractInsnNode, index: Int, frames: Array<out Any?>): Boolean =
|
||||
when (insn) {
|
||||
is LabelNode ->
|
||||
// Do not remove label nodes because they can be referred by try/catch blocks or local variables table
|
||||
false
|
||||
is LineNumberNode ->
|
||||
isDeadLineNumber(insn, index, frames)
|
||||
else ->
|
||||
frames[index] == null
|
||||
}
|
||||
|
||||
private fun isDeadLineNumber(insn: LineNumberNode, index: Int, frames: Array<out Any?>): Boolean {
|
||||
// Line number node is "dead" if the corresponding line number interval
|
||||
// contains at least one "dead" meaningful instruction and no "live" meaningful instructions.
|
||||
var finger: AbstractInsnNode = insn
|
||||
var fingerIndex = index
|
||||
var hasDeadInsn = false
|
||||
loop@ while (true) {
|
||||
finger = finger.next ?: break
|
||||
fingerIndex++
|
||||
when (finger) {
|
||||
is LabelNode ->
|
||||
continue@loop
|
||||
is LineNumberNode ->
|
||||
if (finger.line != insn.line) return hasDeadInsn
|
||||
else -> {
|
||||
if (frames[fingerIndex] != null) return false
|
||||
hasDeadInsn = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class Result(val removedNodes: Set<AbstractInsnNode>) {
|
||||
|
||||
+42
-24
@@ -16,26 +16,30 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.optimization
|
||||
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.findNextOrNull
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.LabelNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.LineNumberNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import java.util.*
|
||||
|
||||
class RedundantNopsCleanupMethodTransformer : MethodTransformer() {
|
||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||
// NOP instruction is required, iff one of the following conditions is true:
|
||||
// (a) it is a sole bytecode instruction in a try-catch block (TCB)
|
||||
// (b) it is a sole bytecode instruction is a source code line
|
||||
LabelNormalizationMethodTransformer().transform(internalClassName, methodNode)
|
||||
|
||||
val requiredNops = HashSet<AbstractInsnNode>()
|
||||
|
||||
recordNopsRequiredForSourceCodeLines(methodNode.instructions.first, requiredNops)
|
||||
// NOP instruction is required, if it is a sole bytecode instruction in a try-catch block (TCB)
|
||||
recordNopsRequiredForTryCatchBlocks(methodNode, requiredNops)
|
||||
|
||||
// NOP instruction is required, if it is a sole bytecode instruction in a debugger stepping interval
|
||||
recordNopsRequiredForDebugger(methodNode, requiredNops)
|
||||
|
||||
var current: AbstractInsnNode? = methodNode.instructions.first
|
||||
while (current != null) {
|
||||
if (current.opcode == Opcodes.NOP && !requiredNops.contains(current)) {
|
||||
@@ -49,17 +53,42 @@ class RedundantNopsCleanupMethodTransformer : MethodTransformer() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordNopsRequiredForSourceCodeLines(first: AbstractInsnNode, requiredNops: MutableSet<AbstractInsnNode>) {
|
||||
var current: AbstractInsnNode? = first
|
||||
while (current != null) {
|
||||
if (current is LineNumberNode) {
|
||||
val nextLineNumberNode = current.getNextLineNumberNode()
|
||||
requiredNops.addIfNotNull(getRequiredNopInRange(current, nextLineNumberNode))
|
||||
current = nextLineNumberNode
|
||||
private fun recordNopsRequiredForDebugger(methodNode: MethodNode, requiredNops: MutableSet<AbstractInsnNode>) {
|
||||
// We two subsets of labels that are "special" for the debugger:
|
||||
// 1) Labels for line numbers.
|
||||
// 2) Labels for observable local variables lifetimes.
|
||||
// NB this includes synthetic variables denoting inlined function bodies and arguments
|
||||
// (see JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION, JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT).
|
||||
//
|
||||
// If we enumerate labels in a given subset in order of occurrence in the method code:
|
||||
// L[0], L[1], ..., L[n-1], L[n]
|
||||
// then for each k, 1 <= k <= n-1:
|
||||
// an instruction interval I[k] = [L[k]; L[k+1]) should contain at least one bytecode instruction (which can be a NOP).
|
||||
|
||||
for (insn in methodNode.instructions) {
|
||||
if (insn is LineNumberNode) {
|
||||
val nextLineNumber = insn.findNextOrNull { it is LineNumberNode && it.line != insn.line }
|
||||
requiredNops.addIfNotNull(getRequiredNopInRange(insn, nextLineNumber))
|
||||
}
|
||||
else {
|
||||
current = current.next
|
||||
}
|
||||
|
||||
val localVariableLabels = run {
|
||||
val labels = hashSetOf<LabelNode>().apply {
|
||||
for (localVariable in methodNode.localVariables) {
|
||||
add(localVariable.start)
|
||||
add(localVariable.end)
|
||||
}
|
||||
}
|
||||
|
||||
methodNode.instructions.toArray().filter { labels.contains(it) }
|
||||
}
|
||||
|
||||
|
||||
for (i in 0 .. localVariableLabels.size - 2) {
|
||||
val begin = localVariableLabels[i]
|
||||
val end = localVariableLabels[i + 1]
|
||||
if (InsnSequence(begin, end).any { it in requiredNops }) continue
|
||||
requiredNops.addIfNotNull(getRequiredNopInRange(begin, end))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,17 +103,6 @@ class RedundantNopsCleanupMethodTransformer : MethodTransformer() {
|
||||
}
|
||||
|
||||
|
||||
internal fun LineNumberNode.getNextLineNumberNode(): LineNumberNode? {
|
||||
var current: AbstractInsnNode? = this
|
||||
while (current != null) {
|
||||
if (current is LineNumberNode && current.line != this.line) {
|
||||
return current
|
||||
}
|
||||
current = current.next
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun getRequiredNopInRange(firstInclusive: AbstractInsnNode, lastExclusive: AbstractInsnNode?): AbstractInsnNode? {
|
||||
var lastNop: AbstractInsnNode? = null
|
||||
var current: AbstractInsnNode? = firstInclusive
|
||||
|
||||
+3
-4
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.codegen.optimization.common.isLoadOperation
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext
|
||||
import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
@@ -47,9 +46,9 @@ class PopBackwardPropagationTransformer : MethodTransformer() {
|
||||
}
|
||||
}
|
||||
|
||||
private val REPLACE_WITH_NOP = Transformation { insnList.replaceNodeGetNext(it, createRemovableNopInsn()) }
|
||||
private val REPLACE_WITH_POP1 = Transformation { insnList.replaceNodeGetNext(it, InsnNode(Opcodes.POP)) }
|
||||
private val REPLACE_WITH_POP2 = Transformation { insnList.replaceNodeGetNext(it, InsnNode(Opcodes.POP2)) }
|
||||
private val REPLACE_WITH_NOP = Transformation { insnList.set(it, createRemovableNopInsn()) }
|
||||
private val REPLACE_WITH_POP1 = Transformation { insnList.set(it, InsnNode(Opcodes.POP)) }
|
||||
private val REPLACE_WITH_POP2 = Transformation { insnList.set(it, InsnNode(Opcodes.POP2)) }
|
||||
private val INSERT_POP1_AFTER = Transformation { insnList.insert(it, InsnNode(Opcodes.POP)) }
|
||||
private val INSERT_POP2_AFTER = Transformation { insnList.insert(it, InsnNode(Opcodes.POP2)) }
|
||||
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ fun simpleFunVoid(f: () -> Unit): Unit {
|
||||
return f()
|
||||
}
|
||||
|
||||
// 4 NOP
|
||||
// 5 NOP
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package inlineInIfFalseDex
|
||||
|
||||
fun inlineIfFalse() {
|
||||
val bar = ""
|
||||
//Breakpoint!
|
||||
if (inlineCall { true }) {
|
||||
foo()
|
||||
}
|
||||
foo()
|
||||
}
|
||||
|
||||
fun foo() {}
|
||||
|
||||
inline fun inlineCall(predicate: (String?) -> Boolean): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
// 0 LINENUMBER 7
|
||||
// 0 LINENUMBER 8
|
||||
// 1 LINENUMBER 9
|
||||
@@ -6,4 +6,4 @@ fun f() {
|
||||
|
||||
// 1 ISTORE 0\s+L3
|
||||
// 1 ILOAD 0\s+INVOKEVIRTUAL java/io/PrintStream.print \(C\)V
|
||||
// 1 LOCALVARIABLE c C L3 L8 0
|
||||
// 1 LOCALVARIABLE c C L3 L6 0
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
/*Threre are two constuctors so we should be sure that we check LOCALVARIABLEs from same method*/
|
||||
// 1 LOCALVARIABLE this LInlinedConstuctorWithSuperCallParamsKt\$main\$\$inlined\$test\$1; L0 L7 0\s+LOCALVARIABLE \$super_call_param\$1 Ljava/lang/String; L0 L7 1
|
||||
// 1 LOCALVARIABLE this LInlinedConstuctorWithSuperCallParamsKt\$main\$\$inlined\$test\$1; L0 L6 0\s+LOCALVARIABLE \$super_call_param\$1 Ljava/lang/String; L0 L6 1
|
||||
|
||||
@@ -911,6 +911,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineIfFalse.kt")
|
||||
public void testInlineIfFalse() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/inlineIfFalse.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt3098.kt")
|
||||
public void testKt3098() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/kt3098.kt");
|
||||
|
||||
Reference in New Issue
Block a user