Support 'break' and 'continue' in expressions
- generate fake jump instructions so that we can always analyze stack depths - fix stack before break and continue by dropping excessive elements (e.g., *a*.foo(*b*, c?:continue)) - Analyzer rewritten in Kotlin, with more flexible control of CFG traversal #Fixed KT-3340 #Fixed KT-4258 #Fixed KT-7941
This commit is contained in:
@@ -17,6 +17,9 @@
|
|||||||
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.fakeAlwaysTrueIfeq
|
||||||
import org.jetbrains.kotlin.lexer.JetTokens
|
import org.jetbrains.kotlin.lexer.JetTokens
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
||||||
@@ -50,6 +53,10 @@ open class BranchedValue(
|
|||||||
v.visitJumpInsn(patchOpcode(if (jumpIfFalse) opcode else negatedOperations[opcode], v), jumpLabel);
|
v.visitJumpInsn(patchOpcode(if (jumpIfFalse) opcode else negatedOperations[opcode], v), jumpLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
condJump(jumpLabel, v, jumpIfFalse)
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
protected open fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
|
||||||
return opcode
|
return opcode
|
||||||
}
|
}
|
||||||
@@ -58,13 +65,21 @@ open class BranchedValue(
|
|||||||
val negatedOperations = hashMapOf<Int, Int>()
|
val negatedOperations = hashMapOf<Int, Int>()
|
||||||
|
|
||||||
val TRUE: BranchedValue = object : BranchedValue(StackValue.Constant(true, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
val TRUE: BranchedValue = object : BranchedValue(StackValue.Constant(true, Type.BOOLEAN_TYPE), null, Type.BOOLEAN_TYPE, IFEQ) {
|
||||||
|
|
||||||
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
if (!jumpIfFalse) {
|
if (!jumpIfFalse) {
|
||||||
v.goTo(jumpLabel)
|
v.goTo(jumpLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
if (!jumpIfFalse) {
|
||||||
|
v.fakeAlwaysTrueIfeq(jumpLabel)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
v.fakeAlwaysFalseIfeq(jumpLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun putSelector(type: Type, v: InstructionAdapter) {
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
v.iconst(1)
|
v.iconst(1)
|
||||||
coerceTo(type, v)
|
coerceTo(type, v)
|
||||||
@@ -78,6 +93,15 @@ open class BranchedValue(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
if (jumpIfFalse) {
|
||||||
|
v.fakeAlwaysTrueIfeq(jumpLabel)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
v.fakeAlwaysFalseIfeq(jumpLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun putSelector(type: Type, v: InstructionAdapter) {
|
override fun putSelector(type: Type, v: InstructionAdapter) {
|
||||||
v.iconst(0)
|
v.iconst(0)
|
||||||
coerceTo(type, v)
|
coerceTo(type, v)
|
||||||
@@ -111,6 +135,10 @@ open class BranchedValue(
|
|||||||
condJump(condition).condJump(label, iv, jumpIfFalse)
|
condJump(condition).condJump(label, iv, jumpIfFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loopJump(condition: StackValue, label: Label, jumpIfFalse: Boolean, iv: InstructionAdapter) {
|
||||||
|
condJump(condition).loopJump(label, iv, jumpIfFalse)
|
||||||
|
}
|
||||||
|
|
||||||
fun condJump(condition: StackValue): CondJump {
|
fun condJump(condition: StackValue): CondJump {
|
||||||
return CondJump(if (condition is BranchedValue) {
|
return CondJump(if (condition is BranchedValue) {
|
||||||
condition
|
condition
|
||||||
@@ -171,6 +199,10 @@ class CondJump(val condition: BranchedValue, op: Int) : BranchedValue(condition,
|
|||||||
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
condition.condJump(jumpLabel, v, jumpIfFalse)
|
condition.condJump(jumpLabel, v, jumpIfFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun loopJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
|
||||||
|
condition.loopJump(jumpLabel, v, jumpIfFalse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberCompare(
|
class NumberCompare(
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.codegen.inline.*;
|
|||||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethod;
|
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethod;
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
|
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.JavaClassProperty;
|
import org.jetbrains.kotlin.codegen.intrinsics.JavaClassProperty;
|
||||||
|
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsnsPackage;
|
||||||
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter;
|
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter;
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
import org.jetbrains.kotlin.codegen.state.GenerationState;
|
||||||
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
|
||||||
@@ -467,7 +468,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
blockStackElements.push(new LoopBlockStackElement(end, condition, targetLabel(expression)));
|
blockStackElements.push(new LoopBlockStackElement(end, condition, targetLabel(expression)));
|
||||||
|
|
||||||
StackValue conditionValue = gen(expression.getCondition());
|
StackValue conditionValue = gen(expression.getCondition());
|
||||||
BranchedValue.Companion.condJump(conditionValue, end, true, v);
|
BranchedValue.Companion.loopJump(conditionValue, end, true, v);
|
||||||
|
|
||||||
generateLoopBody(expression.getBody());
|
generateLoopBody(expression.getBody());
|
||||||
|
|
||||||
@@ -491,6 +492,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
|
|
||||||
blockStackElements.push(new LoopBlockStackElement(breakLabel, continueLabel, targetLabel(expression)));
|
blockStackElements.push(new LoopBlockStackElement(breakLabel, continueLabel, targetLabel(expression)));
|
||||||
|
|
||||||
|
PseudoInsnsPackage.fakeAlwaysFalseIfeq(v, continueLabel);
|
||||||
|
|
||||||
JetExpression body = expression.getBody();
|
JetExpression body = expression.getBody();
|
||||||
JetExpression condition = expression.getCondition();
|
JetExpression condition = expression.getCondition();
|
||||||
StackValue conditionValue;
|
StackValue conditionValue;
|
||||||
@@ -514,7 +517,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
conditionValue = gen(condition);
|
conditionValue = gen(condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
BranchedValue.Companion.condJump(conditionValue, beginLoopLabel, false, v);
|
BranchedValue.Companion.loopJump(conditionValue, beginLoopLabel, false, v);
|
||||||
v.mark(breakLabel);
|
v.mark(breakLabel);
|
||||||
|
|
||||||
blockStackElements.pop();
|
blockStackElements.pop();
|
||||||
@@ -574,6 +577,9 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
v.mark(loopEntry);
|
v.mark(loopEntry);
|
||||||
generator.checkPreCondition(loopExit);
|
generator.checkPreCondition(loopExit);
|
||||||
|
|
||||||
|
// Some forms of for-loop can be optimized as post-condition loops.
|
||||||
|
PseudoInsnsPackage.fakeAlwaysFalseIfeq(v, continueLabel);
|
||||||
|
|
||||||
generator.beforeBody();
|
generator.beforeBody();
|
||||||
blockStackElements.push(new LoopBlockStackElement(loopExit, continueLabel, targetLabel(generator.forExpression)));
|
blockStackElements.push(new LoopBlockStackElement(loopExit, continueLabel, targetLabel(generator.forExpression)));
|
||||||
generator.body();
|
generator.body();
|
||||||
@@ -1231,7 +1237,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
|||||||
if (labelElement == null ||
|
if (labelElement == null ||
|
||||||
loopBlockStackElement.targetLabel != null &&
|
loopBlockStackElement.targetLabel != null &&
|
||||||
labelElement.getReferencedName().equals(loopBlockStackElement.targetLabel.getReferencedName())) {
|
labelElement.getReferencedName().equals(loopBlockStackElement.targetLabel.getReferencedName())) {
|
||||||
v.goTo(isBreak ? loopBlockStackElement.breakLabel : loopBlockStackElement.continueLabel);
|
Label label = isBreak ? loopBlockStackElement.breakLabel : loopBlockStackElement.continueLabel;
|
||||||
|
PseudoInsnsPackage.fixStackAndJump(v, label);
|
||||||
v.mark(afterBreakContinueLabel);
|
v.mark(afterBreakContinueLabel);
|
||||||
return StackValue.none();
|
return StackValue.none();
|
||||||
}
|
}
|
||||||
|
|||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.optimization
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||||
|
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsnOpcode
|
||||||
|
import org.jetbrains.kotlin.codegen.pseudoInsns.parseOrNull
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class FixStackBeforeJumpTransformer : MethodTransformer() {
|
||||||
|
public override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
|
val jumpsToFix = linkedSetOf<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+4
-1
@@ -25,9 +25,11 @@ import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
|||||||
|
|
||||||
public class OptimizationClassBuilder extends DelegatingClassBuilder {
|
public class OptimizationClassBuilder extends DelegatingClassBuilder {
|
||||||
private final ClassBuilder delegate;
|
private final ClassBuilder delegate;
|
||||||
|
private final boolean disableOptimization;
|
||||||
|
|
||||||
public OptimizationClassBuilder(@NotNull ClassBuilder delegate) {
|
public OptimizationClassBuilder(@NotNull ClassBuilder delegate, boolean disableOptimization) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
this.disableOptimization = disableOptimization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -48,6 +50,7 @@ public class OptimizationClassBuilder extends DelegatingClassBuilder {
|
|||||||
) {
|
) {
|
||||||
return new OptimizationMethodVisitor(
|
return new OptimizationMethodVisitor(
|
||||||
super.newMethod(origin, access, name, desc, signature, exceptions),
|
super.newMethod(origin, access, name, desc, signature, exceptions),
|
||||||
|
disableOptimization,
|
||||||
access, name, desc, signature, exceptions
|
access, name, desc, signature, exceptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -24,9 +24,11 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
|
|||||||
|
|
||||||
public class OptimizationClassBuilderFactory implements ClassBuilderFactory {
|
public class OptimizationClassBuilderFactory implements ClassBuilderFactory {
|
||||||
private final ClassBuilderFactory delegate;
|
private final ClassBuilderFactory delegate;
|
||||||
|
private final boolean disableOptimization;
|
||||||
|
|
||||||
public OptimizationClassBuilderFactory(ClassBuilderFactory delegate) {
|
public OptimizationClassBuilderFactory(ClassBuilderFactory delegate, boolean disableOptimization) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
this.disableOptimization = disableOptimization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -38,7 +40,7 @@ public class OptimizationClassBuilderFactory implements ClassBuilderFactory {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public ClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) {
|
public ClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) {
|
||||||
return new OptimizationClassBuilder(delegate.newClassBuilder(origin));
|
return new OptimizationClassBuilder(delegate.newClassBuilder(origin), disableOptimization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+22
-8
@@ -35,7 +35,12 @@ 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[] TRANSFORMERS = new MethodTransformer[]{
|
|
||||||
|
private static final MethodTransformer[] MANDATORY_TRANSFORMERS = new MethodTransformer[] {
|
||||||
|
new FixStackBeforeJumpTransformer()
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] {
|
||||||
new RedundantNullCheckMethodTransformer(),
|
new RedundantNullCheckMethodTransformer(),
|
||||||
new RedundantBoxingMethodTransformer(),
|
new RedundantBoxingMethodTransformer(),
|
||||||
new DeadCodeEliminationMethodTransformer(),
|
new DeadCodeEliminationMethodTransformer(),
|
||||||
@@ -45,9 +50,11 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
|
|
||||||
private final MethodNode methodNode;
|
private final MethodNode methodNode;
|
||||||
private final MethodVisitor delegate;
|
private final MethodVisitor delegate;
|
||||||
|
private final boolean disableOptimization;
|
||||||
|
|
||||||
public OptimizationMethodVisitor(
|
public OptimizationMethodVisitor(
|
||||||
@NotNull MethodVisitor delegate,
|
@NotNull MethodVisitor delegate,
|
||||||
|
boolean disableOptimization,
|
||||||
int access,
|
int access,
|
||||||
@NotNull String name,
|
@NotNull String name,
|
||||||
@NotNull String desc,
|
@NotNull String desc,
|
||||||
@@ -59,6 +66,7 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
this.methodNode = new MethodNode(access, name, desc, signature, exceptions);
|
this.methodNode = new MethodNode(access, name, desc, signature, exceptions);
|
||||||
this.methodNode.localVariables = new ArrayList<LocalVariableNode>(5);
|
this.methodNode.localVariables = new ArrayList<LocalVariableNode>(5);
|
||||||
this.mv = InlineCodegenUtil.wrapWithMaxLocalCalc(methodNode);
|
this.mv = InlineCodegenUtil.wrapWithMaxLocalCalc(methodNode);
|
||||||
|
this.disableOptimization = disableOptimization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,10 +78,15 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
|
|
||||||
super.visitEnd();
|
super.visitEnd();
|
||||||
|
|
||||||
if (canBeAnalyzed(methodNode)) {
|
if (shouldBeTransformed(methodNode)) {
|
||||||
for (MethodTransformer transformer : TRANSFORMERS) {
|
for (MethodTransformer transformer : MANDATORY_TRANSFORMERS) {
|
||||||
transformer.transform("fake", methodNode);
|
transformer.transform("fake", methodNode);
|
||||||
}
|
}
|
||||||
|
if (canBeOptimized(methodNode) && !disableOptimization) {
|
||||||
|
for (MethodTransformer transformer : OPTIMIZATION_TRANSFORMERS) {
|
||||||
|
transformer.transform("fake", methodNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
CommonPackage.prepareForEmitting(methodNode);
|
CommonPackage.prepareForEmitting(methodNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,11 +134,12 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
return traceMethodVisitor;
|
return traceMethodVisitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canBeAnalyzed(@NotNull MethodNode node) {
|
private static boolean shouldBeTransformed(@NotNull MethodNode node) {
|
||||||
int totalFramesSizeMb = node.instructions.size() *
|
return node.instructions.size() > 0;
|
||||||
(node.maxLocals + node.maxStack) / (1024 * 1024);
|
}
|
||||||
|
|
||||||
return node.instructions.size() > 0 &&
|
private static boolean canBeOptimized(@NotNull MethodNode node) {
|
||||||
totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB;
|
int totalFramesSizeMb = node.instructions.size() * (node.maxLocals + node.maxStack) / (1024 * 1024);
|
||||||
|
return totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.optimization.common
|
||||||
|
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis.*
|
||||||
|
import org.jetbrains.org.objectweb.asm.util.Printer
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
public open class MethodAnalyzer<V : Value>(
|
||||||
|
public val owner: String,
|
||||||
|
public val method: MethodNode,
|
||||||
|
protected val interpreter: Interpreter<V>
|
||||||
|
) {
|
||||||
|
public val instructions: InsnList = method.instructions
|
||||||
|
public val nInsns: Int = instructions.size()
|
||||||
|
|
||||||
|
public val frames: Array<Frame<V>?> = arrayOfNulls(nInsns)
|
||||||
|
|
||||||
|
private val handlers: Array<MutableList<TryCatchBlockNode>?> = arrayOfNulls(nInsns)
|
||||||
|
private val queued: BooleanArray = BooleanArray(nInsns)
|
||||||
|
private val queue: IntArray = IntArray(nInsns)
|
||||||
|
private var top: Int = 0
|
||||||
|
|
||||||
|
protected open fun init(owner: String, m: MethodNode) {}
|
||||||
|
|
||||||
|
protected open fun newFrame(nLocals: Int, nStack: Int): Frame<V> = Frame(nLocals, nStack)
|
||||||
|
|
||||||
|
protected open fun newFrame(src: Frame<out V>): Frame<V> = Frame(src)
|
||||||
|
|
||||||
|
protected open fun visitControlFlowEdge(insn: Int, successor: Int): Boolean = true
|
||||||
|
|
||||||
|
protected open fun visitControlFlowExceptionEdge(insn: Int, successor: Int): Boolean = true
|
||||||
|
|
||||||
|
protected open fun visitControlFlowExceptionEdge(insn: Int, tcb: TryCatchBlockNode): Boolean =
|
||||||
|
visitControlFlowExceptionEdge(insn, instructions.indexOf(tcb.handler))
|
||||||
|
|
||||||
|
public fun analyze(): Array<Frame<V>?> {
|
||||||
|
if (nInsns == 0) return frames
|
||||||
|
|
||||||
|
checkAssertions()
|
||||||
|
|
||||||
|
computeExceptionHandlersForEachInsn(method)
|
||||||
|
|
||||||
|
val current = newFrame(method.maxLocals, method.maxStack)
|
||||||
|
val handler = newFrame(method.maxLocals, method.maxStack)
|
||||||
|
initControlFlowAnalysis(current, method, owner)
|
||||||
|
|
||||||
|
while (top > 0) {
|
||||||
|
val insn = queue[--top]
|
||||||
|
val f = frames[insn]!!
|
||||||
|
queued[insn] = false
|
||||||
|
|
||||||
|
val insnNode = method.instructions[insn]
|
||||||
|
try {
|
||||||
|
val insnOpcode = insnNode.getOpcode()
|
||||||
|
val insnType = insnNode.getType()
|
||||||
|
|
||||||
|
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
|
||||||
|
visitNopInsn(f, insn)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current.init(f).execute(insnNode, interpreter)
|
||||||
|
|
||||||
|
when {
|
||||||
|
insnNode is JumpInsnNode ->
|
||||||
|
visitJumpInsnNode(insnNode, current, insn, insnOpcode)
|
||||||
|
insnNode is LookupSwitchInsnNode ->
|
||||||
|
visitLookupSwitchInsnNode(insnNode, current, insn)
|
||||||
|
insnNode is TableSwitchInsnNode ->
|
||||||
|
visitTableSwitchInsnNode(insnNode, current, insn)
|
||||||
|
insnOpcode != Opcodes.ATHROW && (insnOpcode < Opcodes.IRETURN || insnOpcode > Opcodes.RETURN) ->
|
||||||
|
visitOpInsn(current, insn)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers[insn]?.forEach { tcb ->
|
||||||
|
val type = Type.getObjectType(tcb.type?:"java/lang/Throwable")
|
||||||
|
val jump = instructions.indexOf(tcb.handler)
|
||||||
|
if (visitControlFlowExceptionEdge(insn, tcb)) {
|
||||||
|
handler.init(f)
|
||||||
|
handler.clearStack()
|
||||||
|
handler.push(interpreter.newValue(type))
|
||||||
|
mergeControlFlowEdge(jump, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (e: AnalyzerException) {
|
||||||
|
throw AnalyzerException(e.node, "Error at instruction " + insn + ": " + e.getMessage(), e)
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
throw AnalyzerException(insnNode, "Error at instruction " + insn + ": " + e.getMessage(), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAssertions() {
|
||||||
|
if (instructions.toArray() any { it.getOpcode() == Opcodes.JSR || it.getOpcode() == Opcodes.RET })
|
||||||
|
throw AssertionError("Subroutines are deprecated since Java 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitOpInsn(current: Frame<V>, insn: Int) {
|
||||||
|
processControlFlowEdge(current, insn, insn + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitTableSwitchInsnNode(insnNode: TableSwitchInsnNode, current: Frame<V>, insn: Int) {
|
||||||
|
var jump = instructions.indexOf(insnNode.dflt)
|
||||||
|
processControlFlowEdge(current, insn, jump)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
jump = instructions.indexOf(label)
|
||||||
|
processControlFlowEdge(current, insn, jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitLookupSwitchInsnNode(insnNode: LookupSwitchInsnNode, current: Frame<V>, insn: Int) {
|
||||||
|
var jump = instructions.indexOf(insnNode.dflt)
|
||||||
|
processControlFlowEdge(current, insn, jump)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
jump = instructions.indexOf(label)
|
||||||
|
processControlFlowEdge(current, insn, jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame<V>, insn: Int, insnOpcode: Int) {
|
||||||
|
if (insnOpcode != Opcodes.GOTO && insnOpcode != Opcodes.JSR) {
|
||||||
|
processControlFlowEdge(current, insn, insn + 1)
|
||||||
|
}
|
||||||
|
val jump = instructions.indexOf(insnNode.label)
|
||||||
|
processControlFlowEdge(current, insn, jump)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitNopInsn(f: Frame<V>, insn: Int) {
|
||||||
|
processControlFlowEdge(f, insn, insn + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processControlFlowEdge(current: Frame<V>, insn: Int, jump: Int) {
|
||||||
|
if (visitControlFlowEdge(insn, jump)) {
|
||||||
|
mergeControlFlowEdge(jump, current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initControlFlowAnalysis(current: Frame<V>, m: MethodNode, owner: String) {
|
||||||
|
current.setReturn(interpreter.newValue(Type.getReturnType(m.desc)))
|
||||||
|
val args = Type.getArgumentTypes(m.desc)
|
||||||
|
var local = 0
|
||||||
|
if ((m.access and Opcodes.ACC_STATIC) == 0) {
|
||||||
|
val ctype = Type.getObjectType(owner)
|
||||||
|
current.setLocal(local++, interpreter.newValue(ctype))
|
||||||
|
}
|
||||||
|
for (i in args.indices) {
|
||||||
|
current.setLocal(local++, interpreter.newValue(args[i]))
|
||||||
|
if (args[i].getSize() == 2) {
|
||||||
|
current.setLocal(local++, interpreter.newValue(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (local < m.maxLocals) {
|
||||||
|
current.setLocal(local++, interpreter.newValue(null))
|
||||||
|
}
|
||||||
|
mergeControlFlowEdge(0, current)
|
||||||
|
|
||||||
|
init(owner, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
||||||
|
for (i in m.tryCatchBlocks.indices) {
|
||||||
|
val tcb = m.tryCatchBlocks.get(i)
|
||||||
|
val begin = instructions.indexOf(tcb.start)
|
||||||
|
val end = instructions.indexOf(tcb.end)
|
||||||
|
for (j in begin..end - 1) {
|
||||||
|
var insnHandlers: MutableList<TryCatchBlockNode>? = handlers[j]
|
||||||
|
if (insnHandlers == null) {
|
||||||
|
insnHandlers = ArrayList<TryCatchBlockNode>()
|
||||||
|
handlers[j] = insnHandlers
|
||||||
|
}
|
||||||
|
insnHandlers.add(tcb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeControlFlowEdge(insn: Int, frame: Frame<V>) {
|
||||||
|
val oldFrame = frames[insn]
|
||||||
|
var changes: Boolean
|
||||||
|
|
||||||
|
if (oldFrame == null) {
|
||||||
|
frames[insn] = newFrame(frame)
|
||||||
|
changes = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
changes = oldFrame.merge(frame, interpreter)
|
||||||
|
}
|
||||||
|
if (changes && !queued[insn]) {
|
||||||
|
queued[insn] = true
|
||||||
|
queue[top++] = insn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.pseudoInsns
|
||||||
|
|
||||||
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.InsnList
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||||
|
import kotlin.platform.platformStatic
|
||||||
|
|
||||||
|
public val PSEUDO_INSN_CALL_OWNER: String = "kotlin.jvm.\$PseudoInsn"
|
||||||
|
public val PSEUDO_INSN_PARTS_SEPARATOR: String = ":"
|
||||||
|
|
||||||
|
public enum class PseudoInsnOpcode(val signature: String = "()V") {
|
||||||
|
FIX_STACK_BEFORE_JUMP(),
|
||||||
|
FAKE_ALWAYS_TRUE_IFEQ("()I"),
|
||||||
|
FAKE_ALWAYS_FALSE_IFEQ("()I")
|
||||||
|
;
|
||||||
|
|
||||||
|
public fun insnOf(): PseudoInsn = PseudoInsn(this, emptyList())
|
||||||
|
public fun insnOf(args: List<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) {
|
||||||
|
insnOf().emit(iv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PseudoInsn(public val opcode: PseudoInsnOpcode, public val args: List<String>) {
|
||||||
|
public val encodedMethodName: String =
|
||||||
|
if (args.isEmpty())
|
||||||
|
opcode.toString()
|
||||||
|
else
|
||||||
|
opcode.toString() + PSEUDO_INSN_PARTS_SEPARATOR + args.join(PSEUDO_INSN_PARTS_SEPARATOR)
|
||||||
|
|
||||||
|
public fun emit(iv: InstructionAdapter) {
|
||||||
|
iv.invokestatic(PSEUDO_INSN_CALL_OWNER, encodedMethodName, opcode.signature, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun InstructionAdapter.fixStackAndJump(label: Label) {
|
||||||
|
PseudoInsnOpcode.FIX_STACK_BEFORE_JUMP.emit(this)
|
||||||
|
this.goTo(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun InstructionAdapter.fakeAlwaysTrueIfeq(label: Label) {
|
||||||
|
PseudoInsnOpcode.FAKE_ALWAYS_TRUE_IFEQ.emit(this)
|
||||||
|
this.ifeq(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun InstructionAdapter.fakeAlwaysFalseIfeq(label: Label) {
|
||||||
|
PseudoInsnOpcode.FAKE_ALWAYS_FALSE_IFEQ.emit(this)
|
||||||
|
this.ifeq(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun parseOrNull(insn: AbstractInsnNode): PseudoInsn? =
|
||||||
|
if (isPseudoInsn(insn))
|
||||||
|
parseParts(getPseudoInsnParts(insn as MethodInsnNode))
|
||||||
|
else null
|
||||||
|
|
||||||
|
private fun isPseudoInsn(insn: AbstractInsnNode) =
|
||||||
|
insn is MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESTATIC && insn.owner == PSEUDO_INSN_CALL_OWNER
|
||||||
|
|
||||||
|
private fun getPseudoInsnParts(insn: MethodInsnNode): List<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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -149,9 +149,7 @@ public class GenerationState {
|
|||||||
|
|
||||||
this.intrinsics = new IntrinsicMethods();
|
this.intrinsics = new IntrinsicMethods();
|
||||||
|
|
||||||
if (!disableOptimization) {
|
builderFactory = new OptimizationClassBuilderFactory(builderFactory, disableOptimization);
|
||||||
builderFactory = new OptimizationClassBuilderFactory(builderFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassBuilderFactory interceptedBuilderFactory = new BuilderFactoryForDuplicateSignatureDiagnostics(
|
ClassBuilderFactory interceptedBuilderFactory = new BuilderFactoryForDuplicateSignatureDiagnostics(
|
||||||
builderFactory, this.bindingContext, diagnostics);
|
builderFactory, this.bindingContext, diagnostics);
|
||||||
|
|||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
fun box(): String {
|
||||||
|
OUTER@while (true) {
|
||||||
|
var x = ""
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
x = x + break@OUTER
|
||||||
|
} while (true)
|
||||||
|
} finally {
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
fun test(str: String): String {
|
||||||
|
var s = ""
|
||||||
|
for (i in 1..3) {
|
||||||
|
s += if (i<2) str else break
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String = test("OK")
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
fun box(): String {
|
||||||
|
var s = "OK"
|
||||||
|
for (i in 1..3) {
|
||||||
|
s = s + if (i<2) "" else continue
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
inline fun bar(block: () -> String) : String {
|
||||||
|
return block()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun bar2() : String {
|
||||||
|
while (true) break
|
||||||
|
return bar { return "def" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foobar(x: String, y: String, z: String) = x + y + z
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val test = foobar("abc", bar2(), "ghi")
|
||||||
|
return if (test == "abcdefghi")
|
||||||
|
"OK"
|
||||||
|
else "Failed, test=$test"
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
fun box(): String {
|
||||||
|
var x = "OK"
|
||||||
|
do {
|
||||||
|
while (true) {
|
||||||
|
x = x + break
|
||||||
|
}
|
||||||
|
} while (false)
|
||||||
|
return x
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
fun foo(x: Long, y: Int, z: Double, s: String) {}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
foo(0, 0, 0.0, "" + continue)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
foo(0, 0, 0.0, "" + break)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
fun box(): String {
|
||||||
|
var x = "OK"
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
x = x + continue
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
x = x + break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
fun box(): String {
|
||||||
|
var r = ""
|
||||||
|
for (i in 1..1) {
|
||||||
|
try {
|
||||||
|
r += "O"
|
||||||
|
break
|
||||||
|
} finally {
|
||||||
|
r += "K"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun box(): String {
|
||||||
|
while (true) break
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
fun concatNonNulls(strings: List<String?>): String {
|
||||||
|
var result = ""
|
||||||
|
for (str in strings) {
|
||||||
|
result += str?:continue
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val test = concatNonNulls(listOf("abc", null, null, "", null, "def"))
|
||||||
|
if (test != "abcdef") return "Failed: test=$test"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+63
@@ -2196,6 +2196,69 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchFinallyChain.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/tryCatchFinallyChain.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class BreakContinueInExpressions extends AbstractBlackBoxCodegenTest {
|
||||||
|
public void testAllFilesPresentInBreakContinueInExpressions() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("breakFromOuter.kt")
|
||||||
|
public void testBreakFromOuter() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakFromOuter.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("breakInExpr.kt")
|
||||||
|
public void testBreakInExpr() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/breakInExpr.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("continueInExpr.kt")
|
||||||
|
public void testContinueInExpr() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/continueInExpr.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inlineWithStack.kt")
|
||||||
|
public void testInlineWithStack() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/inlineWithStack.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("innerLoopWithStack.kt")
|
||||||
|
public void testInnerLoopWithStack() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/innerLoopWithStack.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("popSizes.kt")
|
||||||
|
public void testPopSizes() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("tryFinally1.kt")
|
||||||
|
public void testTryFinally1() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally1.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("tryFinally2.kt")
|
||||||
|
public void testTryFinally2() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/tryFinally2.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("whileTrueBreak.kt")
|
||||||
|
public void testWhileTrueBreak() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/whileTrueBreak.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/box/deadCodeElimination")
|
@TestMetadata("compiler/testData/codegen/box/deadCodeElimination")
|
||||||
|
|||||||
+15
@@ -917,6 +917,21 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/boxWithStdlib/controlStructures")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class ControlStructures extends AbstractBlackBoxCodegenTest {
|
||||||
|
public void testAllFilesPresentInControlStructures() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/controlStructures"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("continueInExpr.kt")
|
||||||
|
public void testContinueInExpr() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/controlStructures/continueInExpr.kt");
|
||||||
|
doTestWithStdlib(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/boxWithStdlib/dataClasses")
|
@TestMetadata("compiler/testData/codegen/boxWithStdlib/dataClasses")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user