diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt index 2d6337a09c2..8b2c5660305 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt @@ -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): Result { - val removedNodes = HashSet() + val insnsToRemove = ArrayList() - 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): 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): 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) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantNopsCleanupMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantNopsCleanupMethodTransformer.kt index b2e7142d5ef..3198348f53c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantNopsCleanupMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantNopsCleanupMethodTransformer.kt @@ -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() - 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) { - 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) { + // 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().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 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt index 4ee4a0b8ad8..6b5af5a2462 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt @@ -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)) } diff --git a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/nopInlineFuns.kt b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/nopInlineFuns.kt index 6bd370efa41..fd0484340e0 100644 --- a/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/nopInlineFuns.kt +++ b/compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/nopInlineFuns.kt @@ -23,4 +23,4 @@ fun simpleFunVoid(f: () -> Unit): Unit { return f() } -// 4 NOP +// 5 NOP diff --git a/compiler/testData/codegen/bytecodeText/constantConditions/inlineIfFalse.kt b/compiler/testData/codegen/bytecodeText/constantConditions/inlineIfFalse.kt new file mode 100644 index 00000000000..e271a5b29ca --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/constantConditions/inlineIfFalse.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt b/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt index a7e06bcf379..d993a974f2b 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/loopVarInterval.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/jackAndJill/inlinedConstuctorWithSuperCallParams.kt b/compiler/testData/codegen/bytecodeText/jackAndJill/inlinedConstuctorWithSuperCallParams.kt index 5ae3ef172b8..27f52b3f341 100644 --- a/compiler/testData/codegen/bytecodeText/jackAndJill/inlinedConstuctorWithSuperCallParams.kt +++ b/compiler/testData/codegen/bytecodeText/jackAndJill/inlinedConstuctorWithSuperCallParams.kt @@ -14,4 +14,4 @@ fun main(args: Array) { } /*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 diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 43ae032a9e6..3f4ccb5e83a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -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");