diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt index d6763b3633f..decb9bc8b46 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt @@ -17,6 +17,9 @@ package org.jetbrains.kotlin.codegen import com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsnOpcode +import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq +import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysTrueIfeq import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Opcodes.* @@ -50,6 +53,10 @@ open class BranchedValue( v.visitJumpInsn(patchOpcode(if (jumpIfFalse) opcode else negatedOperations[opcode], v), jumpLabel); } + open fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { + condJump(jumpLabel, v, jumpIfFalse) + } + protected open fun patchOpcode(opcode: Int, v: InstructionAdapter): Int { return opcode } @@ -58,13 +65,21 @@ open class BranchedValue( val negatedOperations = hashMapOf() val TRUE: BranchedValue = object : BranchedValue(StackValue.Constant(true, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) { - override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { if (!jumpIfFalse) { v.goTo(jumpLabel) } } + override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { + if (!jumpIfFalse) { + v.fakeAlwaysTrueIfeq(jumpLabel) + } + else { + v.fakeAlwaysFalseIfeq(jumpLabel) + } + } + override fun putSelector(type: Type, v: InstructionAdapter) { v.iconst(1) coerceTo(type, v) @@ -78,6 +93,15 @@ open class BranchedValue( } } + override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { + if (jumpIfFalse) { + v.fakeAlwaysTrueIfeq(jumpLabel) + } + else { + v.fakeAlwaysFalseIfeq(jumpLabel) + } + } + override fun putSelector(type: Type, v: InstructionAdapter) { v.iconst(0) coerceTo(type, v) @@ -111,6 +135,10 @@ open class BranchedValue( condJump(condition).condJump(label, iv, jumpIfFalse) } + fun loopJump(condition: StackValue, label: Label, jumpIfFalse: Boolean, iv: InstructionAdapter) { + condJump(condition).loopJump(label, iv, jumpIfFalse) + } + fun condJump(condition: StackValue): CondJump { return CondJump(if (condition is BranchedValue) { condition @@ -171,6 +199,10 @@ class CondJump(val condition: BranchedValue, op: Int) : BranchedValue(condition, override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { condition.condJump(jumpLabel, v, jumpIfFalse) } + + override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) { + condition.loopJump(jumpLabel, v, jumpIfFalse) + } } class NumberCompare( diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index f2e254eb64f..77ea4aa6ebd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.codegen.inline.*; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethod; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.kotlin.codegen.intrinsics.JavaClassProperty; +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsnsPackage; import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.JetTypeMapper; @@ -467,7 +468,7 @@ public class ExpressionCodegen extends JetVisitor implem blockStackElements.push(new LoopBlockStackElement(end, condition, targetLabel(expression))); StackValue conditionValue = gen(expression.getCondition()); - BranchedValue.Companion.condJump(conditionValue, end, true, v); + BranchedValue.Companion.loopJump(conditionValue, end, true, v); generateLoopBody(expression.getBody()); @@ -491,6 +492,8 @@ public class ExpressionCodegen extends JetVisitor implem blockStackElements.push(new LoopBlockStackElement(breakLabel, continueLabel, targetLabel(expression))); + PseudoInsnsPackage.fakeAlwaysFalseIfeq(v, continueLabel); + JetExpression body = expression.getBody(); JetExpression condition = expression.getCondition(); StackValue conditionValue; @@ -514,7 +517,7 @@ public class ExpressionCodegen extends JetVisitor implem conditionValue = gen(condition); } - BranchedValue.Companion.condJump(conditionValue, beginLoopLabel, false, v); + BranchedValue.Companion.loopJump(conditionValue, beginLoopLabel, false, v); v.mark(breakLabel); blockStackElements.pop(); @@ -574,6 +577,9 @@ public class ExpressionCodegen extends JetVisitor implem v.mark(loopEntry); generator.checkPreCondition(loopExit); + // Some forms of for-loop can be optimized as post-condition loops. + PseudoInsnsPackage.fakeAlwaysFalseIfeq(v, continueLabel); + generator.beforeBody(); blockStackElements.push(new LoopBlockStackElement(loopExit, continueLabel, targetLabel(generator.forExpression))); generator.body(); @@ -1231,7 +1237,8 @@ public class ExpressionCodegen extends JetVisitor implem if (labelElement == null || loopBlockStackElement.targetLabel != null && labelElement.getReferencedName().equals(loopBlockStackElement.targetLabel.getReferencedName())) { - v.goTo(isBreak ? loopBlockStackElement.breakLabel : loopBlockStackElement.continueLabel); + Label label = isBreak ? loopBlockStackElement.breakLabel : loopBlockStackElement.continueLabel; + PseudoInsnsPackage.fixStackAndJump(v, label); v.mark(afterBreakContinueLabel); return StackValue.none(); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt new file mode 100644 index 00000000000..1e39ae7eef0 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization + +import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsnOpcode +import org.jetbrains.kotlin.codegen.pseudoInsns.parseOrNull +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.* +import java.util.* +import kotlin.properties.Delegates +import kotlin.test.assertEquals + +class FixStackBeforeJumpTransformer : MethodTransformer() { + public override fun transform(internalClassName: String, methodNode: MethodNode) { + val jumpsToFix = linkedSetOf() + val fakesToGotos = arrayListOf() + val fakesToRemove = arrayListOf() + + methodNode.instructions.forEach { insnNode -> + when { + PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP.isa(insnNode) -> { + val next = insnNode.getNext() + assert(next.getOpcode() == Opcodes.GOTO, + "Instruction after ${PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP} should be GOTO") + jumpsToFix.add(next as JumpInsnNode) + } + PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ.isa(insnNode) -> { + assert(insnNode.getNext().getOpcode() == Opcodes.IFEQ, + "Instruction after ${PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ} should be IFEQ") + fakesToGotos.add(insnNode) + } + PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ.isa(insnNode) -> { + assert(insnNode.getNext().getOpcode() == Opcodes.IFEQ, + "Instruction after ${PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ} should be IFEQ") + fakesToRemove.add(insnNode) + } + } + } + + if (jumpsToFix.isEmpty() && fakesToGotos.isEmpty() && fakesToRemove.isEmpty()) { + return + } + + if (jumpsToFix.isNotEmpty()) { + val analyzer = StackDepthAnalyzer(internalClassName, methodNode, jumpsToFix) + val frames = analyzer.analyze() + + val actions = arrayListOf<() -> Unit>() + + for (jumpNode in jumpsToFix) { + val jumpIndex = methodNode.instructions.indexOf(jumpNode) + val labelIndex = methodNode.instructions.indexOf(jumpNode.label) + + val DEAD_CODE = -1 // Stack size is always non-negative for live code + val actualStackSize = frames[jumpIndex]?.getStackSize() ?: DEAD_CODE + val expectedStackSize = frames[labelIndex]?.getStackSize() ?: DEAD_CODE + + if (actualStackSize != DEAD_CODE && expectedStackSize != DEAD_CODE) { + assert(expectedStackSize <= actualStackSize, + "Label at $labelIndex, jump at $jumpIndex: stack underflow: $expectedStackSize > $actualStackSize") + val frame = frames[jumpIndex]!! + actions.add({ replaceMarkerWithPops(methodNode, jumpNode.getPrevious(), expectedStackSize, frame) }) + } + else if (actualStackSize != DEAD_CODE && expectedStackSize == DEAD_CODE) { + throw AssertionError("Live jump $jumpIndex to dead label $labelIndex") + } + else { + val marker = jumpNode.getPrevious() + actions.add({ methodNode.instructions.remove(marker) }) + } + } + + actions.forEach { it() } + } + + for (marker in fakesToGotos) { + replaceAlwaysTrueIfeqWithGoto(methodNode, marker) + } + + for (marker in fakesToRemove) { + removeAlwaysFalseIfeq(methodNode, marker) + } + } + + private fun removeAlwaysFalseIfeq(methodNode: MethodNode, nodeToReplace: AbstractInsnNode) { + with (methodNode.instructions) { + remove(nodeToReplace.getNext()) + remove(nodeToReplace) + } + } + + private fun replaceAlwaysTrueIfeqWithGoto(methodNode: MethodNode, nodeToReplace: AbstractInsnNode) { + with (methodNode.instructions) { + val next = nodeToReplace.getNext() as JumpInsnNode + insertBefore(nodeToReplace, JumpInsnNode(Opcodes.GOTO, next.label)) + remove(nodeToReplace) + remove(next) + } + } + + private fun replaceMarkerWithPops(methodNode: MethodNode, nodeToReplace: AbstractInsnNode, expectedStackSize: Int, frame: Frame) { + with (methodNode.instructions) { + while (frame.getStackSize() > expectedStackSize) { + val top = frame.pop() + insertBefore(nodeToReplace, getPopInstruction(top)) + } + remove(nodeToReplace) + } + } + + private fun getPopInstruction(top: BasicValue) = + InsnNode(when (top.getSize()) { + 1 -> Opcodes.POP + 2 -> Opcodes.POP2 + else -> throw AssertionError("Unexpected value type size") + }) + + private class StackDepthAnalyzer( + owner: String, + methodNode: MethodNode, + val markedJumps: Set + ) : MethodAnalyzer(owner, methodNode, OptimizationBasicInterpreter()) { + protected override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean { + val insnNode = instructions[insn] + return !(insnNode is JumpInsnNode && markedJumps.contains(insnNode)) + } + } + +} + +private inline fun InsnList.forEach(block: (AbstractInsnNode) -> Unit) { + val iter = this.iterator() + while (iter.hasNext()) { + val insn = iter.next() + block(insn) + } +} + diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java index 9ee1153bed1..ac1ef64e6f9 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java @@ -25,9 +25,11 @@ import org.jetbrains.org.objectweb.asm.MethodVisitor; public class OptimizationClassBuilder extends DelegatingClassBuilder { private final ClassBuilder delegate; + private final boolean disableOptimization; - public OptimizationClassBuilder(@NotNull ClassBuilder delegate) { + public OptimizationClassBuilder(@NotNull ClassBuilder delegate, boolean disableOptimization) { this.delegate = delegate; + this.disableOptimization = disableOptimization; } @NotNull @@ -48,6 +50,7 @@ public class OptimizationClassBuilder extends DelegatingClassBuilder { ) { return new OptimizationMethodVisitor( super.newMethod(origin, access, name, desc, signature, exceptions), + disableOptimization, access, name, desc, signature, exceptions ); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java index 05072f92b9b..9819fa9650f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java @@ -24,9 +24,11 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; public class OptimizationClassBuilderFactory implements ClassBuilderFactory { private final ClassBuilderFactory delegate; + private final boolean disableOptimization; - public OptimizationClassBuilderFactory(ClassBuilderFactory delegate) { + public OptimizationClassBuilderFactory(ClassBuilderFactory delegate, boolean disableOptimization) { this.delegate = delegate; + this.disableOptimization = disableOptimization; } @NotNull @@ -38,7 +40,7 @@ public class OptimizationClassBuilderFactory implements ClassBuilderFactory { @NotNull @Override public ClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) { - return new OptimizationClassBuilder(delegate.newClassBuilder(origin)); + return new OptimizationClassBuilder(delegate.newClassBuilder(origin), disableOptimization); } @Override diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java index 7525cca74c7..6cf98fb407c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java @@ -35,7 +35,12 @@ import java.util.List; public class OptimizationMethodVisitor extends MethodVisitor { private static final int MEMORY_LIMIT_BY_METHOD_MB = 50; - private static final MethodTransformer[] TRANSFORMERS = new MethodTransformer[]{ + + private static final MethodTransformer[] MANDATORY_TRANSFORMERS = new MethodTransformer[] { + new FixStackBeforeJumpTransformer() + }; + + private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] { new RedundantNullCheckMethodTransformer(), new RedundantBoxingMethodTransformer(), new DeadCodeEliminationMethodTransformer(), @@ -45,9 +50,11 @@ public class OptimizationMethodVisitor extends MethodVisitor { private final MethodNode methodNode; private final MethodVisitor delegate; + private final boolean disableOptimization; public OptimizationMethodVisitor( @NotNull MethodVisitor delegate, + boolean disableOptimization, int access, @NotNull String name, @NotNull String desc, @@ -59,6 +66,7 @@ public class OptimizationMethodVisitor extends MethodVisitor { this.methodNode = new MethodNode(access, name, desc, signature, exceptions); this.methodNode.localVariables = new ArrayList(5); this.mv = InlineCodegenUtil.wrapWithMaxLocalCalc(methodNode); + this.disableOptimization = disableOptimization; } @Override @@ -70,10 +78,15 @@ public class OptimizationMethodVisitor extends MethodVisitor { super.visitEnd(); - if (canBeAnalyzed(methodNode)) { - for (MethodTransformer transformer : TRANSFORMERS) { + if (shouldBeTransformed(methodNode)) { + for (MethodTransformer transformer : MANDATORY_TRANSFORMERS) { transformer.transform("fake", methodNode); } + if (canBeOptimized(methodNode) && !disableOptimization) { + for (MethodTransformer transformer : OPTIMIZATION_TRANSFORMERS) { + transformer.transform("fake", methodNode); + } + } CommonPackage.prepareForEmitting(methodNode); } @@ -121,11 +134,12 @@ public class OptimizationMethodVisitor extends MethodVisitor { return traceMethodVisitor; } - private static boolean canBeAnalyzed(@NotNull MethodNode node) { - int totalFramesSizeMb = node.instructions.size() * - (node.maxLocals + node.maxStack) / (1024 * 1024); + private static boolean shouldBeTransformed(@NotNull MethodNode node) { + return node.instructions.size() > 0; + } - return node.instructions.size() > 0 && - totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB; + private static boolean canBeOptimized(@NotNull MethodNode node) { + int totalFramesSizeMb = node.instructions.size() * (node.maxLocals + node.maxStack) / (1024 * 1024); + return totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB; } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/MethodAnalyzer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/MethodAnalyzer.kt new file mode 100644 index 00000000000..2e78f8a251a --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/MethodAnalyzer.kt @@ -0,0 +1,220 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization.common + +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis +import org.jetbrains.org.objectweb.asm.tree.analysis.* +import org.jetbrains.org.objectweb.asm.util.Printer +import java.util.* +import kotlin.properties.Delegates + +public open class MethodAnalyzer( + public val owner: String, + public val method: MethodNode, + protected val interpreter: Interpreter +) { + public val instructions: InsnList = method.instructions + public val nInsns: Int = instructions.size() + + public val frames: Array?> = arrayOfNulls(nInsns) + + private val handlers: Array?> = arrayOfNulls(nInsns) + private val queued: BooleanArray = BooleanArray(nInsns) + private val queue: IntArray = IntArray(nInsns) + private var top: Int = 0 + + protected open fun init(owner: String, m: MethodNode) {} + + protected open fun newFrame(nLocals: Int, nStack: Int): Frame = Frame(nLocals, nStack) + + protected open fun newFrame(src: Frame): Frame = Frame(src) + + protected open fun visitControlFlowEdge(insn: Int, successor: Int): Boolean = true + + protected open fun visitControlFlowExceptionEdge(insn: Int, successor: Int): Boolean = true + + protected open fun visitControlFlowExceptionEdge(insn: Int, tcb: TryCatchBlockNode): Boolean = + visitControlFlowExceptionEdge(insn, instructions.indexOf(tcb.handler)) + + public fun analyze(): Array?> { + if (nInsns == 0) return frames + + checkAssertions() + + computeExceptionHandlersForEachInsn(method) + + val current = newFrame(method.maxLocals, method.maxStack) + val handler = newFrame(method.maxLocals, method.maxStack) + initControlFlowAnalysis(current, method, owner) + + while (top > 0) { + val insn = queue[--top] + val f = frames[insn]!! + queued[insn] = false + + val insnNode = method.instructions[insn] + try { + val insnOpcode = insnNode.getOpcode() + val insnType = insnNode.getType() + + if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) { + visitNopInsn(f, insn) + } + else { + current.init(f).execute(insnNode, interpreter) + + when { + insnNode is JumpInsnNode -> + visitJumpInsnNode(insnNode, current, insn, insnOpcode) + insnNode is LookupSwitchInsnNode -> + visitLookupSwitchInsnNode(insnNode, current, insn) + insnNode is TableSwitchInsnNode -> + visitTableSwitchInsnNode(insnNode, current, insn) + insnOpcode != Opcodes.ATHROW && (insnOpcode < Opcodes.IRETURN || insnOpcode > Opcodes.RETURN) -> + visitOpInsn(current, insn) + else -> {} + } + } + + handlers[insn]?.forEach { tcb -> + val type = Type.getObjectType(tcb.type?:"java/lang/Throwable") + val jump = instructions.indexOf(tcb.handler) + if (visitControlFlowExceptionEdge(insn, tcb)) { + handler.init(f) + handler.clearStack() + handler.push(interpreter.newValue(type)) + mergeControlFlowEdge(jump, handler) + } + } + + } + catch (e: AnalyzerException) { + throw AnalyzerException(e.node, "Error at instruction " + insn + ": " + e.getMessage(), e) + } + catch (e: Exception) { + throw AnalyzerException(insnNode, "Error at instruction " + insn + ": " + e.getMessage(), e) + } + + } + + return frames + } + + private fun checkAssertions() { + if (instructions.toArray() any { it.getOpcode() == Opcodes.JSR || it.getOpcode() == Opcodes.RET }) + throw AssertionError("Subroutines are deprecated since Java 6") + } + + private fun visitOpInsn(current: Frame, insn: Int) { + processControlFlowEdge(current, insn, insn + 1) + } + + private fun visitTableSwitchInsnNode(insnNode: TableSwitchInsnNode, current: Frame, insn: Int) { + var jump = instructions.indexOf(insnNode.dflt) + processControlFlowEdge(current, insn, jump) + for (label in insnNode.labels) { + jump = instructions.indexOf(label) + processControlFlowEdge(current, insn, jump) + } + } + + private fun visitLookupSwitchInsnNode(insnNode: LookupSwitchInsnNode, current: Frame, insn: Int) { + var jump = instructions.indexOf(insnNode.dflt) + processControlFlowEdge(current, insn, jump) + for (label in insnNode.labels) { + jump = instructions.indexOf(label) + processControlFlowEdge(current, insn, jump) + } + } + + private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame, insn: Int, insnOpcode: Int) { + if (insnOpcode != Opcodes.GOTO && insnOpcode != Opcodes.JSR) { + processControlFlowEdge(current, insn, insn + 1) + } + val jump = instructions.indexOf(insnNode.label) + processControlFlowEdge(current, insn, jump) + } + + private fun visitNopInsn(f: Frame, insn: Int) { + processControlFlowEdge(f, insn, insn + 1) + } + + private fun processControlFlowEdge(current: Frame, insn: Int, jump: Int) { + if (visitControlFlowEdge(insn, jump)) { + mergeControlFlowEdge(jump, current) + } + } + + private fun initControlFlowAnalysis(current: Frame, m: MethodNode, owner: String) { + current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))) + val args = Type.getArgumentTypes(m.desc) + var local = 0 + if ((m.access and Opcodes.ACC_STATIC) == 0) { + val ctype = Type.getObjectType(owner) + current.setLocal(local++, interpreter.newValue(ctype)) + } + for (i in args.indices) { + current.setLocal(local++, interpreter.newValue(args[i])) + if (args[i].getSize() == 2) { + current.setLocal(local++, interpreter.newValue(null)) + } + } + while (local < m.maxLocals) { + current.setLocal(local++, interpreter.newValue(null)) + } + mergeControlFlowEdge(0, current) + + init(owner, m) + } + + private fun computeExceptionHandlersForEachInsn(m: MethodNode) { + for (i in m.tryCatchBlocks.indices) { + val tcb = m.tryCatchBlocks.get(i) + val begin = instructions.indexOf(tcb.start) + val end = instructions.indexOf(tcb.end) + for (j in begin..end - 1) { + var insnHandlers: MutableList? = handlers[j] + if (insnHandlers == null) { + insnHandlers = ArrayList() + handlers[j] = insnHandlers + } + insnHandlers.add(tcb) + } + } + } + + private fun mergeControlFlowEdge(insn: Int, frame: Frame) { + val oldFrame = frames[insn] + var changes: Boolean + + if (oldFrame == null) { + frames[insn] = newFrame(frame) + changes = true + } + else { + changes = oldFrame.merge(frame, interpreter) + } + if (changes && !queued[insn]) { + queued[insn] = true + queue[top++] = insn + } + } + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt new file mode 100644 index 00000000000..d1608dfe2a8 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.pseudoInsns + +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.InsnList +import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode +import kotlin.platform.platformStatic + +public val PSEUDO_INSN_CALL_OWNER: String = "kotlin.jvm.\$PseudoInsn" +public val PSEUDO_INSN_PARTS_SEPARATOR: String = ":" + +public enum class PseudoInsnOpcode(val signature: String = "()V") { + FIX_STACK_BEFORE_JUMP(), + FAKE_ALWAYS_TRUE_IFEQ("()I"), + FAKE_ALWAYS_FALSE_IFEQ("()I") + ; + + public fun insnOf(): PseudoInsn = PseudoInsn(this, emptyList()) + public fun insnOf(args: List): PseudoInsn = PseudoInsn(this, args) + + public fun parseOrNull(insn: AbstractInsnNode): PseudoInsn? { + val pseudo = parseOrNull(insn) + return if (pseudo?.opcode == this) pseudo else null + } + + public fun isa(insn: AbstractInsnNode): Boolean = + if (isPseudoInsn(insn)) { + val methodName = (insn as MethodInsnNode).name + methodName == this.toString() || methodName.startsWith(this.toString() + PSEUDO_INSN_PARTS_SEPARATOR) + } + else false + + public fun emit(iv: InstructionAdapter) { + insnOf().emit(iv) + } +} + +public class PseudoInsn(public val opcode: PseudoInsnOpcode, public val args: List) { + public val encodedMethodName: String = + if (args.isEmpty()) + opcode.toString() + else + opcode.toString() + PSEUDO_INSN_PARTS_SEPARATOR + args.join(PSEUDO_INSN_PARTS_SEPARATOR) + + public fun emit(iv: InstructionAdapter) { + iv.invokestatic(PSEUDO_INSN_CALL_OWNER, encodedMethodName, opcode.signature, false) + } +} + +public fun InstructionAdapter.fixStackAndJump(label: Label) { + PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP.emit(this) + this.goTo(label) +} + +public fun InstructionAdapter.fakeAlwaysTrueIfeq(label: Label) { + PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ.emit(this) + this.ifeq(label) +} + +public fun InstructionAdapter.fakeAlwaysFalseIfeq(label: Label) { + PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ.emit(this) + this.ifeq(label) +} + +public fun parseOrNull(insn: AbstractInsnNode): PseudoInsn? = + if (isPseudoInsn(insn)) + parseParts(getPseudoInsnParts(insn as MethodInsnNode)) + else null + +private fun isPseudoInsn(insn: AbstractInsnNode) = + insn is MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESTATIC && insn.owner == PSEUDO_INSN_CALL_OWNER + +private fun getPseudoInsnParts(insn: MethodInsnNode): List = + insn.name.splitBy(PSEUDO_INSN_PARTS_SEPARATOR) + +private fun parseParts(parts: List): PseudoInsn? { + try { + return PseudoInsnOpcode.valueOf(parts[0]).insnOf(parts.subList(1, parts.size())) + } + catch (e: IllegalArgumentException) { + return null + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java index 6ebfc747c97..f444f480dc2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java @@ -149,9 +149,7 @@ public class GenerationState { this.intrinsics = new IntrinsicMethods(); - if (!disableOptimization) { - builderFactory = new OptimizationClassBuilderFactory(builderFactory); - } + builderFactory = new OptimizationClassBuilderFactory(builderFactory, disableOptimization); ClassBuilderFactory interceptedBuilderFactory = new BuilderFactoryForDuplicateSignatureDiagnostics( builderFactory, this.bindingContext, diagnostics); diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakFromOuter.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakFromOuter.kt new file mode 100644 index 00000000000..7566e7c14a9 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakFromOuter.kt @@ -0,0 +1,12 @@ +fun box(): String { + OUTER@while (true) { + var x = "" + try { + do { + x = x + break@OUTER + } while (true) + } finally { + return "OK" + } + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakInExpr.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakInExpr.kt new file mode 100644 index 00000000000..203452b0a60 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakInExpr.kt @@ -0,0 +1,9 @@ +fun test(str: String): String { + var s = "" + for (i in 1..3) { + s += if (i<2) str else break + } + return s +} + +fun box(): String = test("OK") \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/continueInExpr.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/continueInExpr.kt new file mode 100644 index 00000000000..667cd474bd0 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/continueInExpr.kt @@ -0,0 +1,7 @@ +fun box(): String { + var s = "OK" + for (i in 1..3) { + s = s + if (i<2) "" else continue + } + return s +} diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/inlineWithStack.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/inlineWithStack.kt new file mode 100644 index 00000000000..7c5b904df3e --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/inlineWithStack.kt @@ -0,0 +1,17 @@ +inline fun bar(block: () -> String) : String { + return block() +} + +inline fun bar2() : String { + while (true) break + return bar { return "def" } +} + +fun foobar(x: String, y: String, z: String) = x + y + z + +fun box(): String { + val test = foobar("abc", bar2(), "ghi") + return if (test == "abcdefghi") + "OK" + else "Failed, test=$test" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/innerLoopWithStack.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/innerLoopWithStack.kt new file mode 100644 index 00000000000..96cf2e2eaec --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/innerLoopWithStack.kt @@ -0,0 +1,9 @@ +fun box(): String { + var x = "OK" + do { + while (true) { + x = x + break + } + } while (false) + return x +} diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt new file mode 100644 index 00000000000..8c736e5952b --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt @@ -0,0 +1,13 @@ +fun foo(x: Long, y: Int, z: Double, s: String) {} + +fun box(): String { + while (true) { + try { + foo(0, 0, 0.0, "" + continue) + } + finally { + foo(0, 0, 0.0, "" + break) + } + } + return "OK" +} diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally1.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally1.kt new file mode 100644 index 00000000000..b918861535a --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally1.kt @@ -0,0 +1,12 @@ +fun box(): String { + var x = "OK" + while (true) { + try { + x = x + continue + } + finally { + x = x + break + } + } + return x +} diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally2.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally2.kt new file mode 100644 index 00000000000..147362a181c --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally2.kt @@ -0,0 +1,13 @@ +fun box(): String { + var r = "" + for (i in 1..1) { + try { + r += "O" + break + } finally { + r += "K" + continue + } + } + return r +} diff --git a/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/whileTrueBreak.kt b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/whileTrueBreak.kt new file mode 100644 index 00000000000..09b73099a16 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/whileTrueBreak.kt @@ -0,0 +1,4 @@ +fun box(): String { + while (true) break + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/controlStructures/continueInExpr.kt b/compiler/testData/codegen/boxWithStdlib/controlStructures/continueInExpr.kt new file mode 100644 index 00000000000..f5d84f7340c --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/controlStructures/continueInExpr.kt @@ -0,0 +1,14 @@ +fun concatNonNulls(strings: List): String { + var result = "" + for (str in strings) { + result += str?:continue + } + return result +} + +fun box(): String { + val test = concatNonNulls(listOf("abc", null, null, "", null, "def")) + if (test != "abcdef") return "Failed: test=$test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java index d99e42a4664..35e3b9989f6 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -2196,6 +2196,69 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchFinallyChain.kt"); doTest(fileName); } + + @TestMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class BreakContinueInExpressions extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInBreakContinueInExpressions() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("breakFromOuter.kt") + public void testBreakFromOuter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakFromOuter.kt"); + doTest(fileName); + } + + @TestMetadata("breakInExpr.kt") + public void testBreakInExpr() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakInExpr.kt"); + doTest(fileName); + } + + @TestMetadata("continueInExpr.kt") + public void testContinueInExpr() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/continueInExpr.kt"); + doTest(fileName); + } + + @TestMetadata("inlineWithStack.kt") + public void testInlineWithStack() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/inlineWithStack.kt"); + doTest(fileName); + } + + @TestMetadata("innerLoopWithStack.kt") + public void testInnerLoopWithStack() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/innerLoopWithStack.kt"); + doTest(fileName); + } + + @TestMetadata("popSizes.kt") + public void testPopSizes() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt"); + doTest(fileName); + } + + @TestMetadata("tryFinally1.kt") + public void testTryFinally1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally1.kt"); + doTest(fileName); + } + + @TestMetadata("tryFinally2.kt") + public void testTryFinally2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally2.kt"); + doTest(fileName); + } + + @TestMetadata("whileTrueBreak.kt") + public void testWhileTrueBreak() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/whileTrueBreak.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/deadCodeElimination") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index abaf55f04e1..675239c3466 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -917,6 +917,21 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode } } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/controlStructures") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ControlStructures extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInControlStructures() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/controlStructures"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("continueInExpr.kt") + public void testContinueInExpr() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/controlStructures/continueInExpr.kt"); + doTestWithStdlib(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/dataClasses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)