KT-15112, KT-15631
Revert changes for "tolerant to uninitialized values" in OptimizationBasicInterpreter: this approach doesn't converge for some specific cases where local variable is reused (e.g., in several inlined functions - see https://youtrack.jetbrains.com/issue/KT-15112). Instead, treat fake always-false conditional jump in the beginning of the post-condition loop as a "reference point" for stack on loop break / continue. This requires an extra abstraction layer in FixStackAnalyzer, since we can't perform fine-grain manipulations on Frames (such as "combine frame C from local variables of frame A and stack of frame B"). NB additional NOPs will be generated for post-condition loops. Should make a separate bytecode postprocessing pass to get rid of unnecessary NOPs (several YT issues for perceived quality of the generated bytecode are about such NOPs).
This commit is contained in:
@@ -587,6 +587,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
blockStackElements.push(new LoopBlockStackElement(breakLabel, continueLabel, targetLabel(expression)));
|
blockStackElements.push(new LoopBlockStackElement(breakLabel, continueLabel, targetLabel(expression)));
|
||||||
|
|
||||||
PseudoInsnsKt.fakeAlwaysFalseIfeq(v, continueLabel);
|
PseudoInsnsKt.fakeAlwaysFalseIfeq(v, continueLabel);
|
||||||
|
PseudoInsnsKt.fakeAlwaysFalseIfeq(v, breakLabel);
|
||||||
|
|
||||||
KtExpression body = expression.getBody();
|
KtExpression body = expression.getBody();
|
||||||
KtExpression condition = expression.getCondition();
|
KtExpression condition = expression.getCondition();
|
||||||
|
|||||||
+2
-13
@@ -32,15 +32,8 @@ import java.util.List;
|
|||||||
import static org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue.*;
|
import static org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue.*;
|
||||||
|
|
||||||
public class OptimizationBasicInterpreter extends Interpreter<BasicValue> implements Opcodes {
|
public class OptimizationBasicInterpreter extends Interpreter<BasicValue> implements Opcodes {
|
||||||
private final boolean tolerantToUninitializedValues;
|
|
||||||
|
|
||||||
public OptimizationBasicInterpreter(boolean tolerantToUninitializedValues) {
|
|
||||||
super(ASM5);
|
|
||||||
this.tolerantToUninitializedValues = tolerantToUninitializedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OptimizationBasicInterpreter() {
|
public OptimizationBasicInterpreter() {
|
||||||
this(false);
|
super(ASM5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -362,11 +355,7 @@ public class OptimizationBasicInterpreter extends Interpreter<BasicValue> implem
|
|||||||
) {
|
) {
|
||||||
if (v.equals(w)) return v;
|
if (v.equals(w)) return v;
|
||||||
|
|
||||||
if (tolerantToUninitializedValues) {
|
if (v == StrictBasicValue.UNINITIALIZED_VALUE || w == StrictBasicValue.UNINITIALIZED_VALUE) {
|
||||||
if (v == StrictBasicValue.UNINITIALIZED_VALUE) return w;
|
|
||||||
if (w == StrictBasicValue.UNINITIALIZED_VALUE) return v;
|
|
||||||
}
|
|
||||||
else if (v == StrictBasicValue.UNINITIALIZED_VALUE || w == StrictBasicValue.UNINITIALIZED_VALUE) {
|
|
||||||
return StrictBasicValue.UNINITIALIZED_VALUE;
|
return StrictBasicValue.UNINITIALIZED_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+156
-103
@@ -22,138 +22,191 @@ import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
|
|||||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||||
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
|
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
|
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
|
||||||
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.analysis.Frame
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
|
||||||
|
|
||||||
internal class FixStackAnalyzer(
|
internal class FixStackAnalyzer(
|
||||||
owner: String,
|
owner: String,
|
||||||
methodNode: MethodNode,
|
val method: MethodNode,
|
||||||
val context: FixStackContext
|
val context: FixStackContext
|
||||||
) : MethodAnalyzer<BasicValue>(owner, methodNode, OptimizationBasicInterpreter(/* tolerantToUninitializedValues = */true)) {
|
) {
|
||||||
val savedStacks = hashMapOf<AbstractInsnNode, List<BasicValue>>()
|
companion object {
|
||||||
var maxExtraStackSize = 0; private set
|
// Stack size is always non-negative
|
||||||
|
const val DEAD_CODE_STACK_SIZE = -1
|
||||||
override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean {
|
|
||||||
val insnNode = instructions[insn]
|
|
||||||
return !(insnNode is JumpInsnNode && context.breakContinueGotoNodes.contains(insnNode))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> =
|
private val expectedStackNode = hashMapOf<LabelNode, AbstractInsnNode>()
|
||||||
FixStackFrame(nLocals, nStack)
|
|
||||||
|
|
||||||
private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node)
|
val maxExtraStackSize: Int get() = analyzer.maxExtraStackSize
|
||||||
|
|
||||||
inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
fun getStackToSpill(location: AbstractInsnNode) = analyzer.spilledStacks[location]
|
||||||
val extraStack = Stack<BasicValue>()
|
fun getActualStack(location: AbstractInsnNode) = getFrame(location)?.getStackContent()
|
||||||
|
fun getActualStackSize(location: AbstractInsnNode) = getFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
|
||||||
|
fun getExpectedStackSize(location: AbstractInsnNode) = getExpectedStackFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
|
||||||
|
|
||||||
override fun init(src: Frame<out BasicValue>): Frame<BasicValue> {
|
private fun getExpectedStackFrame(location: AbstractInsnNode) = getFrame(expectedStackNode[location] ?: location)
|
||||||
extraStack.clear()
|
private fun getFrame(location: AbstractInsnNode) = analyzer.getFrame(location) as? InternalAnalyzer.FixStackFrame
|
||||||
extraStack.addAll((src as FixStackFrame).extraStack)
|
|
||||||
return super.init(src)
|
fun analyze() {
|
||||||
|
preprocess()
|
||||||
|
analyzer.analyze()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preprocess() {
|
||||||
|
var current: AbstractInsnNode? = method.instructions.first
|
||||||
|
while (current != null) {
|
||||||
|
val next = current.next
|
||||||
|
if (PseudoInsn.FAKE_ALWAYS_FALSE_IFEQ.isa(current) && next is JumpInsnNode) {
|
||||||
|
val nop = InsnNode(Opcodes.NOP)
|
||||||
|
expectedStackNode[next.label] = nop
|
||||||
|
method.instructions.insert(next, nop)
|
||||||
|
method.instructions.remove(current)
|
||||||
|
method.instructions.remove(next)
|
||||||
|
current = nop.next
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current = current.next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearStack() {
|
context.fakeAlwaysFalseIfeqMarkers.clear()
|
||||||
extraStack.clear()
|
}
|
||||||
super.clearStack()
|
|
||||||
|
private val analyzer = InternalAnalyzer(owner, method, context)
|
||||||
|
|
||||||
|
private class InternalAnalyzer(
|
||||||
|
owner: String,
|
||||||
|
method: MethodNode,
|
||||||
|
val context: FixStackContext
|
||||||
|
) : MethodAnalyzer<BasicValue>(owner, method, OptimizationBasicInterpreter()) {
|
||||||
|
val spilledStacks = hashMapOf<AbstractInsnNode, List<BasicValue>>()
|
||||||
|
var maxExtraStackSize = 0; private set
|
||||||
|
|
||||||
|
override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean {
|
||||||
|
val insnNode = instructions[insn]
|
||||||
|
return !(insnNode is JumpInsnNode && context.breakContinueGotoNodes.contains(insnNode))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun execute(insn: AbstractInsnNode, interpreter: Interpreter<BasicValue>) {
|
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> =
|
||||||
when {
|
FixStackFrame(nLocals, nStack)
|
||||||
PseudoInsn.SAVE_STACK_BEFORE_TRY.isa(insn) ->
|
|
||||||
executeSaveStackBeforeTry(insn)
|
private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node)
|
||||||
PseudoInsn.RESTORE_STACK_IN_TRY_CATCH.isa(insn) ->
|
|
||||||
executeRestoreStackInTryCatch(insn)
|
inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
||||||
InlineCodegenUtil.isBeforeInlineMarker(insn) ->
|
val extraStack = Stack<BasicValue>()
|
||||||
executeBeforeInlineCallMarker(insn)
|
|
||||||
InlineCodegenUtil.isAfterInlineMarker(insn) ->
|
override fun init(src: Frame<out BasicValue>): Frame<BasicValue> {
|
||||||
executeAfterInlineCallMarker(insn)
|
extraStack.clear()
|
||||||
InlineCodegenUtil.isMarkedReturn(insn) -> {
|
extraStack.addAll((src as FixStackFrame).extraStack)
|
||||||
// KT-9644: might throw "Incompatible return type" on non-local return, in fact we don't care.
|
return super.init(src)
|
||||||
if (insn.opcode == Opcodes.RETURN) return
|
}
|
||||||
|
|
||||||
|
override fun clearStack() {
|
||||||
|
extraStack.clear()
|
||||||
|
super.clearStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
InlineCodegenUtil.isMarkedReturn(insn) -> {
|
||||||
|
// KT-9644: might throw "Incompatible return type" on non-local return, in fact we don't care.
|
||||||
|
if (insn.opcode == Opcodes.RETURN) return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.execute(insn, interpreter)
|
||||||
|
}
|
||||||
|
|
||||||
|
val stackSizeWithExtra: Int get() = super.getStackSize() + extraStack.size
|
||||||
|
|
||||||
|
fun getStackContent(): List<BasicValue> {
|
||||||
|
val savedStack = arrayListOf<BasicValue>()
|
||||||
|
IntRange(0, super.getStackSize() - 1).mapTo(savedStack) { super.getStack(it) }
|
||||||
|
savedStack.addAll(extraStack)
|
||||||
|
return savedStack
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun push(value: BasicValue) {
|
||||||
|
if (super.getStackSize() < maxStackSize) {
|
||||||
|
super.push(value)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
extraStack.add(value)
|
||||||
|
maxExtraStackSize = Math.max(maxExtraStackSize, extraStack.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.execute(insn, interpreter)
|
fun pushAll(values: Collection<BasicValue>) {
|
||||||
}
|
values.forEach { push(it) }
|
||||||
|
|
||||||
fun getStackContent(): List<BasicValue> {
|
|
||||||
val savedStack = arrayListOf<BasicValue>()
|
|
||||||
IntRange(0, super.getStackSize() - 1).mapTo(savedStack) { super.getStack(it) }
|
|
||||||
savedStack.addAll(extraStack)
|
|
||||||
return savedStack
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun push(value: BasicValue) {
|
|
||||||
if (super.getStackSize() < maxStackSize) {
|
|
||||||
super.push(value)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
extraStack.add(value)
|
override fun pop(): BasicValue {
|
||||||
maxExtraStackSize = Math.max(maxExtraStackSize, extraStack.size)
|
if (extraStack.isNotEmpty()) {
|
||||||
|
return extraStack.pop()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return super.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStack(i: Int): BasicValue {
|
||||||
|
if (i < super.getMaxStackSize()) {
|
||||||
|
return super.getStack(i)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return extraStack[i - maxStackSize]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pushAll(values: Collection<BasicValue>) {
|
private fun FixStackFrame.executeBeforeInlineCallMarker(insn: AbstractInsnNode) {
|
||||||
values.forEach { push(it) }
|
saveStackAndClear(insn)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pop(): BasicValue {
|
private fun FixStackFrame.saveStackAndClear(insn: AbstractInsnNode) {
|
||||||
if (extraStack.isNotEmpty()) {
|
val savedValues = getStackContent()
|
||||||
return extraStack.pop()
|
spilledStacks[insn] = savedValues
|
||||||
}
|
|
||||||
else {
|
|
||||||
return super.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStack(i: Int): BasicValue {
|
|
||||||
if (i < super.getMaxStackSize()) {
|
|
||||||
return super.getStack(i)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return extraStack[i - maxStackSize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (stackSize > 0) {
|
|
||||||
val returnValue = pop()
|
|
||||||
clearStack()
|
clearStack()
|
||||||
val savedValues = savedStacks[beforeInlineMarker]
|
|
||||||
pushAll(savedValues!!)
|
|
||||||
push(returnValue)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
val savedValues = savedStacks[beforeInlineMarker]
|
private fun FixStackFrame.executeAfterInlineCallMarker(insn: AbstractInsnNode) {
|
||||||
pushAll(savedValues!!)
|
val beforeInlineMarker = context.openingInlineMethodMarker[insn]
|
||||||
|
if (stackSize > 0) {
|
||||||
|
val returnValue = pop()
|
||||||
|
clearStack()
|
||||||
|
val savedValues = spilledStacks[beforeInlineMarker]
|
||||||
|
pushAll(savedValues!!)
|
||||||
|
push(returnValue)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val savedValues = spilledStacks[beforeInlineMarker]
|
||||||
|
pushAll(savedValues!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FixStackFrame.executeRestoreStackInTryCatch(insn: AbstractInsnNode) {
|
||||||
|
val saveNode = context.saveStackMarkerForRestoreMarker[insn]
|
||||||
|
val savedValues = spilledStacks.getOrElse(saveNode!!) {
|
||||||
|
throw AssertionError("${indexOf(insn)}: Restore stack is unavailable for ${indexOf(saveNode)}")
|
||||||
|
}
|
||||||
|
pushAll(savedValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FixStackFrame.executeSaveStackBeforeTry(insn: AbstractInsnNode) {
|
||||||
|
saveStackAndClear(insn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-24
@@ -72,16 +72,18 @@ class FixStackMethodTransformer : MethodTransformer() {
|
|||||||
val gotoIndex = methodNode.instructions.indexOf(gotoNode)
|
val gotoIndex = methodNode.instructions.indexOf(gotoNode)
|
||||||
val labelIndex = methodNode.instructions.indexOf(gotoNode.label)
|
val labelIndex = methodNode.instructions.indexOf(gotoNode.label)
|
||||||
|
|
||||||
val DEAD_CODE = -1 // Stack size is always non-negative
|
val actualStackSize = analyzer.getActualStackSize(gotoNode)
|
||||||
val actualStackSize = analyzer.frames[gotoIndex]?.stackSize ?: DEAD_CODE
|
val expectedStackSize = analyzer.getExpectedStackSize(gotoNode.label)
|
||||||
val expectedStackSize = analyzer.frames[labelIndex]?.stackSize ?: DEAD_CODE
|
|
||||||
|
|
||||||
if (actualStackSize != DEAD_CODE && expectedStackSize != DEAD_CODE) {
|
if (actualStackSize >= 0 && expectedStackSize >= 0) {
|
||||||
assert(expectedStackSize <= actualStackSize) { "Label at $labelIndex, jump at $gotoIndex: stack underflow: $expectedStackSize > $actualStackSize" }
|
assert(expectedStackSize <= actualStackSize) {
|
||||||
val frame = analyzer.frames[gotoIndex]!!
|
"Label at $labelIndex, jump at $gotoIndex: stack underflow: $expectedStackSize > $actualStackSize"
|
||||||
actions.add({ replaceMarkerWithPops(methodNode, gotoNode.previous, expectedStackSize, frame) })
|
}
|
||||||
|
val actualStackContent = analyzer.getActualStack(gotoNode)
|
||||||
|
?: throw AssertionError("Jump at $gotoIndex should be alive")
|
||||||
|
actions.add({ replaceMarkerWithPops(methodNode, gotoNode.previous, expectedStackSize, actualStackContent) })
|
||||||
}
|
}
|
||||||
else if (actualStackSize != DEAD_CODE && expectedStackSize == DEAD_CODE) {
|
else if (actualStackSize >= 0 && expectedStackSize < 0) {
|
||||||
throw AssertionError("Live jump $gotoIndex to dead label $labelIndex")
|
throw AssertionError("Live jump $gotoIndex to dead label $labelIndex")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -120,7 +122,7 @@ class FixStackMethodTransformer : MethodTransformer() {
|
|||||||
marker: AbstractInsnNode,
|
marker: AbstractInsnNode,
|
||||||
localVariablesManager: LocalVariablesManager
|
localVariablesManager: LocalVariablesManager
|
||||||
) {
|
) {
|
||||||
val savedStackValues = analyzer.savedStacks[marker]
|
val savedStackValues = analyzer.getStackToSpill(marker)
|
||||||
if (savedStackValues != null) {
|
if (savedStackValues != null) {
|
||||||
val savedStackDescriptor = localVariablesManager.allocateVariablesForSaveStackMarker(marker, savedStackValues)
|
val savedStackDescriptor = localVariablesManager.allocateVariablesForSaveStackMarker(marker, savedStackValues)
|
||||||
actions.add({ saveStack(methodNode, marker, savedStackDescriptor, false) })
|
actions.add({ saveStack(methodNode, marker, savedStackDescriptor, false) })
|
||||||
@@ -151,20 +153,21 @@ class FixStackMethodTransformer : MethodTransformer() {
|
|||||||
localVariablesManager: LocalVariablesManager
|
localVariablesManager: LocalVariablesManager
|
||||||
) {
|
) {
|
||||||
val savedStackDescriptor = localVariablesManager.getBeforeInlineDescriptor(inlineMarker)
|
val savedStackDescriptor = localVariablesManager.getBeforeInlineDescriptor(inlineMarker)
|
||||||
val afterInlineFrame = analyzer.getFrame(inlineMarker) as FixStackAnalyzer.FixStackFrame?
|
val stackContentAfterInline = analyzer.getActualStack(inlineMarker)
|
||||||
if (afterInlineFrame != null && savedStackDescriptor.isNotEmpty()) {
|
if (stackContentAfterInline != null && savedStackDescriptor.isNotEmpty()) {
|
||||||
assert(afterInlineFrame.stackSize <= 1) { "Inline method should not leave more than 1 value on stack" }
|
when (stackContentAfterInline.size) {
|
||||||
if (afterInlineFrame.stackSize == 1) {
|
1 -> {
|
||||||
val afterInlineStackValues = afterInlineFrame.getStackContent()
|
val returnValue = stackContentAfterInline.last()
|
||||||
val returnValue = afterInlineStackValues.last()
|
val returnValueLocalVarIndex = localVariablesManager.createReturnValueVariable(returnValue)
|
||||||
val returnValueLocalVarIndex = localVariablesManager.createReturnValueVariable(returnValue)
|
actions.add({
|
||||||
actions.add({
|
restoreStackWithReturnValue(methodNode, inlineMarker, savedStackDescriptor,
|
||||||
restoreStackWithReturnValue(methodNode, inlineMarker, savedStackDescriptor,
|
returnValue, returnValueLocalVarIndex)
|
||||||
returnValue, returnValueLocalVarIndex)
|
})
|
||||||
})
|
}
|
||||||
}
|
0 ->
|
||||||
else {
|
actions.add({ restoreStack(methodNode, inlineMarker, savedStackDescriptor) })
|
||||||
actions.add({ restoreStack(methodNode, inlineMarker, savedStackDescriptor) })
|
else ->
|
||||||
|
throw AssertionError("Inline method should not leave more than 1 value on stack")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -181,7 +184,7 @@ class FixStackMethodTransformer : MethodTransformer() {
|
|||||||
inlineMarker: AbstractInsnNode,
|
inlineMarker: AbstractInsnNode,
|
||||||
localVariablesManager: LocalVariablesManager
|
localVariablesManager: LocalVariablesManager
|
||||||
) {
|
) {
|
||||||
val savedStackValues = analyzer.savedStacks[inlineMarker]
|
val savedStackValues = analyzer.getStackToSpill(inlineMarker)
|
||||||
if (savedStackValues != null) {
|
if (savedStackValues != null) {
|
||||||
val savedStackDescriptor = localVariablesManager.allocateVariablesForBeforeInlineMarker(inlineMarker, savedStackValues)
|
val savedStackDescriptor = localVariablesManager.allocateVariablesForBeforeInlineMarker(inlineMarker, savedStackValues)
|
||||||
actions.add({ saveStack(methodNode, inlineMarker, savedStackDescriptor, false) })
|
actions.add({ saveStack(methodNode, inlineMarker, savedStackDescriptor, false) })
|
||||||
|
|||||||
+3
-4
@@ -115,11 +115,10 @@ fun replaceAlwaysTrueIfeqWithGoto(methodNode: MethodNode, node: AbstractInsnNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replaceMarkerWithPops(methodNode: MethodNode, node: AbstractInsnNode, expectedStackSize: Int, frame: Frame<BasicValue>) {
|
fun replaceMarkerWithPops(methodNode: MethodNode, node: AbstractInsnNode, expectedStackSize: Int, stackContent: List<BasicValue>) {
|
||||||
with (methodNode.instructions) {
|
with (methodNode.instructions) {
|
||||||
while (frame.stackSize > expectedStackSize) {
|
for (stackValue in stackContent.subList(expectedStackSize, stackContent.size)) {
|
||||||
val top = frame.pop()
|
insert(node, getPopInstruction(stackValue))
|
||||||
insertBefore(node, getPopInstruction(top))
|
|
||||||
}
|
}
|
||||||
remove(node)
|
remove(node)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
fun box(): String {
|
||||||
|
var flag = false
|
||||||
|
do {
|
||||||
|
if (flag) break
|
||||||
|
continue
|
||||||
|
} while (false)
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// FULL_JDK
|
||||||
|
// WITH_RUNTIME
|
||||||
|
// IGNORE_BACKEND: JS
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import kotlin.concurrent.read
|
||||||
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
|
private val evalStateLock = ReentrantReadWriteLock()
|
||||||
|
private val classLoaderLock = ReentrantReadWriteLock()
|
||||||
|
val compiledClasses = arrayListOf("")
|
||||||
|
|
||||||
|
fun box(): String = evalStateLock.write {
|
||||||
|
classLoaderLock.read {
|
||||||
|
classLoaderLock.write {
|
||||||
|
"write"
|
||||||
|
}
|
||||||
|
|
||||||
|
compiledClasses.forEach {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
classLoaderLock.read {
|
||||||
|
compiledClasses.map { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
"OK"
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
@kotlin.Metadata
|
||||||
|
public final class PathologicalDoWhileKt {
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@kotlin.Metadata
|
||||||
|
public final class Kt15112Kt {
|
||||||
|
private final static field classLoaderLock: java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
private final static @org.jetbrains.annotations.NotNull field compiledClasses: java.util.ArrayList
|
||||||
|
private final static field evalStateLock: java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method getCompiledClasses(): java.util.ArrayList
|
||||||
|
}
|
||||||
@@ -10,4 +10,4 @@ fun testSome(): Boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 +3 4 2 7 10
|
// 2 3 4 2 7 10
|
||||||
+12
@@ -4345,6 +4345,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("pathologicalDoWhile.kt")
|
||||||
|
public void testPathologicalDoWhile() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/pathologicalDoWhile.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("popSizes.kt")
|
@TestMetadata("popSizes.kt")
|
||||||
public void testPopSizes() throws Exception {
|
public void testPopSizes() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
||||||
@@ -7465,6 +7471,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15112.kt")
|
||||||
|
public void testKt15112() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt15112.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt1770.kt")
|
@TestMetadata("kt1770.kt")
|
||||||
public void testKt1770() throws Exception {
|
public void testKt1770() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||||
|
|||||||
@@ -4345,6 +4345,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("pathologicalDoWhile.kt")
|
||||||
|
public void testPathologicalDoWhile() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/pathologicalDoWhile.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("popSizes.kt")
|
@TestMetadata("popSizes.kt")
|
||||||
public void testPopSizes() throws Exception {
|
public void testPopSizes() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
||||||
@@ -7465,6 +7471,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15112.kt")
|
||||||
|
public void testKt15112() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt15112.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt1770.kt")
|
@TestMetadata("kt1770.kt")
|
||||||
public void testKt1770() throws Exception {
|
public void testKt1770() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||||
|
|||||||
+12
@@ -4345,6 +4345,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("pathologicalDoWhile.kt")
|
||||||
|
public void testPathologicalDoWhile() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/pathologicalDoWhile.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("popSizes.kt")
|
@TestMetadata("popSizes.kt")
|
||||||
public void testPopSizes() throws Exception {
|
public void testPopSizes() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
||||||
@@ -7465,6 +7471,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15112.kt")
|
||||||
|
public void testKt15112() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt15112.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt1770.kt")
|
@TestMetadata("kt1770.kt")
|
||||||
public void testKt1770() throws Exception {
|
public void testKt1770() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ stepOverWhileWithInline.kt:13
|
|||||||
stepOverWhileWithInline.kt:14
|
stepOverWhileWithInline.kt:14
|
||||||
stepOverWhileWithInline.kt:15
|
stepOverWhileWithInline.kt:15
|
||||||
stepOverWhileWithInline.kt:13
|
stepOverWhileWithInline.kt:13
|
||||||
|
stepOverWhileWithInline.kt:18
|
||||||
stepOverWhileWithInline.kt:19
|
stepOverWhileWithInline.kt:19
|
||||||
stepOverWhileWithInline.kt:20
|
stepOverWhileWithInline.kt:20
|
||||||
stepOverWhileWithInline.kt:21
|
stepOverWhileWithInline.kt:21
|
||||||
|
stepOverWhileWithInline.kt:23
|
||||||
stepOverWhileWithInline.kt:24
|
stepOverWhileWithInline.kt:24
|
||||||
stepOverWhileWithInline.kt:25
|
stepOverWhileWithInline.kt:25
|
||||||
stepOverWhileWithInline.kt:26
|
stepOverWhileWithInline.kt:26
|
||||||
stepOverWhileWithInline.kt:27
|
|
||||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
Process finished with exit code 0
|
Process finished with exit code 0
|
||||||
|
|||||||
+12
@@ -5084,6 +5084,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("pathologicalDoWhile.kt")
|
||||||
|
public void testPathologicalDoWhile() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/pathologicalDoWhile.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("popSizes.kt")
|
@TestMetadata("popSizes.kt")
|
||||||
public void testPopSizes() throws Exception {
|
public void testPopSizes() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/breakContinueInExpressions/popSizes.kt");
|
||||||
@@ -8432,6 +8438,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/fullJdk/regressions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15112.kt")
|
||||||
|
public void testKt15112() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt15112.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt1770.kt")
|
@TestMetadata("kt1770.kt")
|
||||||
public void testKt1770() throws Exception {
|
public void testKt1770() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user