diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index b71a1d0e00d..5ad4e6de7b8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -28,6 +28,8 @@ import org.jetbrains.kotlin.codegen.optimization.temporaryVals.TemporaryVariable import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.MethodNode class OptimizationMethodVisitor( @@ -83,11 +85,11 @@ class OptimizationMethodVisitor( if (getTotalFramesWeight(getTotalTcbSize(node), node) > MEMORY_LIMIT_BY_METHOD_MB) return false } - return getTotalFramesWeight(node.instructions.size(), node) < MEMORY_LIMIT_BY_METHOD_MB + return getTotalFramesWeight(node.instructions.first.countInsnsWithFramesUntil(null), node) < MEMORY_LIMIT_BY_METHOD_MB } fun canBeOptimizedUsingSourceInterpreter(node: MethodNode): Boolean { - val methodSize = node.instructions.size() + val methodSize = node.instructions.first.countInsnsWithFramesUntil(null) if (node.tryCatchBlocks.size > TRY_CATCH_BLOCKS_SOFT_LIMIT) { if (getTotalFramesWeight(getTotalTcbSize(node) * methodSize, node) > MEMORY_LIMIT_BY_METHOD_MB) return false @@ -95,10 +97,26 @@ class OptimizationMethodVisitor( return getTotalFramesWeight(methodSize * methodSize, node) < MEMORY_LIMIT_BY_METHOD_MB } + private fun AbstractInsnNode?.countInsnsWithFramesUntil(end: AbstractInsnNode?): Int { + var it = this + var result = 0 + while (it != end && it != null) { + // FastMethodAnalyzer will reuse the Frame instance when a pseudo-instruction or a NOP is followed + // by anything other than a jump-targeted label, so those instructions consume no extra memory. + // To avoid checking all jumps, here we assume all labels are potentially targeted. + // (Effectively, all of this means that adding line numbers should not inhibit optimization.) + if ((it.type != AbstractInsnNode.LINE && it.type != AbstractInsnNode.FRAME && it.type != AbstractInsnNode.LABEL && + it.opcode != Opcodes.NOP) || it.next?.type == AbstractInsnNode.LABEL + ) result++ + it = it.next + } + return result + } + private fun getTotalFramesWeight(size: Int, node: MethodNode) = size.toLong() * (node.maxLocals + node.maxStack) / (1024 * 1024) private fun getTotalTcbSize(node: MethodNode) = - node.tryCatchBlocks.sumOf { node.instructions.indexOf(it.end) - node.instructions.indexOf(it.start) } + node.tryCatchBlocks.sumOf { it.start.countInsnsWithFramesUntil(it.end) } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt index 8470707a75d..0e8b2228d3e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/FastMethodAnalyzer.kt @@ -96,8 +96,12 @@ open class FastMethodAnalyzer val insnOpcode = insnNode.opcode val insnType = insnNode.type - if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) { - mergeControlFlowEdge(insn + 1, f) + if (insnType == AbstractInsnNode.LABEL || + insnType == AbstractInsnNode.LINE || + insnType == AbstractInsnNode.FRAME || + insnOpcode == Opcodes.NOP + ) { + mergeControlFlowEdge(insn + 1, f, canReuse = true) } else { current.init(f).execute(insnNode, interpreter) when { @@ -236,10 +240,10 @@ open class FastMethodAnalyzer } private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame, insn: Int, insnOpcode: Int) { + mergeControlFlowEdge(insnNode.label.indexOf(), current) if (insnOpcode != Opcodes.GOTO) { mergeControlFlowEdge(insn + 1, current) } - mergeControlFlowEdge(insnNode.label.indexOf(), current) } private fun computeExceptionHandlersForEachInsn(m: MethodNode) { @@ -258,9 +262,13 @@ open class FastMethodAnalyzer } } - private fun mergeControlFlowEdge(dest: Int, frame: Frame) { + private fun mergeControlFlowEdge(dest: Int, frame: Frame, canReuse: Boolean = false) { val oldFrame = frames[dest] val changes = when { + canReuse && !isMergeNode[dest] -> { + frames[dest] = frame + true + } oldFrame == null -> { frames[dest] = newFrame(frame.locals, frame.maxStackSize).apply { init(frame) } true