Save/restore stack in try-expressions (and statements).
Parse TryCatchBlockNode's in generated bytecode, infer stack save/restore points. Save stack to local variables before 'try'. Restore stack after the beginning of try-block, catch-block, and default handler. Integrate before/after inline markers rewriting (otherwise it'll break our stacks). #KT-3309 Fixed
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
package org.jetbrains.kotlin.codegen
|
package org.jetbrains.kotlin.codegen
|
||||||
|
|
||||||
import com.intellij.psi.tree.IElementType
|
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.fakeAlwaysFalseIfeq
|
||||||
import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysTrueIfeq
|
import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysTrueIfeq
|
||||||
import org.jetbrains.kotlin.lexer.JetTokens
|
import org.jetbrains.kotlin.lexer.JetTokens
|
||||||
|
|||||||
@@ -2045,7 +2045,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
ClassDescriptor scriptClass = bindingContext.get(CLASS_FOR_SCRIPT, scriptDescriptor);
|
ClassDescriptor scriptClass = bindingContext.get(CLASS_FOR_SCRIPT, scriptDescriptor);
|
||||||
StackValue script = StackValue.thisOrOuter(this, scriptClass, false, false);
|
StackValue script = StackValue.thisOrOuter(this, scriptClass, false, false);
|
||||||
Type fieldType = typeMapper.mapType(valueParameterDescriptor);
|
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);
|
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<InstructionAdapter, Unit>() {
|
return StackValue.operation(expectedAsmType, new Function1<InstructionAdapter, Unit>() {
|
||||||
@Override
|
@Override
|
||||||
public Unit invoke(InstructionAdapter v) {
|
public Unit invoke(InstructionAdapter v) {
|
||||||
|
JetFinallySection finallyBlock = expression.getFinallyBlock();
|
||||||
JetFinallySection finallyBlock = expression.getFinallyBlock();
|
|
||||||
FinallyBlockStackElement finallyBlockStackElement = null;
|
FinallyBlockStackElement finallyBlockStackElement = null;
|
||||||
if (finallyBlock != null) {
|
if (finallyBlock != null) {
|
||||||
finallyBlockStackElement = new FinallyBlockStackElement(expression);
|
finallyBlockStackElement = new FinallyBlockStackElement(expression);
|
||||||
blockStackElements.push(finallyBlockStackElement);
|
blockStackElements.push(finallyBlockStackElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//PseudoInsnsPackage.saveStackBeforeTryExpr(v);
|
||||||
|
|
||||||
Label tryStart = new Label();
|
Label tryStart = new Label();
|
||||||
v.mark(tryStart);
|
v.mark(tryStart);
|
||||||
@@ -3667,6 +3668,7 @@ The "returned" value of try expression with no finally is either the last expres
|
|||||||
v.mark(defaultCatchStart);
|
v.mark(defaultCatchStart);
|
||||||
int savedException = myFrameMap.enterTemp(JAVA_THROWABLE_TYPE);
|
int savedException = myFrameMap.enterTemp(JAVA_THROWABLE_TYPE);
|
||||||
v.store(savedException, JAVA_THROWABLE_TYPE);
|
v.store(savedException, JAVA_THROWABLE_TYPE);
|
||||||
|
|
||||||
Label defaultCatchEnd = new Label();
|
Label defaultCatchEnd = new Label();
|
||||||
v.mark(defaultCatchEnd);
|
v.mark(defaultCatchEnd);
|
||||||
|
|
||||||
|
|||||||
@@ -471,6 +471,32 @@ public class InlineCodegenUtil {
|
|||||||
"()V", false);
|
"()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) {
|
public static int getLoadStoreArgSize(int opcode) {
|
||||||
return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
|
return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.kotlin.codegen.ClosureCodegen;
|
import org.jetbrains.kotlin.codegen.ClosureCodegen;
|
||||||
import org.jetbrains.kotlin.codegen.StackValue;
|
import org.jetbrains.kotlin.codegen.StackValue;
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
|
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.codegen.state.JetTypeMapper;
|
||||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass;
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass;
|
||||||
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache;
|
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache;
|
||||||
@@ -373,6 +374,8 @@ public class MethodInliner {
|
|||||||
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
|
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
|
||||||
node = prepareNode(node, finallyDeepShift);
|
node = prepareNode(node, finallyDeepShift);
|
||||||
|
|
||||||
|
MandatoryMethodTransformer.INSTANCE$.transform("fake", node);
|
||||||
|
|
||||||
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
-156
@@ -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<JumpInsnNode>()
|
|
||||||
val fakesToGotos = arrayListOf<AbstractInsnNode>()
|
|
||||||
val fakesToRemove = arrayListOf<AbstractInsnNode>()
|
|
||||||
|
|
||||||
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<BasicValue>) {
|
|
||||||
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<JumpInsnNode>
|
|
||||||
) : MethodAnalyzer<BasicValue>(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+155
@@ -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<LabelNode, LabelNode>()
|
||||||
|
val removedLabelNodes = hashSetOf<LabelNode>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
+28
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-9
@@ -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.RedundantBoxingMethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantNullCheckMethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantNullCheckMethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.CommonPackage;
|
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.kotlin.codegen.optimization.transformer.MethodTransformer;
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||||
@@ -36,16 +37,11 @@ import java.util.List;
|
|||||||
public class OptimizationMethodVisitor extends MethodVisitor {
|
public class OptimizationMethodVisitor extends MethodVisitor {
|
||||||
private static final int MEMORY_LIMIT_BY_METHOD_MB = 50;
|
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[] {
|
private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] {
|
||||||
new RedundantNullCheckMethodTransformer(),
|
new RedundantNullCheckMethodTransformer(),
|
||||||
new RedundantBoxingMethodTransformer(),
|
new RedundantBoxingMethodTransformer(),
|
||||||
new DeadCodeEliminationMethodTransformer(),
|
new DeadCodeEliminationMethodTransformer(),
|
||||||
new RedundantGotoMethodTransformer(),
|
new RedundantGotoMethodTransformer()
|
||||||
new StoreStackBeforeInlineMethodTransformer()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private final MethodNode methodNode;
|
private final MethodNode methodNode;
|
||||||
@@ -79,9 +75,7 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
super.visitEnd();
|
super.visitEnd();
|
||||||
|
|
||||||
if (shouldBeTransformed(methodNode)) {
|
if (shouldBeTransformed(methodNode)) {
|
||||||
for (MethodTransformer transformer : MANDATORY_TRANSFORMERS) {
|
MandatoryMethodTransformer.INSTANCE$.transform("fake", methodNode);
|
||||||
transformer.transform("fake", methodNode);
|
|
||||||
}
|
|
||||||
if (canBeOptimized(methodNode) && !disableOptimization) {
|
if (canBeOptimized(methodNode) && !disableOptimization) {
|
||||||
for (MethodTransformer transformer : OPTIMIZATION_TRANSFORMERS) {
|
for (MethodTransformer transformer : OPTIMIZATION_TRANSFORMERS) {
|
||||||
transformer.transform("fake", methodNode);
|
transformer.transform("fake", methodNode);
|
||||||
|
|||||||
-220
@@ -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<Frame<BasicValue>?>): 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<Frame<BasicValue>?>) {
|
|
||||||
val insns = methodNode.instructions.toArray()
|
|
||||||
|
|
||||||
val storedValuesDescriptorsStack = Stack<StoredStackValuesDescriptor>()
|
|
||||||
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<BasicValue>,
|
|
||||||
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<BasicValue>,
|
|
||||||
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<BasicValue>,
|
|
||||||
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 <V : BasicValue> Frame<V>.getStackValuesStartingFrom(from: Int): List<V> =
|
|
||||||
IntRange(from, getStackSize() - 1).map { getStack(it) }.requireNoNulls()
|
|
||||||
|
|
||||||
private fun MethodNode.updateMaxLocals(newMaxLocals: Int) {
|
|
||||||
maxLocals = Math.max(maxLocals, newMaxLocals)
|
|
||||||
}
|
|
||||||
+14
-8
@@ -44,7 +44,11 @@ public open class MethodAnalyzer<V : Value>(
|
|||||||
|
|
||||||
protected open fun newFrame(nLocals: Int, nStack: Int): Frame<V> = Frame(nLocals, nStack)
|
protected open fun newFrame(nLocals: Int, nStack: Int): Frame<V> = Frame(nLocals, nStack)
|
||||||
|
|
||||||
protected open fun newFrame(src: Frame<out V>): Frame<V> = Frame(src)
|
protected open fun newFrame(src: Frame<out V>): Frame<V> {
|
||||||
|
val frame = newFrame(src.getLocals(), src.getMaxStackSize())
|
||||||
|
frame.init(src)
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun visitControlFlowEdge(insn: Int, successor: Int): Boolean = true
|
protected open fun visitControlFlowEdge(insn: Int, successor: Int): Boolean = true
|
||||||
|
|
||||||
@@ -94,12 +98,12 @@ public open class MethodAnalyzer<V : Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlers[insn]?.forEach { tcb ->
|
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)
|
val jump = instructions.indexOf(tcb.handler)
|
||||||
if (visitControlFlowExceptionEdge(insn, tcb)) {
|
if (visitControlFlowExceptionEdge(insn, tcb)) {
|
||||||
handler.init(f)
|
handler.init(f)
|
||||||
handler.clearStack()
|
handler.clearStack()
|
||||||
handler.push(interpreter.newValue(type))
|
handler.push(interpreter.newValue(exnType))
|
||||||
mergeControlFlowEdge(jump, handler)
|
mergeControlFlowEdge(jump, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +121,9 @@ public open class MethodAnalyzer<V : Value>(
|
|||||||
return frames
|
return frames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun getFrame(insn: AbstractInsnNode): Frame<V>? =
|
||||||
|
frames[instructions.indexOf(insn)]
|
||||||
|
|
||||||
private fun checkAssertions() {
|
private fun checkAssertions() {
|
||||||
if (instructions.toArray() any { it.getOpcode() == Opcodes.JSR || it.getOpcode() == Opcodes.RET })
|
if (instructions.toArray() any { it.getOpcode() == Opcodes.JSR || it.getOpcode() == Opcodes.RET })
|
||||||
throw AssertionError("Subroutines are deprecated since Java 6")
|
throw AssertionError("Subroutines are deprecated since Java 6")
|
||||||
@@ -170,9 +177,9 @@ public open class MethodAnalyzer<V : Value>(
|
|||||||
val ctype = Type.getObjectType(owner)
|
val ctype = Type.getObjectType(owner)
|
||||||
current.setLocal(local++, interpreter.newValue(ctype))
|
current.setLocal(local++, interpreter.newValue(ctype))
|
||||||
}
|
}
|
||||||
for (i in args.indices) {
|
for (arg in args) {
|
||||||
current.setLocal(local++, interpreter.newValue(args[i]))
|
current.setLocal(local++, interpreter.newValue(arg))
|
||||||
if (args[i].getSize() == 2) {
|
if (arg.getSize() == 2) {
|
||||||
current.setLocal(local++, interpreter.newValue(null))
|
current.setLocal(local++, interpreter.newValue(null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,8 +192,7 @@ public open class MethodAnalyzer<V : Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
||||||
for (i in m.tryCatchBlocks.indices) {
|
for (tcb in m.tryCatchBlocks) {
|
||||||
val tcb = m.tryCatchBlocks.get(i)
|
|
||||||
val begin = instructions.indexOf(tcb.start)
|
val begin = instructions.indexOf(tcb.start)
|
||||||
val end = instructions.indexOf(tcb.end)
|
val end = instructions.indexOf(tcb.end)
|
||||||
for (j in begin..end - 1) {
|
for (j in begin..end - 1) {
|
||||||
|
|||||||
@@ -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.tree.AbstractInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
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.Frame
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
@@ -28,7 +30,9 @@ val AbstractInsnNode.isMeaningful : Boolean get() =
|
|||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequence<AbstractInsnNode> {
|
public class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequence<AbstractInsnNode> {
|
||||||
|
public constructor(insnList: InsnList) : this(insnList.getFirst(), null)
|
||||||
|
|
||||||
override fun iterator(): Iterator<AbstractInsnNode> {
|
override fun iterator(): Iterator<AbstractInsnNode> {
|
||||||
return object : Iterator<AbstractInsnNode> {
|
return object : Iterator<AbstractInsnNode> {
|
||||||
var current: AbstractInsnNode? = from
|
var current: AbstractInsnNode? = from
|
||||||
@@ -77,3 +81,22 @@ abstract class BasicValueWrapper(val wrappedValue: BasicValue?) : BasicValue(wra
|
|||||||
return super.equals(other) && this.javaClass == other?.javaClass
|
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
|
||||||
|
|||||||
+97
@@ -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<LabelNode>()
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LabelNode, DecompiledTryDescriptor>()
|
||||||
|
val decompiledTryDescriptorForHandler = hashMapOf<LabelNode, DecompiledTryDescriptor>()
|
||||||
|
|
||||||
|
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<LabelNode>()
|
||||||
|
val doneHandlerLabels = hashSetOf<LabelNode>()
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+157
@@ -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<BasicValue>(owner, methodNode, OptimizationBasicInterpreter()) {
|
||||||
|
val savedStacks = hashMapOf<AbstractInsnNode, List<BasicValue>>()
|
||||||
|
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<BasicValue> =
|
||||||
|
FixStackFrame(nLocals, nStack)
|
||||||
|
|
||||||
|
private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node)
|
||||||
|
|
||||||
|
public inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
||||||
|
val extraStack = Stack<BasicValue>()
|
||||||
|
|
||||||
|
public override fun init(src: Frame<out BasicValue>): Frame<BasicValue> {
|
||||||
|
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<BasicValue>) {
|
||||||
|
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<BasicValue> {
|
||||||
|
val savedStack = arrayListOf<BasicValue>()
|
||||||
|
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<BasicValue>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+142
@@ -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<JumpInsnNode>()
|
||||||
|
val fakeAlwaysTrueIfeqMarkers = arrayListOf<AbstractInsnNode>()
|
||||||
|
val fakeAlwaysFalseIfeqMarkers = arrayListOf<AbstractInsnNode>()
|
||||||
|
|
||||||
|
val saveStackNodesForTryStartLabel = hashMapOf<LabelNode, AbstractInsnNode>()
|
||||||
|
val saveStackMarkerForRestoreMarker = hashMapOf<AbstractInsnNode, AbstractInsnNode>()
|
||||||
|
val restoreStackMarkersForSaveMarker = hashMapOf<AbstractInsnNode, MutableList<AbstractInsnNode>>()
|
||||||
|
|
||||||
|
val openingInlineMethodMarker = hashMapOf<AbstractInsnNode, AbstractInsnNode>()
|
||||||
|
var consistentInlineMarkers: Boolean = true; private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
insertTryCatchBlocksMarkers(methodNode)
|
||||||
|
|
||||||
|
val inlineMarkersStack = Stack<AbstractInsnNode>()
|
||||||
|
|
||||||
|
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<AbstractInsnNode>() }).add(insnNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findMatchingSaveNodes(insnNode: AbstractInsnNode, restoreLabel: LabelNode): List<AbstractInsnNode> {
|
||||||
|
val saveNodes = SmartHashSet<AbstractInsnNode>()
|
||||||
|
methodNode.tryCatchBlocks.forEach { tcb ->
|
||||||
|
if (restoreLabel == tcb.start || restoreLabel == tcb.handler) {
|
||||||
|
saveStackNodesForTryStartLabel[tcb.start]?.let { saveNodes.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SmartList<AbstractInsnNode>(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()
|
||||||
|
|
||||||
|
}
|
||||||
+213
@@ -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) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+97
@@ -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<AbstractInsnNode, AllocatedHandle>()
|
||||||
|
|
||||||
|
private fun updateMaxLocals(newValue: Int) {
|
||||||
|
methodNode.maxLocals = Math.max(methodNode.maxLocals, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allocateVariablesForSaveStackMarker(saveStackMarker: AbstractInsnNode, savedStackValues: List<BasicValue>): SavedStackDescriptor {
|
||||||
|
val numRestoreStackMarkers = context.restoreStackMarkersForSaveMarker[saveStackMarker].size()
|
||||||
|
return allocateNewHandle(numRestoreStackMarkers, saveStackMarker, savedStackValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allocateNewHandle(numRestoreStackMarkers: Int, saveStackMarker: AbstractInsnNode, savedStackValues: List<BasicValue>): 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<BasicValue>): 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
|
||||||
|
}
|
||||||
|
}
|
||||||
+153
@@ -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 <V : Value> Frame<V>.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<BasicValue>,
|
||||||
|
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<BasicValue>) {
|
||||||
|
with (methodNode.instructions) {
|
||||||
|
while (frame.getStackSize() > expectedStackSize) {
|
||||||
|
val top = frame.pop()
|
||||||
|
insertBefore(node, getPopInstruction(top))
|
||||||
|
}
|
||||||
|
remove(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -24,78 +24,46 @@ import org.jetbrains.org.objectweb.asm.tree.InsnList
|
|||||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||||
import kotlin.platform.platformStatic
|
import kotlin.platform.platformStatic
|
||||||
|
|
||||||
public val PSEUDO_INSN_CALL_OWNER: String = "kotlin.jvm.\$PseudoInsn"
|
public val PSEUDO_INSN_CALL_OWNER: String = "kotlin/jvm/internal/\$PseudoInsn"
|
||||||
public val PSEUDO_INSN_PARTS_SEPARATOR: String = ":"
|
|
||||||
|
|
||||||
public enum class PseudoInsnOpcode(val signature: String = "()V") {
|
public enum class PseudoInsn(val signature: String = "()V") {
|
||||||
FIX_STACK_BEFORE_JUMP(),
|
FIX_STACK_BEFORE_JUMP(),
|
||||||
FAKE_ALWAYS_TRUE_IFEQ("()I"),
|
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<String>): 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) {
|
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<String>) {
|
public fun isPseudoInsn(insn: AbstractInsnNode): Boolean =
|
||||||
public val encodedMethodName: String =
|
insn is MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESTATIC && insn.owner == PSEUDO_INSN_CALL_OWNER
|
||||||
if (args.isEmpty())
|
|
||||||
opcode.toString()
|
|
||||||
else
|
|
||||||
opcode.toString() + PSEUDO_INSN_PARTS_SEPARATOR + args.join(PSEUDO_INSN_PARTS_SEPARATOR)
|
|
||||||
|
|
||||||
public fun emit(iv: InstructionAdapter) {
|
public fun parsePseudoInsnOrNull(insn: AbstractInsnNode): PseudoInsn? =
|
||||||
iv.invokestatic(PSEUDO_INSN_CALL_OWNER, encodedMethodName, opcode.signature, false)
|
if (isPseudoInsn(insn))
|
||||||
}
|
PseudoInsn.valueOf((insn as MethodInsnNode).name)
|
||||||
}
|
else null
|
||||||
|
|
||||||
public fun InstructionAdapter.fixStackAndJump(label: Label) {
|
public fun InstructionAdapter.fixStackAndJump(label: Label) {
|
||||||
PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP.emit(this)
|
PseudoInsn.FIX_STACK_BEFORE_JUMP.emit(this)
|
||||||
this.goTo(label)
|
this.goTo(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun InstructionAdapter.fakeAlwaysTrueIfeq(label: Label) {
|
public fun InstructionAdapter.fakeAlwaysTrueIfeq(label: Label) {
|
||||||
PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ.emit(this)
|
PseudoInsn.FAKE_ALWAYS_TRUE_IFEQ.emit(this)
|
||||||
this.ifeq(label)
|
this.ifeq(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun InstructionAdapter.fakeAlwaysFalseIfeq(label: Label) {
|
public fun InstructionAdapter.fakeAlwaysFalseIfeq(label: Label) {
|
||||||
PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ.emit(this)
|
PseudoInsn.FAKE_ALWAYS_FALSE_IFEQ.emit(this)
|
||||||
this.ifeq(label)
|
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<String> =
|
|
||||||
insn.name.splitBy(PSEUDO_INSN_PARTS_SEPARATOR)
|
|
||||||
|
|
||||||
private fun parseParts(parts: List<String>): PseudoInsn? {
|
|
||||||
try {
|
|
||||||
return PseudoInsnOpcode.valueOf(parts[0]).insnOf(parts.subList(1, parts.size()))
|
|
||||||
}
|
|
||||||
catch (e: IllegalArgumentException) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
fun box(): String =
|
||||||
|
"O" + try { throw Exception("oops!") } catch (e: Exception) { "K" }
|
||||||
+52
@@ -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"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
+8
@@ -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"
|
||||||
|
}
|
||||||
+35
@@ -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"
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
fun box(): String =
|
||||||
|
"O" + try { "K" } finally { "hmmm" }
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
inline fun <T> 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"
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
inline fun <T> 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"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
inline fun <T> 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"
|
||||||
|
}
|
||||||
Vendored
+20
@@ -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"
|
||||||
|
}
|
||||||
+25
@@ -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"
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fun shouldReturnFalse() : Boolean {
|
||||||
|
try {
|
||||||
|
return true
|
||||||
|
} finally {
|
||||||
|
if (true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String =
|
||||||
|
if (shouldReturnFalse()) "Failed" else "OK"
|
||||||
+22
@@ -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"
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fun box(): String =
|
||||||
|
"O" + try { "K" } catch (e: Exception) { "oops!" }
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun box(): String =
|
||||||
|
"" +
|
||||||
|
try { "O" } catch (e: Exception) { "1" } +
|
||||||
|
try { throw Exception("oops!") } catch (e: Exception) { "K" }
|
||||||
+19
@@ -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"
|
||||||
|
}
|
||||||
+17
@@ -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"
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
fun box(): String =
|
||||||
|
"O" +
|
||||||
|
try {
|
||||||
|
throw Exception("oops!")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
try { "K" } catch (e: Exception) { "2" }
|
||||||
|
}
|
||||||
+10
@@ -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"
|
||||||
|
}
|
||||||
Vendored
+17
@@ -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")
|
||||||
+2
-2
@@ -16,6 +16,6 @@ fun foo() : String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 ALOAD
|
// 14 ALOAD
|
||||||
// 0 ASTORE
|
// 2 ASTORE
|
||||||
// 0 InlineMarker
|
// 0 InlineMarker
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public abstract class AbstractBytecodeTextTest extends CodegenTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertEquals(expected.toString(), actual.toString());
|
assertEquals(text, expected.toString(), actual.toString());
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
System.out.println(text);
|
System.out.println(text);
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ public abstract class CodegenTestCase extends UsefulTestCase {
|
|||||||
analyzer.analyze(classNode.name, method);
|
analyzer.analyze(classNode.name, method);
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
System.out.println(file.asText());
|
System.err.println(file.asText());
|
||||||
System.err.println(classNode.name + "::" + method.name + method.desc);
|
System.err.println(classNode.name + "::" + method.name + method.desc);
|
||||||
|
|
||||||
//noinspection InstanceofCatchParameter
|
//noinspection InstanceofCatchParameter
|
||||||
|
|||||||
+123
@@ -2289,6 +2289,129 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("compiler/testData/codegen/box/deadCodeElimination")
|
||||||
|
|||||||
Reference in New Issue
Block a user