diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt index decb9bc8b46..4c23e5bb50f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/BranchedValue.kt @@ -17,7 +17,6 @@ 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 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 99be45477c4..384cfe537f0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -2045,7 +2045,8 @@ public class ExpressionCodegen extends JetVisitor implem ClassDescriptor scriptClass = bindingContext.get(CLASS_FOR_SCRIPT, scriptDescriptor); StackValue script = StackValue.thisOrOuter(this, scriptClass, false, false); Type fieldType = typeMapper.mapType(valueParameterDescriptor); - return StackValue.field(fieldType, scriptClassType, valueParameterDescriptor.getName().getIdentifier(), false, script, valueParameterDescriptor); + return StackValue.field(fieldType, scriptClassType, valueParameterDescriptor.getName().getIdentifier(), false, script, + valueParameterDescriptor); } throw new UnsupportedOperationException("don't know how to generate reference " + descriptor); @@ -3596,14 +3597,14 @@ The "returned" value of try expression with no finally is either the last expres return StackValue.operation(expectedAsmType, new Function1() { @Override public Unit invoke(InstructionAdapter v) { - - JetFinallySection finallyBlock = expression.getFinallyBlock(); + JetFinallySection finallyBlock = expression.getFinallyBlock(); FinallyBlockStackElement finallyBlockStackElement = null; if (finallyBlock != null) { finallyBlockStackElement = new FinallyBlockStackElement(expression); blockStackElements.push(finallyBlockStackElement); } + //PseudoInsnsPackage.saveStackBeforeTryExpr(v); Label tryStart = new Label(); v.mark(tryStart); @@ -3667,6 +3668,7 @@ The "returned" value of try expression with no finally is either the last expres v.mark(defaultCatchStart); int savedException = myFrameMap.enterTemp(JAVA_THROWABLE_TYPE); v.store(savedException, JAVA_THROWABLE_TYPE); + Label defaultCatchEnd = new Label(); v.mark(defaultCatchEnd); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index 8db898e6d26..d5f142da4e1 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -471,6 +471,32 @@ public class InlineCodegenUtil { "()V", false); } + public static boolean isInlineMarker(AbstractInsnNode insn) { + return isInlineMarker(insn, null); + } + + public static boolean isInlineMarker(AbstractInsnNode insn, String name) { + if (insn instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insn; + return insn.getOpcode() == Opcodes.INVOKESTATIC && + methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) && + (name != null ? methodInsnNode.name.equals(name) + : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) || + methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME)); + } + else { + return false; + } + } + + public static boolean isBeforeInlineMarker(AbstractInsnNode insn) { + return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME); + } + + public static boolean isAfterInlineMarker(AbstractInsnNode insn) { + return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME); + } + public static int getLoadStoreArgSize(int opcode) { return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1; } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java index 684f3808526..b687a7ccca3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.codegen.ClosureCodegen; import org.jetbrains.kotlin.codegen.StackValue; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods; +import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer; import org.jetbrains.kotlin.codegen.state.JetTypeMapper; import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass; import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache; @@ -373,6 +374,8 @@ public class MethodInliner { protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) { node = prepareNode(node, finallyDeepShift); + MandatoryMethodTransformer.INSTANCE$.transform("fake", node); + Analyzer analyzer = new Analyzer(new SourceInterpreter()) { @NotNull @Override diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt deleted file mode 100644 index 1e39ae7eef0..00000000000 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/FixStackBeforeJumpTransformer.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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/LabelNormalizationMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/LabelNormalizationMethodTransformer.kt new file mode 100644 index 00000000000..0ff23c89494 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/LabelNormalizationMethodTransformer.kt @@ -0,0 +1,155 @@ +/* + * 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.transformer.MethodTransformer +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.tree.* + +public object LabelNormalizationMethodTransformer : MethodTransformer() { + val newLabelNodes = hashMapOf() + val removedLabelNodes = hashSetOf() + + public override fun transform(internalClassName: String, methodNode: MethodNode) { + newLabelNodes.clear() + removedLabelNodes.clear() + + with(methodNode.instructions) { + insertBefore(getFirst(), LabelNode(Label())) + insert(getLast(), LabelNode(Label())) + } + + rewriteLabelInsns(methodNode) + if (removedLabelNodes.isEmpty()) return + + rewriteInsns(methodNode) + rewriteTryCatchBlocks(methodNode) + rewriteLocalVars(methodNode) + } + + private fun rewriteLabelInsns(methodNode: MethodNode) { + var prevLabelNode: LabelNode? = null + var thisNode = methodNode.instructions.getFirst() + while (thisNode != null) { + if (thisNode is LabelNode) { + if (prevLabelNode != null) { + newLabelNodes[thisNode] = prevLabelNode + removedLabelNodes.add(thisNode) + thisNode = methodNode.instructions.removeNodeGetNext(thisNode) + } + else { + prevLabelNode = thisNode + newLabelNodes[thisNode] = thisNode + thisNode = thisNode.getNext() + } + } + else { + prevLabelNode = null + thisNode = thisNode.getNext() + } + } + } + + private fun rewriteInsns(methodNode: MethodNode) { + var thisNode = methodNode.instructions.getFirst() + while (thisNode != null) { + thisNode = when (thisNode) { + is JumpInsnNode -> + rewriteJumpInsn(methodNode, thisNode) + is LineNumberNode -> + rewriteLineNumberNode(methodNode, thisNode) + is LookupSwitchInsnNode -> + rewriteLookupSwitchInsn(methodNode, thisNode) + is TableSwitchInsnNode -> + rewriteTableSwitchInsn(methodNode, thisNode) + is FrameNode -> + rewriteFrameNode(methodNode, thisNode) + else -> + thisNode.getNext() + } + } + } + + private fun rewriteLineNumberNode(methodNode: MethodNode, oldLineNode: LineNumberNode): AbstractInsnNode? { + if (isRemoved(oldLineNode.start)) { + val newLineNode = oldLineNode.clone(newLabelNodes) + return methodNode.instructions.replaceNodeGetNext(oldLineNode, newLineNode) + } + else { + return oldLineNode.getNext() + } + } + + private fun rewriteJumpInsn(methodNode: MethodNode, oldJumpNode: JumpInsnNode): AbstractInsnNode? { + if (isRemoved(oldJumpNode.label)) { + val newJumpNode = oldJumpNode.clone(newLabelNodes) + return methodNode.instructions.replaceNodeGetNext(oldJumpNode, newJumpNode) + } + else { + return oldJumpNode.getNext() + } + } + + private fun rewriteLookupSwitchInsn(methodNode: MethodNode, oldSwitchNode: LookupSwitchInsnNode): AbstractInsnNode? = + methodNode.instructions.replaceNodeGetNext(oldSwitchNode, oldSwitchNode.clone(newLabelNodes)) + + private fun rewriteTableSwitchInsn(methodNode: MethodNode, oldSwitchNode: TableSwitchInsnNode): AbstractInsnNode? = + methodNode.instructions.replaceNodeGetNext(oldSwitchNode, oldSwitchNode.clone(newLabelNodes)) + + private fun rewriteFrameNode(methodNode: MethodNode, oldFrameNode: FrameNode): AbstractInsnNode? = + methodNode.instructions.replaceNodeGetNext(oldFrameNode, oldFrameNode.clone(newLabelNodes)) + + private fun rewriteTryCatchBlocks(methodNode: MethodNode) { + methodNode.tryCatchBlocks = methodNode.tryCatchBlocks.map { oldTcb -> + if (isRemoved(oldTcb.start) || isRemoved(oldTcb.end) || isRemoved(oldTcb.handler)) { + val newTcb = TryCatchBlockNode(getNew(oldTcb.start), getNew(oldTcb.end), getNew(oldTcb.handler), oldTcb.type) + newTcb.visibleTypeAnnotations = oldTcb.visibleTypeAnnotations + newTcb.invisibleTypeAnnotations = oldTcb.invisibleTypeAnnotations + newTcb + } + else { + oldTcb + } + } + } + + private fun rewriteLocalVars(methodNode: MethodNode) { + methodNode.localVariables = methodNode.localVariables.map { oldVar -> + if (isRemoved(oldVar.start) || isRemoved(oldVar.end)) { + LocalVariableNode(oldVar.name, oldVar.desc, oldVar.signature, getNew(oldVar.start), getNew(oldVar.end), oldVar.index) + } + else { + oldVar + } + } + } + + private fun isRemoved(labelNode: LabelNode): Boolean = removedLabelNodes.contains(labelNode) + private fun getNew(oldLabelNode: LabelNode): LabelNode = newLabelNodes[oldLabelNode] +} + +private fun InsnList.replaceNodeGetNext(oldNode: AbstractInsnNode, newNode: AbstractInsnNode): AbstractInsnNode? { + insertBefore(oldNode, newNode) + remove(oldNode) + return newNode.getNext() +} + +private fun InsnList.removeNodeGetNext(oldNode: AbstractInsnNode): AbstractInsnNode? { + val next = oldNode.getNext() + remove(oldNode) + return next +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt new file mode 100644 index 00000000000..2772abf06c2 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt @@ -0,0 +1,28 @@ +/* + * 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.fixStack.FixStackMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.org.objectweb.asm.tree.MethodNode + +public object MandatoryMethodTransformer : MethodTransformer() { + public override fun transform(internalClassName: String, methodNode: MethodNode) { + LabelNormalizationMethodTransformer.transform(internalClassName, methodNode) + FixStackMethodTransformer.transform(internalClassName, methodNode) + } +} \ No newline at end of file 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 6cf98fb407c..8027d2e79e3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantNullCheckMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.common.CommonPackage; +import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.Opcodes; @@ -36,16 +37,11 @@ import java.util.List; public class OptimizationMethodVisitor extends MethodVisitor { private static final int MEMORY_LIMIT_BY_METHOD_MB = 50; - private static final MethodTransformer[] MANDATORY_TRANSFORMERS = new MethodTransformer[] { - new FixStackBeforeJumpTransformer() - }; - private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] { new RedundantNullCheckMethodTransformer(), new RedundantBoxingMethodTransformer(), new DeadCodeEliminationMethodTransformer(), - new RedundantGotoMethodTransformer(), - new StoreStackBeforeInlineMethodTransformer() + new RedundantGotoMethodTransformer() }; private final MethodNode methodNode; @@ -79,9 +75,7 @@ public class OptimizationMethodVisitor extends MethodVisitor { super.visitEnd(); if (shouldBeTransformed(methodNode)) { - for (MethodTransformer transformer : MANDATORY_TRANSFORMERS) { - transformer.transform("fake", methodNode); - } + MandatoryMethodTransformer.INSTANCE$.transform("fake", methodNode); if (canBeOptimized(methodNode) && !disableOptimization) { for (MethodTransformer transformer : OPTIMIZATION_TRANSFORMERS) { transformer.transform("fake", methodNode); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/StoreStackBeforeInlineMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/StoreStackBeforeInlineMethodTransformer.kt deleted file mode 100644 index e3bae6ebae2..00000000000 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/StoreStackBeforeInlineMethodTransformer.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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.transformer.MethodTransformer -import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter -import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil -import org.jetbrains.org.objectweb.asm.tree.MethodNode -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode -import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode -import org.jetbrains.org.objectweb.asm.tree.analysis.Frame -import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue -import org.jetbrains.org.objectweb.asm.tree.VarInsnNode -import org.jetbrains.org.objectweb.asm.Type -import com.intellij.util.containers.Stack - -class StoreStackBeforeInlineMethodTransformer : MethodTransformer() { - override fun transform(internalClassName: String, methodNode: MethodNode) { - val frames = MethodTransformer.analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) - if (needToProcess(methodNode, frames)) { - process(methodNode, frames) - } - else { - removeInlineMarkers(methodNode) - } - } -} - -private fun needToProcess(node: MethodNode, frames: Array?>): Boolean { - val insns = node.instructions.toArray() - var balance = 0 - var isThereAnyInlineMarker = false - - for ((insn, frame) in insns.zip(frames)) { - if (isInlineMarker(insn)) { - isThereAnyInlineMarker = true - - // inline marker is not available - if (frame == null) return false - } - - if (isBeforeInlineMarker(insn)) { - balance++ - } - else if(isAfterInlineMarker(insn)) { - balance-- - } - - if (balance < 0) return false - } - - return balance == 0 && isThereAnyInlineMarker -} - -private fun isBeforeInlineMarker(insn: AbstractInsnNode) = isInlineMarker(insn, InlineCodegenUtil.INLINE_MARKER_BEFORE_METHOD_NAME) - -private fun isAfterInlineMarker(insn: AbstractInsnNode) = isInlineMarker(insn, InlineCodegenUtil.INLINE_MARKER_AFTER_METHOD_NAME) - -private fun isInlineMarker(insn: AbstractInsnNode, markerName: String? = null): Boolean { - return insn.getOpcode() == Opcodes.INVOKESTATIC && - insn is MethodInsnNode && - insn.owner == InlineCodegenUtil.INLINE_MARKER_CLASS_NAME && - if (markerName != null) markerName == insn.name else ( - insn.name == InlineCodegenUtil.INLINE_MARKER_BEFORE_METHOD_NAME || - insn.name == InlineCodegenUtil.INLINE_MARKER_AFTER_METHOD_NAME - ) -} - -private fun process(methodNode: MethodNode, frames: Array?>) { - val insns = methodNode.instructions.toArray() - - val storedValuesDescriptorsStack = Stack() - var firstAvailableVarIndex = methodNode.maxLocals - var currentStoredValuesCount = 0 - - for ((insn, frame) in insns.zip(frames)) { - if (isBeforeInlineMarker(insn)) { - frame ?: throw AssertionError("process method shouldn't be called if frame is null before inline marker") - - val desc = storeStackValues(methodNode, frame, insn, firstAvailableVarIndex, currentStoredValuesCount) - - firstAvailableVarIndex += desc.storedStackSize - currentStoredValuesCount += desc.storedValuesCount - storedValuesDescriptorsStack.push(desc) - } - else if (isAfterInlineMarker(insn)) { - frame ?: throw AssertionError("process method shouldn't be called if frame is null before inline marker") - - val desc = storedValuesDescriptorsStack.pop() ?: - throw AssertionError("should be non null becase markers are balanced") - - loadStackValues(methodNode, frame, insn, desc) - firstAvailableVarIndex -= desc.storedStackSize - currentStoredValuesCount -= desc.storedValuesCount - } - - if (isInlineMarker(insn)) { - methodNode.instructions.remove(insn) - } - } -} - -private class StoredStackValuesDescriptor( - val values: List, - val firstVariableIndex: Int, - val storedStackSize: Int, - alreadyStoredValuesCount: Int -) { - val nextFreeVarIndex : Int get() = firstVariableIndex + storedStackSize - val storedValuesCount: Int get() = values.size() - val isStored: Boolean get() = storedValuesCount > 0 - val totalValuesCountOnStackBeforeInline = alreadyStoredValuesCount + storedValuesCount -} - -private fun removeInlineMarkers(node: MethodNode) { - for (insn in node.instructions.toArray()) { - if (isInlineMarker(insn)) { - node.instructions.remove(insn) - } - } -} - -private fun storeStackValues( - node: MethodNode, - frame: Frame, - beforeInlineMarker: AbstractInsnNode, - firstAvailableVarIndex: Int, - alreadyStoredValuesCount: Int -) : StoredStackValuesDescriptor { - var stackSize = 0 - - val values = frame.getStackValuesStartingFrom(alreadyStoredValuesCount) - - for (value in values.reverse()) { - node.instructions.insertBefore( - beforeInlineMarker, - VarInsnNode( - value.getType()!!.getOpcode(Opcodes.ISTORE), - firstAvailableVarIndex + stackSize - ) - ) - stackSize += value.getSize() - } - - node.updateMaxLocals(firstAvailableVarIndex + stackSize) - - return StoredStackValuesDescriptor(values, firstAvailableVarIndex, stackSize, alreadyStoredValuesCount) -} - -private fun loadStackValues( - node: MethodNode, - frame: Frame, - afterInlineMarker: AbstractInsnNode, - desc: StoredStackValuesDescriptor -) { - if (!desc.isStored) return - - val insns = node.instructions - var returnValueVarIndex = -1 - var returnType : Type? = null - - if (frame.getStackSize() != desc.totalValuesCountOnStackBeforeInline) { - // only returned value - assert( - (frame.getStackSize() - desc.totalValuesCountOnStackBeforeInline) == 1, - "Stack sizes should not differ by more than 1 (returned value)" - ) - - returnValueVarIndex = desc.nextFreeVarIndex - returnType = frame.getStack(frame.getStackSize() - 1)!!.getType() - node.updateMaxLocals(returnValueVarIndex + returnType!!.getSize()) - - insns.insertBefore( - afterInlineMarker, - VarInsnNode(returnType!!.getOpcode(Opcodes.ISTORE), returnValueVarIndex) - ) - } - - var currentVarIndex = desc.firstVariableIndex + desc.storedStackSize - - for (value in desc.values) { - currentVarIndex -= value.getSize() - insns.insertBefore( - afterInlineMarker, - VarInsnNode( - value.getType()!!.getOpcode(Opcodes.ILOAD), - currentVarIndex - ) - ) - } - - if (returnValueVarIndex != -1) { - insns.insertBefore( - afterInlineMarker, - VarInsnNode(returnType!!.getOpcode(Opcodes.ILOAD), returnValueVarIndex) - ) - } -} - -private fun Frame.getStackValuesStartingFrom(from: Int): List = - IntRange(from, getStackSize() - 1).map { getStack(it) }.requireNoNulls() - -private fun MethodNode.updateMaxLocals(newMaxLocals: Int) { - maxLocals = Math.max(maxLocals, newMaxLocals) -} 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 index 2e78f8a251a..6d9a214ece3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/MethodAnalyzer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/MethodAnalyzer.kt @@ -44,7 +44,11 @@ public open class MethodAnalyzer( protected open fun newFrame(nLocals: Int, nStack: Int): Frame = Frame(nLocals, nStack) - protected open fun newFrame(src: Frame): Frame = Frame(src) + protected open fun newFrame(src: Frame): Frame { + val frame = newFrame(src.getLocals(), src.getMaxStackSize()) + frame.init(src) + return frame + } protected open fun visitControlFlowEdge(insn: Int, successor: Int): Boolean = true @@ -94,12 +98,12 @@ public open class MethodAnalyzer( } handlers[insn]?.forEach { tcb -> - val type = Type.getObjectType(tcb.type?:"java/lang/Throwable") + val exnType = 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)) + handler.push(interpreter.newValue(exnType)) mergeControlFlowEdge(jump, handler) } } @@ -117,6 +121,9 @@ public open class MethodAnalyzer( return frames } + public fun getFrame(insn: AbstractInsnNode): Frame? = + frames[instructions.indexOf(insn)] + private fun checkAssertions() { if (instructions.toArray() any { it.getOpcode() == Opcodes.JSR || it.getOpcode() == Opcodes.RET }) throw AssertionError("Subroutines are deprecated since Java 6") @@ -170,9 +177,9 @@ public open class MethodAnalyzer( 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) { + for (arg in args) { + current.setLocal(local++, interpreter.newValue(arg)) + if (arg.getSize() == 2) { current.setLocal(local++, interpreter.newValue(null)) } } @@ -185,8 +192,7 @@ public open class MethodAnalyzer( } private fun computeExceptionHandlersForEachInsn(m: MethodNode) { - for (i in m.tryCatchBlocks.indices) { - val tcb = m.tryCatchBlocks.get(i) + for (tcb in m.tryCatchBlocks) { val begin = instructions.indexOf(tcb.start) val end = instructions.indexOf(tcb.end) for (j in begin..end - 1) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt index 565cbdc2d2d..b291df1fc78 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.codegen.optimization.common import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.InsnList +import org.jetbrains.org.objectweb.asm.tree.LabelNode import org.jetbrains.org.objectweb.asm.tree.analysis.Frame import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue import org.jetbrains.org.objectweb.asm.tree.MethodNode @@ -28,7 +30,9 @@ val AbstractInsnNode.isMeaningful : Boolean get() = else -> true } -class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequence { +public class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequence { + public constructor(insnList: InsnList) : this(insnList.getFirst(), null) + override fun iterator(): Iterator { return object : Iterator { var current: AbstractInsnNode? = from @@ -77,3 +81,22 @@ abstract class BasicValueWrapper(val wrappedValue: BasicValue?) : BasicValue(wra return super.equals(other) && this.javaClass == other?.javaClass } } + +inline fun AbstractInsnNode.findNextOrNull(predicate: (AbstractInsnNode) -> Boolean): AbstractInsnNode? { + var finger = this.getNext() + while (finger != null && !predicate(finger)) { + finger = finger.getNext() + } + return finger +} + +inline fun AbstractInsnNode.findPreviousOrNull(predicate: (AbstractInsnNode) -> Boolean): AbstractInsnNode? { + var finger = this.getPrevious() + while (finger != null && !predicate(finger)) { + finger = finger.getPrevious() + } + return finger +} + +fun AbstractInsnNode.hasOpcode(): Boolean = + getOpcode() >= 0 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/AnalyzeTryCatchBlocks.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/AnalyzeTryCatchBlocks.kt new file mode 100644 index 00000000000..ac330676925 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/AnalyzeTryCatchBlocks.kt @@ -0,0 +1,97 @@ +/* + * 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.fixStack + +import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes +import org.jetbrains.kotlin.codegen.optimization.common.findNextOrNull +import org.jetbrains.kotlin.codegen.optimization.common.hasOpcode +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.util.Printer + +private class DecompiledTryDescriptor(val tryStartLabel: LabelNode) { + var defaultHandlerTcb : TryCatchBlockNode? = null + val handlerStartLabels = hashSetOf() +} + +private fun TryCatchBlockNode.isDefaultHandlerNode(): Boolean = + start == handler + +private fun MethodNode.debugString(tcb: TryCatchBlockNode): String = + "TCB<${instructions.indexOf(tcb.start)}, ${instructions.indexOf(tcb.end)}, ${instructions.indexOf(tcb.handler)}>" + +internal fun insertTryCatchBlocksMarkers(methodNode: MethodNode) { + if (methodNode.tryCatchBlocks.isEmpty()) return + + val decompiledTryDescriptorForStart = linkedMapOf() + val decompiledTryDescriptorForHandler = hashMapOf() + + for (tcb in methodNode.tryCatchBlocks) { + if (tcb.isDefaultHandlerNode()) { + assert(decompiledTryDescriptorForHandler.containsKey(tcb.start), + "${methodNode.debugString(tcb)}: default handler should occur after some regular handler") + } + + val decompiledTryDescriptor = decompiledTryDescriptorForHandler.getOrPut(tcb.handler) { + decompiledTryDescriptorForStart.getOrPut(tcb.start) { + DecompiledTryDescriptor(tcb.start) + } + } + with(decompiledTryDescriptor) { + handlerStartLabels.add(tcb.handler) + + if (tcb.isDefaultHandlerNode()) { + assert(defaultHandlerTcb == null) { + "${methodNode.debugString(tcb)}: default handler is already found: ${methodNode.debugString(defaultHandlerTcb!!)}" + } + + defaultHandlerTcb = tcb + } + } + } + + val doneTryStartLabels = hashSetOf() + val doneHandlerLabels = hashSetOf() + + for (decompiledTryDescriptor in decompiledTryDescriptorForStart.values()) { + with(decompiledTryDescriptor) { + if (!doneTryStartLabels.contains(tryStartLabel)) { + doneTryStartLabels.add(tryStartLabel) + + val nopNode = tryStartLabel.findNextOrNull { it.hasOpcode() }!! + assert(nopNode.getOpcode() == Opcodes.NOP, + "${methodNode.instructions.indexOf(nopNode)}: try block should start with NOP") + + methodNode.instructions.insertBefore(tryStartLabel, PseudoInsn.SAVE_STACK_BEFORE_TRY.createInsnNode()) + methodNode.instructions.insert(nopNode, PseudoInsn.RESTORE_STACK_IN_TRY_CATCH.createInsnNode()) + } + + for (handlerStartLabel in handlerStartLabels) { + if (!doneHandlerLabels.contains(handlerStartLabel)) { + doneHandlerLabels.add(handlerStartLabel) + + val storeNode = handlerStartLabel.findNextOrNull { it.hasOpcode() }!! + assert(storeNode.getOpcode() == Opcodes.ASTORE, + "${methodNode.instructions.indexOf(storeNode)}: handler should start with ASTORE") + + methodNode.instructions.insert(storeNode, PseudoInsn.RESTORE_STACK_IN_TRY_CATCH.createInsnNode()) + } + } + } + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackAnalyzer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackAnalyzer.kt new file mode 100644 index 00000000000..e1001342b5a --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackAnalyzer.kt @@ -0,0 +1,157 @@ +/* + * 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.fixStack + +import com.intellij.util.SmartList +import com.intellij.util.containers.Stack +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame +import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter +import java.util.* + +public class FixStackAnalyzer( + owner: String, + methodNode: MethodNode, + val context: FixStackContext +) : MethodAnalyzer(owner, methodNode, OptimizationBasicInterpreter()) { + val savedStacks = hashMapOf>() + var maxExtraStackSize = 0; private set + + protected override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean { + val insnNode = instructions[insn] + return !(insnNode is JumpInsnNode && context.breakContinueGotoNodes.contains(insnNode)) + } + + protected override fun newFrame(nLocals: Int, nStack: Int): Frame = + FixStackFrame(nLocals, nStack) + + private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node) + + public inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame(nLocals, nStack) { + val extraStack = Stack() + + public override fun init(src: Frame): Frame { + extraStack.clear() + extraStack.addAll((src as FixStackFrame).extraStack) + return super.init(src) + } + + public override fun clearStack() { + extraStack.clear() + super.clearStack() + } + + public override fun execute(insn: AbstractInsnNode, interpreter: Interpreter) { + when { + PseudoInsn.SAVE_STACK_BEFORE_TRY.isa(insn) -> + executeSaveStackBeforeTry(insn) + PseudoInsn.RESTORE_STACK_IN_TRY_CATCH.isa(insn) -> + executeRestoreStackInTryCatch(insn) + InlineCodegenUtil.isBeforeInlineMarker(insn) -> + executeBeforeInlineCallMarker(insn) + InlineCodegenUtil.isAfterInlineMarker(insn) -> + executeAfterInlineCallMarker(insn) + } + + super.execute(insn, interpreter) + } + + public fun getStackContent(): List { + val savedStack = arrayListOf() + IntRange(0, super.getStackSize() - 1).mapTo(savedStack) { super.getStack(it) } + savedStack.addAll(extraStack) + return savedStack + } + + public override fun push(value: BasicValue) { + if (super.getStackSize() < getMaxStackSize()) { + super.push(value) + } + else { + extraStack.add(value) + maxExtraStackSize = Math.max(maxExtraStackSize, extraStack.size()) + } + } + + public fun pushAll(values: Collection) { + values.forEach { push(it) } + } + + public override fun pop(): BasicValue { + if (extraStack.isNotEmpty()) { + return extraStack.pop() + } + else { + return super.pop() + } + } + + public override fun getStack(i: Int): BasicValue { + if (i < super.getMaxStackSize()) { + return super.getStack(i) + } + else { + return extraStack[i - getMaxStackSize()] + } + } + } + + private fun FixStackFrame.executeBeforeInlineCallMarker(insn: AbstractInsnNode) { + saveStackAndClear(insn) + } + + private fun FixStackFrame.saveStackAndClear(insn: AbstractInsnNode) { + val savedValues = getStackContent() + savedStacks[insn] = savedValues + clearStack() + } + + private fun FixStackFrame.executeAfterInlineCallMarker(insn: AbstractInsnNode) { + val beforeInlineMarker = context.openingInlineMethodMarker[insn] + if (getStackSize() > 0) { + val returnValue = pop() + clearStack() + val savedValues = savedStacks[beforeInlineMarker] + pushAll(savedValues) + push(returnValue) + } + else { + val savedValues = savedStacks[beforeInlineMarker] + pushAll(savedValues) + } + } + + private fun FixStackFrame.executeRestoreStackInTryCatch(insn: AbstractInsnNode) { + val saveNode = context.saveStackMarkerForRestoreMarker[insn] + val savedValues = savedStacks.getOrElse(saveNode) { + throw AssertionError("${indexOf(insn)}: Restore stack is unavailable for ${indexOf(saveNode)}") + } + pushAll(savedValues) + } + + private fun FixStackFrame.executeSaveStackBeforeTry(insn: AbstractInsnNode) { + saveStackAndClear(insn) + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackContext.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackContext.kt new file mode 100644 index 00000000000..418d801aada --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackContext.kt @@ -0,0 +1,142 @@ +/* + * 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.fixStack + +import com.intellij.util.SmartList +import com.intellij.util.containers.SmartHashSet +import com.intellij.util.containers.Stack +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence +import org.jetbrains.kotlin.codegen.optimization.common.findPreviousOrNull +import org.jetbrains.kotlin.codegen.optimization.common.hasOpcode +import org.jetbrains.kotlin.codegen.optimization.fixStack.forEachPseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.parsePseudoInsnOrNull +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.* +import java.util.* +import kotlin.properties.Delegates + +internal class FixStackContext(val methodNode: MethodNode) { + val breakContinueGotoNodes = linkedSetOf() + val fakeAlwaysTrueIfeqMarkers = arrayListOf() + val fakeAlwaysFalseIfeqMarkers = arrayListOf() + + val saveStackNodesForTryStartLabel = hashMapOf() + val saveStackMarkerForRestoreMarker = hashMapOf() + val restoreStackMarkersForSaveMarker = hashMapOf>() + + val openingInlineMethodMarker = hashMapOf() + var consistentInlineMarkers: Boolean = true; private set + + init { + insertTryCatchBlocksMarkers(methodNode) + + val inlineMarkersStack = Stack() + + InsnSequence(methodNode.instructions).forEach { insnNode -> + val pseudoInsn = parsePseudoInsnOrNull(insnNode) + when { + pseudoInsn == PseudoInsn.FIX_STACK_BEFORE_JUMP -> + visitFixStackBeforeJump(insnNode) + pseudoInsn == PseudoInsn.FAKE_ALWAYS_TRUE_IFEQ -> + visitFakeAlwaysTrueIfeq(insnNode) + pseudoInsn == PseudoInsn.FAKE_ALWAYS_FALSE_IFEQ -> + visitFakeAlwaysFalseIfeq(insnNode) + pseudoInsn == PseudoInsn.SAVE_STACK_BEFORE_TRY -> + visitSaveStackBeforeTry(insnNode) + pseudoInsn == PseudoInsn.RESTORE_STACK_IN_TRY_CATCH -> + visitRestoreStackInTryCatch(insnNode) + InlineCodegenUtil.isBeforeInlineMarker(insnNode) -> { + inlineMarkersStack.push(insnNode) + } + InlineCodegenUtil.isAfterInlineMarker(insnNode) -> { + assert(inlineMarkersStack.isNotEmpty(), "Mismatching after inline method marker at ${indexOf(insnNode)}") + openingInlineMethodMarker[insnNode] = inlineMarkersStack.pop() + } + } + } + + if (inlineMarkersStack.isNotEmpty()) { + consistentInlineMarkers = false + } + } + + private fun visitFixStackBeforeJump(insnNode: AbstractInsnNode) { + val next = insnNode.getNext() + assert(next.getOpcode() == Opcodes.GOTO, "${indexOf(insnNode)}: should be followed by GOTO") + breakContinueGotoNodes.add(next as JumpInsnNode) + } + + private fun visitFakeAlwaysTrueIfeq(insnNode: AbstractInsnNode) { + assert(insnNode.getNext().getOpcode() == Opcodes.IFEQ, "${indexOf(insnNode)}: should be followed by IFEQ") + fakeAlwaysTrueIfeqMarkers.add(insnNode) + } + + private fun visitFakeAlwaysFalseIfeq(insnNode: AbstractInsnNode) { + assert(insnNode.getNext().getOpcode() == Opcodes.IFEQ, "${indexOf(insnNode)}: should be followed by IFEQ") + fakeAlwaysFalseIfeqMarkers.add(insnNode) + } + + private fun visitSaveStackBeforeTry(insnNode: AbstractInsnNode) { + val tryStartLabel = insnNode.getNext() + assert(tryStartLabel is LabelNode, "${indexOf(insnNode)}: save should be followed by a label") + saveStackNodesForTryStartLabel[tryStartLabel as LabelNode] = insnNode + } + + private fun visitRestoreStackInTryCatch(insnNode: AbstractInsnNode) { + val restoreLabel = insnNode.findPreviousOrNull { it.hasOpcode() }!!.findPreviousOrNull { it is LabelNode || it.hasOpcode() }!! + if (restoreLabel !is LabelNode) { + throw AssertionError("${indexOf(insnNode)}: restore should be preceded by a catch block label") + } + val saveNodes = findMatchingSaveNodes(insnNode, restoreLabel) + if (saveNodes.isEmpty()) { + throw AssertionError("${indexOf(insnNode)}: in handler ${indexOf(restoreLabel)} restore is not matched with save") + } + else if (saveNodes.size() > 1) { + throw AssertionError("${indexOf(insnNode)}: in handler ${indexOf(restoreLabel)} restore is matched with several saves") + } + val saveNode = saveNodes.first() + saveStackMarkerForRestoreMarker[insnNode] = saveNode + restoreStackMarkersForSaveMarker.getOrPut(saveNode, { SmartList() }).add(insnNode) + } + + private fun findMatchingSaveNodes(insnNode: AbstractInsnNode, restoreLabel: LabelNode): List { + val saveNodes = SmartHashSet() + methodNode.tryCatchBlocks.forEach { tcb -> + if (restoreLabel == tcb.start || restoreLabel == tcb.handler) { + saveStackNodesForTryStartLabel[tcb.start]?.let { saveNodes.add(it) } + } + } + return SmartList(saveNodes) + } + + private fun indexOf(node: AbstractInsnNode) = methodNode.instructions.indexOf(node) + + fun hasAnyMarkers(): Boolean = + breakContinueGotoNodes.isNotEmpty() || + fakeAlwaysTrueIfeqMarkers.isNotEmpty() || + fakeAlwaysFalseIfeqMarkers.isNotEmpty() || + saveStackNodesForTryStartLabel.isNotEmpty() || + openingInlineMethodMarker.isNotEmpty() + + fun isAnalysisRequired(): Boolean = + breakContinueGotoNodes.isNotEmpty() || + saveStackNodesForTryStartLabel.isNotEmpty() || + openingInlineMethodMarker.isNotEmpty() + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackMethodTransformer.kt new file mode 100644 index 00000000000..c01bc2985f6 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/FixStackMethodTransformer.kt @@ -0,0 +1,213 @@ +/* + * 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.fixStack + +import com.intellij.util.containers.Stack +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence +import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.fixStack.forEachPseudoInsn +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.parsePseudoInsnOrNull +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame +import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter +import java.util.* +import kotlin.properties.Delegates + +public object FixStackMethodTransformer : MethodTransformer() { + public override fun transform(internalClassName: String, methodNode: MethodNode) { + val context = FixStackContext(methodNode) + + if (!context.hasAnyMarkers()) return + + // If inline method markers are inconsistent, remove them now + if (!context.consistentInlineMarkers) { + InsnSequence(methodNode.instructions).forEach { insnNode -> + if (InlineCodegenUtil.isInlineMarker(insnNode)) + methodNode.instructions.remove(insnNode) + } + } + + if (context.isAnalysisRequired()) { + val analyzer = FixStackAnalyzer(internalClassName, methodNode, context) + analyzer.analyze() + + methodNode.maxStack = methodNode.maxStack + analyzer.maxExtraStackSize + + val actions = arrayListOf<() -> Unit>() + + transformBreakContinueGotos(methodNode, context, actions, analyzer) + + transformSaveRestoreStackMarkers(methodNode, context, actions, analyzer) + + actions.forEach { it() } + } + + context.fakeAlwaysTrueIfeqMarkers.forEach { marker -> + replaceAlwaysTrueIfeqWithGoto(methodNode, marker) + } + + context.fakeAlwaysFalseIfeqMarkers.forEach { marker -> + removeAlwaysFalseIfeq(methodNode, marker) + } + } + + private fun transformBreakContinueGotos( + methodNode: MethodNode, + fixStackContext: FixStackContext, + actions: MutableList<() -> Unit>, + analyzer: FixStackAnalyzer + ) { + fixStackContext.breakContinueGotoNodes.forEach { gotoNode -> + val gotoIndex = methodNode.instructions.indexOf(gotoNode) + val labelIndex = methodNode.instructions.indexOf(gotoNode.label) + + val DEAD_CODE = -1 // Stack size is always non-negative + val actualStackSize = analyzer.frames[gotoIndex]?.getStackSize() ?: DEAD_CODE + val expectedStackSize = analyzer.frames[labelIndex]?.getStackSize() ?: DEAD_CODE + + if (actualStackSize != DEAD_CODE && expectedStackSize != DEAD_CODE) { + assert(expectedStackSize <= actualStackSize, + "Label at $labelIndex, jump at $gotoIndex: stack underflow: $expectedStackSize > $actualStackSize") + val frame = analyzer.frames[gotoIndex]!! + actions.add({ replaceMarkerWithPops(methodNode, gotoNode.getPrevious(), expectedStackSize, frame) }) + } + else if (actualStackSize != DEAD_CODE && expectedStackSize == DEAD_CODE) { + throw AssertionError("Live jump $gotoIndex to dead label $labelIndex") + } + else { + val marker = gotoNode.getPrevious() + actions.add({ methodNode.instructions.remove(marker) }) + } + } + } + + private fun transformSaveRestoreStackMarkers( + methodNode: MethodNode, + context: FixStackContext, + actions: MutableList<() -> Unit>, + analyzer: FixStackAnalyzer + ) { + val localVariablesManager = LocalVariablesManager(context, methodNode) + InsnSequence(methodNode.instructions).forEach { marker -> + val pseudoInsn = parsePseudoInsnOrNull(marker) + when { + pseudoInsn == PseudoInsn.SAVE_STACK_BEFORE_TRY -> + transformSaveStackMarker(methodNode, actions, analyzer, marker, localVariablesManager) + pseudoInsn == PseudoInsn.RESTORE_STACK_IN_TRY_CATCH -> + transformRestoreStackMarker(methodNode, actions, marker, localVariablesManager) + InlineCodegenUtil.isBeforeInlineMarker(marker) -> + transformBeforeInlineCallMarker(methodNode, actions, analyzer, marker, localVariablesManager) + InlineCodegenUtil.isAfterInlineMarker(marker) -> + transformAfterInlineCallMarker(methodNode, actions, analyzer, marker, localVariablesManager) + } + } + } + + private fun transformSaveStackMarker( + methodNode: MethodNode, + actions: MutableList<() -> Unit>, + analyzer: FixStackAnalyzer, + marker: AbstractInsnNode, + localVariablesManager: LocalVariablesManager + ) { + val savedStackValues = analyzer.savedStacks[marker] + if (savedStackValues != null) { + val savedStackDescriptor = localVariablesManager.allocateVariablesForSaveStackMarker(marker, savedStackValues) + actions.add({ saveStack(methodNode, marker, savedStackDescriptor, false) }) + } + else { + // marker is dead code + localVariablesManager.allocateVariablesForSaveStackMarker(marker, emptyList()) + actions.add({ methodNode.instructions.remove(marker) }) + } + } + + private fun transformRestoreStackMarker( + methodNode: MethodNode, + actions: MutableList<() -> Unit>, + marker: AbstractInsnNode, + localVariablesManager: LocalVariablesManager + ) { + val savedStackDescriptor = localVariablesManager.getSavedStackDescriptorOrNull(marker) + if (savedStackDescriptor != null) { + actions.add({ restoreStack(methodNode, marker, savedStackDescriptor) }) + } + else { + // marker is dead code + actions.add({ methodNode.instructions.remove(marker) }) + } + localVariablesManager.markRestoreStackMarkerEmitted(marker) + } + + private fun transformAfterInlineCallMarker( + methodNode: MethodNode, + actions: MutableList<() -> Unit>, + analyzer: FixStackAnalyzer, + inlineMarker: AbstractInsnNode, + localVariablesManager: LocalVariablesManager + ) { + val savedStackDescriptor = localVariablesManager.getBeforeInlineDescriptor(inlineMarker) + val afterInlineFrame = analyzer.getFrame(inlineMarker) as FixStackAnalyzer.FixStackFrame? + if (afterInlineFrame != null && savedStackDescriptor.isNotEmpty()) { + assert(afterInlineFrame.getStackSize() <= 1, "Inline method should not leave more than 1 value on stack") + if (afterInlineFrame.getStackSize() == 1) { + val afterInlineStackValues = afterInlineFrame.getStackContent() + val returnValue = afterInlineStackValues.last() + val returnValueLocalVarIndex = localVariablesManager.createReturnValueVariable(returnValue) + actions.add({ + restoreStackWithReturnValue(methodNode, inlineMarker, savedStackDescriptor, + returnValue, returnValueLocalVarIndex) + }) + } + else { + actions.add({ restoreStack(methodNode, inlineMarker, savedStackDescriptor) }) + } + } + else { + // after inline marker is dead code + actions.add({ methodNode.instructions.remove(inlineMarker) }) + } + localVariablesManager.markAfterInlineMarkerEmitted(inlineMarker) + } + + private fun transformBeforeInlineCallMarker( + methodNode: MethodNode, + actions: MutableList<() -> Unit>, + analyzer: FixStackAnalyzer, + inlineMarker: AbstractInsnNode, + localVariablesManager: LocalVariablesManager + ) { + val savedStackValues = analyzer.savedStacks[inlineMarker] + if (savedStackValues != null) { + val savedStackDescriptor = localVariablesManager.allocateVariablesForBeforeInlineMarker(inlineMarker, savedStackValues) + actions.add({ saveStack(methodNode, inlineMarker, savedStackDescriptor, false) }) + } + else { + // before inline marker is dead code + localVariablesManager.allocateVariablesForBeforeInlineMarker(inlineMarker, emptyList()) + actions.add({ methodNode.instructions.remove(inlineMarker) }) + } + } + + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/LocalVariablesManager.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/LocalVariablesManager.kt new file mode 100644 index 00000000000..90e74c3d96c --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/LocalVariablesManager.kt @@ -0,0 +1,97 @@ +/* + * 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.fixStack + +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue + +internal class LocalVariablesManager(val context: FixStackContext, val methodNode: MethodNode) { + private class AllocatedHandle(val savedStackDescriptor: SavedStackDescriptor, var numRestoreMarkers: Int) { + fun isFullyEmitted(): Boolean = + numRestoreMarkers == 0 + + fun markRestoreNodeEmitted() { + assert(numRestoreMarkers > 0, "Emitted more restore markers than expected for $savedStackDescriptor") + numRestoreMarkers-- + } + } + + private val initialMaxLocals = methodNode.maxLocals + private val allocatedHandles = hashMapOf() + + private fun updateMaxLocals(newValue: Int) { + methodNode.maxLocals = Math.max(methodNode.maxLocals, newValue) + } + + fun allocateVariablesForSaveStackMarker(saveStackMarker: AbstractInsnNode, savedStackValues: List): SavedStackDescriptor { + val numRestoreStackMarkers = context.restoreStackMarkersForSaveMarker[saveStackMarker].size() + return allocateNewHandle(numRestoreStackMarkers, saveStackMarker, savedStackValues) + } + + private fun allocateNewHandle(numRestoreStackMarkers: Int, saveStackMarker: AbstractInsnNode, savedStackValues: List): SavedStackDescriptor { + val firstUnusedLocalVarIndex = getFirstUnusedLocalVariableIndex() + val savedStackDescriptor = SavedStackDescriptor(savedStackValues, firstUnusedLocalVarIndex) + updateMaxLocals(savedStackDescriptor.firstUnusedLocalVarIndex) + val allocatedHandle = AllocatedHandle(savedStackDescriptor, numRestoreStackMarkers) + allocatedHandles[saveStackMarker] = allocatedHandle + return savedStackDescriptor + } + + fun getSavedStackDescriptorOrNull(restoreStackMarker: AbstractInsnNode): SavedStackDescriptor { + val saveStackMarker = context.saveStackMarkerForRestoreMarker[restoreStackMarker] + return allocatedHandles[saveStackMarker].savedStackDescriptor + } + + private fun getFirstUnusedLocalVariableIndex(): Int = + allocatedHandles.values().fold(initialMaxLocals) { + index, handle -> Math.max(index, handle.savedStackDescriptor.firstUnusedLocalVarIndex) + } + + fun markRestoreStackMarkerEmitted(restoreStackMarker: AbstractInsnNode) { + val saveStackMarker = context.saveStackMarkerForRestoreMarker[restoreStackMarker] + markEmitted(saveStackMarker) + } + + fun allocateVariablesForBeforeInlineMarker(beforeInlineMarker: AbstractInsnNode, savedStackValues: List): SavedStackDescriptor { + return allocateNewHandle(1, beforeInlineMarker, savedStackValues) + } + + fun getBeforeInlineDescriptor(afterInlineMarker: AbstractInsnNode): SavedStackDescriptor { + val beforeInlineMarker = context.openingInlineMethodMarker[afterInlineMarker] + return allocatedHandles[beforeInlineMarker].savedStackDescriptor + } + + fun markAfterInlineMarkerEmitted(afterInlineMarker: AbstractInsnNode) { + val beforeInlineMarker = context.openingInlineMethodMarker[afterInlineMarker] + markEmitted(beforeInlineMarker) + } + + private fun markEmitted(saveStackMarker: AbstractInsnNode) { + val allocatedHandle = allocatedHandles[saveStackMarker] + allocatedHandle.markRestoreNodeEmitted() + if (allocatedHandle.isFullyEmitted()) { + allocatedHandles.remove(saveStackMarker) + } + } + + fun createReturnValueVariable(returnValue: BasicValue): Int { + val returnValueIndex = getFirstUnusedLocalVariableIndex() + updateMaxLocals(returnValueIndex + returnValue.getSize()) + return returnValueIndex + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt new file mode 100644 index 00000000000..01541346128 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt @@ -0,0 +1,153 @@ +/* + * 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.fixStack + +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.parsePseudoInsnOrNull +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame +import org.jetbrains.org.objectweb.asm.tree.analysis.Value + +public inline fun InsnList.forEachPseudoInsn(block: (PseudoInsn, AbstractInsnNode) -> Unit) { + InsnSequence(this).forEach { insn -> + parsePseudoInsnOrNull(insn)?.let { block(it, insn) } + } +} + +public inline fun InsnList.forEachInlineMarker(block: (String, MethodInsnNode) -> Unit) { + InsnSequence(this).forEach { insn -> + if (InlineCodegenUtil.isInlineMarker(insn)) { + val methodInsnNode = insn as MethodInsnNode + block(methodInsnNode.name, methodInsnNode) + } + } +} + +public fun Frame.top(): V? { + val stackSize = getStackSize() + if (stackSize == 0) + return null + else + return getStack(stackSize - 1) +} + +public fun MethodNode.updateMaxLocals(newMaxLocals: Int) { + maxLocals = Math.max(maxLocals, newMaxLocals) +} + +class SavedStackDescriptor( + val savedValues: List, + val firstLocalVarIndex: Int +) { + val savedValuesSize = savedValues.fold(0, { size, value -> size + value.getSize() }) + val firstUnusedLocalVarIndex = firstLocalVarIndex + savedValuesSize + + public override fun toString(): String = + "@$firstLocalVarIndex: [$savedValues]" + + fun isNotEmpty(): Boolean = savedValues.isNotEmpty() +} + + +fun saveStack(methodNode: MethodNode, nodeToReplace: AbstractInsnNode, savedStackDescriptor: SavedStackDescriptor, + restoreImmediately: Boolean) { + with(methodNode.instructions) { + generateStoreInstructions(methodNode, nodeToReplace, savedStackDescriptor) + if (restoreImmediately) { + generateLoadInstructions(methodNode, nodeToReplace, savedStackDescriptor) + } + remove(nodeToReplace) + } +} + +fun restoreStack(methodNode: MethodNode, location: AbstractInsnNode, savedStackDescriptor: SavedStackDescriptor) { + with(methodNode.instructions) { + generateLoadInstructions(methodNode, location, savedStackDescriptor) + remove(location) + } +} + +fun restoreStackWithReturnValue( + methodNode: MethodNode, + nodeToReplace: AbstractInsnNode, + savedStackDescriptor: SavedStackDescriptor, + returnValue: BasicValue, + returnValueLocalVarIndex: Int +) { + with(methodNode.instructions) { + insertBefore(nodeToReplace, VarInsnNode(returnValue.getType().getOpcode(Opcodes.ISTORE), returnValueLocalVarIndex)) + generateLoadInstructions(methodNode, nodeToReplace, savedStackDescriptor) + insertBefore(nodeToReplace, VarInsnNode(returnValue.getType().getOpcode(Opcodes.ILOAD), returnValueLocalVarIndex)) + remove(nodeToReplace) + } +} + +fun generateLoadInstructions(methodNode: MethodNode, location: AbstractInsnNode, savedStackDescriptor: SavedStackDescriptor) { + var localVarIndex = savedStackDescriptor.firstLocalVarIndex + for (value in savedStackDescriptor.savedValues) { + methodNode.instructions.insertBefore(location, + VarInsnNode(value.getType().getOpcode(Opcodes.ILOAD), localVarIndex)) + localVarIndex += value.getSize() + } +} + +fun generateStoreInstructions(methodNode: MethodNode, location: AbstractInsnNode, savedStackDescriptor: SavedStackDescriptor) { + var localVarIndex = savedStackDescriptor.firstUnusedLocalVarIndex + for (value in savedStackDescriptor.savedValues.reverse()) { + localVarIndex -= value.getSize() + methodNode.instructions.insertBefore(location, + VarInsnNode(value.getType().getOpcode(Opcodes.ISTORE), localVarIndex)) + } +} + +fun getPopInstruction(top: BasicValue) = + InsnNode(when (top.getSize()) { + 1 -> Opcodes.POP + 2 -> Opcodes.POP2 + else -> throw AssertionError("Unexpected value type size") + }) + +fun removeAlwaysFalseIfeq(methodNode: MethodNode, node: AbstractInsnNode) { + with (methodNode.instructions) { + remove(node.getNext()) + remove(node) + } +} + +fun replaceAlwaysTrueIfeqWithGoto(methodNode: MethodNode, node: AbstractInsnNode) { + with (methodNode.instructions) { + val next = node.getNext() as JumpInsnNode + insertBefore(node, JumpInsnNode(Opcodes.GOTO, next.label)) + remove(node) + remove(next) + } +} + +fun replaceMarkerWithPops(methodNode: MethodNode, node: AbstractInsnNode, expectedStackSize: Int, frame: Frame) { + with (methodNode.instructions) { + while (frame.getStackSize() > expectedStackSize) { + val top = frame.pop() + insertBefore(node, getPopInstruction(top)) + } + remove(node) + } +} + diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt index d1608dfe2a8..112c259daf4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt @@ -24,78 +24,46 @@ 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 val PSEUDO_INSN_CALL_OWNER: String = "kotlin/jvm/internal/\$PseudoInsn" -public enum class PseudoInsnOpcode(val signature: String = "()V") { +public enum class PseudoInsn(val signature: String = "()V") { FIX_STACK_BEFORE_JUMP(), FAKE_ALWAYS_TRUE_IFEQ("()I"), - FAKE_ALWAYS_FALSE_IFEQ("()I") + FAKE_ALWAYS_FALSE_IFEQ("()I"), + SAVE_STACK_BEFORE_TRY(), + RESTORE_STACK_IN_TRY_CATCH() ; - 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) + iv.invokestatic(PSEUDO_INSN_CALL_OWNER, toString(), signature, false) } + + public fun createInsnNode(): MethodInsnNode = + MethodInsnNode(Opcodes.INVOKESTATIC, PSEUDO_INSN_CALL_OWNER, toString(), signature, false) + + public fun isa(node: AbstractInsnNode): Boolean = + this == parsePseudoInsnOrNull(node) } -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 isPseudoInsn(insn: AbstractInsnNode): Boolean = + insn is MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESTATIC && insn.owner == PSEUDO_INSN_CALL_OWNER - public fun emit(iv: InstructionAdapter) { - iv.invokestatic(PSEUDO_INSN_CALL_OWNER, encodedMethodName, opcode.signature, false) - } -} +public fun parsePseudoInsnOrNull(insn: AbstractInsnNode): PseudoInsn? = + if (isPseudoInsn(insn)) + PseudoInsn.valueOf((insn as MethodInsnNode).name) + else null public fun InstructionAdapter.fixStackAndJump(label: Label) { - PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP.emit(this) + PseudoInsn.FIX_STACK_BEFORE_JUMP.emit(this) this.goTo(label) } public fun InstructionAdapter.fakeAlwaysTrueIfeq(label: Label) { - PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ.emit(this) + PseudoInsn.FAKE_ALWAYS_TRUE_IFEQ.emit(this) this.ifeq(label) } public fun InstructionAdapter.fakeAlwaysFalseIfeq(label: Label) { - PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ.emit(this) + PseudoInsn.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/testData/codegen/box/controlStructures/tryCatchInExpressions/catch.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/catch.kt new file mode 100644 index 00000000000..cb00faf1fe3 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/catch.kt @@ -0,0 +1,2 @@ +fun box(): String = + "O" + try { throw Exception("oops!") } catch (e: Exception) { "K" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/complexChain.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/complexChain.kt new file mode 100644 index 00000000000..36f2e84f29f --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/complexChain.kt @@ -0,0 +1,52 @@ +fun cleanup() {} + +inline fun concat(x: String, y: String): String = x + y + +inline fun throws() { + try { + throw Exception() + } + finally { + cleanup() + } +} + +inline fun first(x: String, y: String): String = x + +fun box(): String = + "" + concat( + try { "" } finally { "0" }, + "" + concat( + first( + try { + try { + "O" + } + finally { + "1" + } + } + catch (e: Exception) { + throw e + } + finally { + cleanup() + }, + "2" + ), + first( + try { + throws() + throw Exception() + "3" + } + catch (e: Exception) { + "K" + } + finally { + cleanup() + }, + "4" + ) + ) + ) diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/differentTypes.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/differentTypes.kt new file mode 100644 index 00000000000..44fa018cce5 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/differentTypes.kt @@ -0,0 +1,8 @@ +fun foo(b: Byte, s: String, i: Int, d: Double, li: Long): String = "$b $s $i $d $li" + +fun box(): String { + val test = foo(1, "abc", 1, 1.0, try { 1L } catch (e: Exception) { 10L }) + if (test != "1 abc 1 1.0 1") return "Failed, test==$test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/expectException.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/expectException.kt new file mode 100644 index 00000000000..63bd4900c12 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/expectException.kt @@ -0,0 +1,35 @@ +public inline fun fails(block: () -> Unit): Throwable? { + var thrown: Throwable? = null + try { + block() + } catch (e: Throwable) { + thrown = e + } + if (thrown == null) + throw Exception("Expected an exception to be thrown") + return thrown +} + +public inline fun throwIt(msg: String) { + throw Exception(msg) +} + +fun box(): String { + fails { + throwIt("oops!") + } + + var x = 0 + try { + fails { + x = 1 + } + } + catch (e: Exception) { + x = 2 + } + + if (x != 2) return "Failed: x==$x" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/finally.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/finally.kt new file mode 100644 index 00000000000..2d4b9f825a7 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/finally.kt @@ -0,0 +1,2 @@ +fun box(): String = + "O" + try { "K" } finally { "hmmm" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryCatch.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryCatch.kt new file mode 100644 index 00000000000..dd3a181348a --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryCatch.kt @@ -0,0 +1,17 @@ +inline fun tryOrElse(f1: () -> T, f2: () -> T): T { + try { + return f1() + } + catch (e: Exception) { + return f2() + } +} + +fun testIt() = "abc" + tryOrElse({ "def" }, { "oops" }) + "ghi" + +fun box(): String { + val test = testIt() + if (test != "abcdefghi") return "Failed, test==$test" + + return "OK" +} diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryExpr.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryExpr.kt new file mode 100644 index 00000000000..8205200452a --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryExpr.kt @@ -0,0 +1,14 @@ +inline fun tryOrElse(f1: () -> T, f2: () -> T): T = + try { f1() } catch (e: Exception) { f2() } + +fun testIt() = + "abc" + + tryOrElse({ try { "def" } catch(e: Exception) { "oops!" } }, { "hmmm..." }) + + "ghi" + +fun box(): String { + val test = testIt() + if (test != "abcdefghi") return "Failed, test==$test" + + return "OK" +} diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryFinally.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryFinally.kt new file mode 100644 index 00000000000..eb488beb689 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryFinally.kt @@ -0,0 +1,22 @@ +inline fun tryAndThen(f1: () -> Unit, f2: () -> Unit, f3: () -> T): T { + try { + f1() + } + catch (e: Exception) { + f2() + } + finally { + return f3() + } +} + +fun testIt() = "abc" + + tryAndThen({}, {}, { "def" }) + + "ghi" + +fun box(): String { + val test = testIt() + if (test != "abcdefghi") return "Failed, test==$test" + + return "OK" +} diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/multipleCatchBlocks.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/multipleCatchBlocks.kt new file mode 100644 index 00000000000..45dd6ead838 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/multipleCatchBlocks.kt @@ -0,0 +1,20 @@ +class Exception1(msg: String): Exception(msg) +class Exception2(msg: String): Exception(msg) +class Exception3(msg: String): Exception(msg) + +fun box(): String = + "O" + try { + throw Exception3("K") + } + catch (e1: Exception1) { + "e1" + } + catch (e2: Exception2) { + "e2" + } + catch (e3: Exception3) { + e3.getMessage() + } + catch (e: Exception) { + "e" + } diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTry.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTry.kt new file mode 100644 index 00000000000..c60bde586a1 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTry.kt @@ -0,0 +1,25 @@ +inline fun test(s: () -> Int): Int = + try { + val i = s() + i + 10 + } + finally { + 0 + } + +fun box() : String { + test { + try { + val p = 1 + return "OK" + } + catch(e: Exception) { + -2 + } + finally { + -3 + } + } + + return "Failed" +} diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner1.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner1.kt new file mode 100644 index 00000000000..bbf5ad07064 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner1.kt @@ -0,0 +1,11 @@ +fun shouldReturnFalse() : Boolean { + try { + return true + } finally { + if (true) + return false + } +} + +fun box(): String = + if (shouldReturnFalse()) "Failed" else "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner2.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner2.kt new file mode 100644 index 00000000000..7c0c3e81c55 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner2.kt @@ -0,0 +1,22 @@ +fun shouldReturn11() : Int { + var x = 0 + while (true) { + try { + if(x < 10) + x++ + else + break + } + finally { + x++ + } + } + return x +} + +fun box(): String { + val test = shouldReturn11() + if (test != 11) return "Failed, test=$test" + + return "OK" +} diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/try.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/try.kt new file mode 100644 index 00000000000..695e324988e --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/try.kt @@ -0,0 +1,2 @@ +fun box(): String = + "O" + try { "K" } catch (e: Exception) { "oops!" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAfterTry.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAfterTry.kt new file mode 100644 index 00000000000..7d6dcc3d2a3 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAfterTry.kt @@ -0,0 +1,4 @@ +fun box(): String = + "" + + try { "O" } catch (e: Exception) { "1" } + + try { throw Exception("oops!") } catch (e: Exception) { "K" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndBreak.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndBreak.kt new file mode 100644 index 00000000000..a1f867b1c3d --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndBreak.kt @@ -0,0 +1,19 @@ +fun idiv(a: Int, b: Int): Int = + if (b == 0) throw Exception("Division by zero") else a / b + +fun foo(): Int { + var sum = 0 + var i = 2 + while (i > -10) { + sum += try { idiv(100, i) } catch (e: Exception) { break } + i-- + } + return sum +} + +fun box(): String { + val test = foo() + if (test != 150) return "Failed, test=$test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndContinue.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndContinue.kt new file mode 100644 index 00000000000..120d463b00c --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndContinue.kt @@ -0,0 +1,17 @@ +fun idiv(a: Int, b: Int): Int = + if (b == 0) throw Exception("Division by zero") else a / b + +fun foo(): Int { + var sum = 0 + for (i in -10 .. 10) { + sum += try { idiv(100, i) } catch (e: Exception) { continue } + } + return sum +} + +fun box(): String { + val test = foo() + if (test != 0) return "Failed, test=$test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideCatch.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideCatch.kt new file mode 100644 index 00000000000..09f9857b57c --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideCatch.kt @@ -0,0 +1,8 @@ +fun box(): String = + "O" + + try { + throw Exception("oops!") + } + catch (e: Exception) { + try { "K" } catch (e: Exception) { "2" } + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideTry.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideTry.kt new file mode 100644 index 00000000000..3d1ccd9e7ae --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideTry.kt @@ -0,0 +1,10 @@ +class MyException(message: String): Exception(message) + +fun box(): String = + "O" + + try { + try { throw Exception("oops!") } catch (mye: MyException) { "1" } + } + catch (e: Exception) { + "K" + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/unmatchedInlineMarkers.kt b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/unmatchedInlineMarkers.kt new file mode 100644 index 00000000000..015b4137f65 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/unmatchedInlineMarkers.kt @@ -0,0 +1,17 @@ +inline fun catchAll(x: String, block: () -> Unit): String { + try { + block() + } catch (e: Throwable) { + } + return x +} + +inline fun throwIt(msg: String) { + throw Exception(msg) +} + +inline fun bar(x: String): String = + x + catchAll("") { throwIt("oops!") } + +fun box(): String = + bar("OK") diff --git a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt index 0b68ac9f93b..255dbcccba6 100644 --- a/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt +++ b/compiler/testData/codegen/bytecodeText/storeStackBeforeInline/unreachableMarker.kt @@ -16,6 +16,6 @@ fun foo() : String { ) } -// 12 ALOAD -// 0 ASTORE +// 14 ALOAD +// 2 ASTORE // 0 InlineMarker diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java index 42a6b8713fe..389b1c2b915 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java @@ -50,7 +50,7 @@ public abstract class AbstractBytecodeTextTest extends CodegenTestCase { } try { - assertEquals(expected.toString(), actual.toString()); + assertEquals(text, expected.toString(), actual.toString()); } catch (Throwable e) { System.out.println(text); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java index f3eca85f790..de6bfde4436 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -246,7 +246,7 @@ public abstract class CodegenTestCase extends UsefulTestCase { analyzer.analyze(classNode.name, method); } catch (Throwable e) { - System.out.println(file.asText()); + System.err.println(file.asText()); System.err.println(classNode.name + "::" + method.name + method.desc); //noinspection InstanceofCatchParameter diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java index 357a6e933b2..fe9d48e3f7d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -2289,6 +2289,129 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TryCatchInExpressions extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInTryCatchInExpressions() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("catch.kt") + public void testCatch() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/catch.kt"); + doTest(fileName); + } + + @TestMetadata("complexChain.kt") + public void testComplexChain() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/complexChain.kt"); + doTest(fileName); + } + + @TestMetadata("differentTypes.kt") + public void testDifferentTypes() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/differentTypes.kt"); + doTest(fileName); + } + + @TestMetadata("expectException.kt") + public void testExpectException() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/expectException.kt"); + doTest(fileName); + } + + @TestMetadata("finally.kt") + public void testFinally() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/finally.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTryCatch.kt") + public void testInlineTryCatch() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryCatch.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTryExpr.kt") + public void testInlineTryExpr() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryExpr.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTryFinally.kt") + public void testInlineTryFinally() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/inlineTryFinally.kt"); + doTest(fileName); + } + + @TestMetadata("multipleCatchBlocks.kt") + public void testMultipleCatchBlocks() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/multipleCatchBlocks.kt"); + doTest(fileName); + } + + @TestMetadata("splitTry.kt") + public void testSplitTry() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTry.kt"); + doTest(fileName); + } + + @TestMetadata("splitTryCorner1.kt") + public void testSplitTryCorner1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner1.kt"); + doTest(fileName); + } + + @TestMetadata("splitTryCorner2.kt") + public void testSplitTryCorner2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/splitTryCorner2.kt"); + doTest(fileName); + } + + @TestMetadata("try.kt") + public void testTry() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/try.kt"); + doTest(fileName); + } + + @TestMetadata("tryAfterTry.kt") + public void testTryAfterTry() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAfterTry.kt"); + doTest(fileName); + } + + @TestMetadata("tryAndBreak.kt") + public void testTryAndBreak() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndBreak.kt"); + doTest(fileName); + } + + @TestMetadata("tryAndContinue.kt") + public void testTryAndContinue() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryAndContinue.kt"); + doTest(fileName); + } + + @TestMetadata("tryInsideCatch.kt") + public void testTryInsideCatch() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideCatch.kt"); + doTest(fileName); + } + + @TestMetadata("tryInsideTry.kt") + public void testTryInsideTry() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/tryInsideTry.kt"); + doTest(fileName); + } + + @TestMetadata("unmatchedInlineMarkers.kt") + public void testUnmatchedInlineMarkers() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions/unmatchedInlineMarkers.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/deadCodeElimination")