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)));
|
||||
|
||||
PseudoInsnsKt.fakeAlwaysFalseIfeq(v, continueLabel);
|
||||
PseudoInsnsKt.fakeAlwaysFalseIfeq(v, breakLabel);
|
||||
|
||||
KtExpression body = expression.getBody();
|
||||
KtExpression condition = expression.getCondition();
|
||||
|
||||
+2
-13
@@ -32,15 +32,8 @@ import java.util.List;
|
||||
import static org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue.*;
|
||||
|
||||
public class OptimizationBasicInterpreter extends Interpreter<BasicValue> implements Opcodes {
|
||||
private final boolean tolerantToUninitializedValues;
|
||||
|
||||
public OptimizationBasicInterpreter(boolean tolerantToUninitializedValues) {
|
||||
super(ASM5);
|
||||
this.tolerantToUninitializedValues = tolerantToUninitializedValues;
|
||||
}
|
||||
|
||||
public OptimizationBasicInterpreter() {
|
||||
this(false);
|
||||
super(ASM5);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -362,11 +355,7 @@ public class OptimizationBasicInterpreter extends Interpreter<BasicValue> implem
|
||||
) {
|
||||
if (v.equals(w)) return v;
|
||||
|
||||
if (tolerantToUninitializedValues) {
|
||||
if (v == StrictBasicValue.UNINITIALIZED_VALUE) return w;
|
||||
if (w == StrictBasicValue.UNINITIALIZED_VALUE) return v;
|
||||
}
|
||||
else if (v == StrictBasicValue.UNINITIALIZED_VALUE || w == StrictBasicValue.UNINITIALIZED_VALUE) {
|
||||
if (v == StrictBasicValue.UNINITIALIZED_VALUE || w == 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.pseudoInsns.PseudoInsn
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
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.*
|
||||
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
|
||||
|
||||
internal class FixStackAnalyzer(
|
||||
owner: String,
|
||||
methodNode: MethodNode,
|
||||
val method: MethodNode,
|
||||
val context: FixStackContext
|
||||
) : MethodAnalyzer<BasicValue>(owner, methodNode, OptimizationBasicInterpreter(/* tolerantToUninitializedValues = */true)) {
|
||||
val savedStacks = 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))
|
||||
) {
|
||||
companion object {
|
||||
// Stack size is always non-negative
|
||||
const val DEAD_CODE_STACK_SIZE = -1
|
||||
}
|
||||
|
||||
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> =
|
||||
FixStackFrame(nLocals, nStack)
|
||||
private val expectedStackNode = hashMapOf<LabelNode, AbstractInsnNode>()
|
||||
|
||||
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) {
|
||||
val extraStack = Stack<BasicValue>()
|
||||
fun getStackToSpill(location: AbstractInsnNode) = analyzer.spilledStacks[location]
|
||||
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> {
|
||||
extraStack.clear()
|
||||
extraStack.addAll((src as FixStackFrame).extraStack)
|
||||
return super.init(src)
|
||||
private fun getExpectedStackFrame(location: AbstractInsnNode) = getFrame(expectedStackNode[location] ?: location)
|
||||
private fun getFrame(location: AbstractInsnNode) = analyzer.getFrame(location) as? InternalAnalyzer.FixStackFrame
|
||||
|
||||
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() {
|
||||
extraStack.clear()
|
||||
super.clearStack()
|
||||
context.fakeAlwaysFalseIfeqMarkers.clear()
|
||||
}
|
||||
|
||||
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>) {
|
||||
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
|
||||
override fun newFrame(nLocals: Int, nStack: Int): Frame<BasicValue> =
|
||||
FixStackFrame(nLocals, nStack)
|
||||
|
||||
private fun indexOf(node: AbstractInsnNode) = method.instructions.indexOf(node)
|
||||
|
||||
inner class FixStackFrame(nLocals: Int, nStack: Int) : Frame<BasicValue>(nLocals, nStack) {
|
||||
val extraStack = Stack<BasicValue>()
|
||||
|
||||
override fun init(src: Frame<out BasicValue>): Frame<BasicValue> {
|
||||
extraStack.clear()
|
||||
extraStack.addAll((src as FixStackFrame).extraStack)
|
||||
return super.init(src)
|
||||
}
|
||||
|
||||
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 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)
|
||||
fun pushAll(values: Collection<BasicValue>) {
|
||||
values.forEach { push(it) }
|
||||
}
|
||||
else {
|
||||
extraStack.add(value)
|
||||
maxExtraStackSize = Math.max(maxExtraStackSize, extraStack.size)
|
||||
|
||||
override fun pop(): BasicValue {
|
||||
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>) {
|
||||
values.forEach { push(it) }
|
||||
private fun FixStackFrame.executeBeforeInlineCallMarker(insn: AbstractInsnNode) {
|
||||
saveStackAndClear(insn)
|
||||
}
|
||||
|
||||
override fun pop(): BasicValue {
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
private fun FixStackFrame.saveStackAndClear(insn: AbstractInsnNode) {
|
||||
val savedValues = getStackContent()
|
||||
spilledStacks[insn] = savedValues
|
||||
clearStack()
|
||||
val savedValues = savedStacks[beforeInlineMarker]
|
||||
pushAll(savedValues!!)
|
||||
push(returnValue)
|
||||
}
|
||||
else {
|
||||
val savedValues = savedStacks[beforeInlineMarker]
|
||||
pushAll(savedValues!!)
|
||||
|
||||
private fun FixStackFrame.executeAfterInlineCallMarker(insn: AbstractInsnNode) {
|
||||
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 labelIndex = methodNode.instructions.indexOf(gotoNode.label)
|
||||
|
||||
val DEAD_CODE = -1 // Stack size is always non-negative
|
||||
val actualStackSize = analyzer.frames[gotoIndex]?.stackSize ?: DEAD_CODE
|
||||
val expectedStackSize = analyzer.frames[labelIndex]?.stackSize ?: DEAD_CODE
|
||||
val actualStackSize = analyzer.getActualStackSize(gotoNode)
|
||||
val expectedStackSize = analyzer.getExpectedStackSize(gotoNode.label)
|
||||
|
||||
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.previous, expectedStackSize, frame) })
|
||||
if (actualStackSize >= 0 && expectedStackSize >= 0) {
|
||||
assert(expectedStackSize <= actualStackSize) {
|
||||
"Label at $labelIndex, jump at $gotoIndex: stack underflow: $expectedStackSize > $actualStackSize"
|
||||
}
|
||||
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")
|
||||
}
|
||||
else {
|
||||
@@ -120,7 +122,7 @@ class FixStackMethodTransformer : MethodTransformer() {
|
||||
marker: AbstractInsnNode,
|
||||
localVariablesManager: LocalVariablesManager
|
||||
) {
|
||||
val savedStackValues = analyzer.savedStacks[marker]
|
||||
val savedStackValues = analyzer.getStackToSpill(marker)
|
||||
if (savedStackValues != null) {
|
||||
val savedStackDescriptor = localVariablesManager.allocateVariablesForSaveStackMarker(marker, savedStackValues)
|
||||
actions.add({ saveStack(methodNode, marker, savedStackDescriptor, false) })
|
||||
@@ -151,20 +153,21 @@ class FixStackMethodTransformer : MethodTransformer() {
|
||||
localVariablesManager: LocalVariablesManager
|
||||
) {
|
||||
val savedStackDescriptor = localVariablesManager.getBeforeInlineDescriptor(inlineMarker)
|
||||
val afterInlineFrame = analyzer.getFrame(inlineMarker) as FixStackAnalyzer.FixStackFrame?
|
||||
if (afterInlineFrame != null && savedStackDescriptor.isNotEmpty()) {
|
||||
assert(afterInlineFrame.stackSize <= 1) { "Inline method should not leave more than 1 value on stack" }
|
||||
if (afterInlineFrame.stackSize == 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) })
|
||||
val stackContentAfterInline = analyzer.getActualStack(inlineMarker)
|
||||
if (stackContentAfterInline != null && savedStackDescriptor.isNotEmpty()) {
|
||||
when (stackContentAfterInline.size) {
|
||||
1 -> {
|
||||
val returnValue = stackContentAfterInline.last()
|
||||
val returnValueLocalVarIndex = localVariablesManager.createReturnValueVariable(returnValue)
|
||||
actions.add({
|
||||
restoreStackWithReturnValue(methodNode, inlineMarker, savedStackDescriptor,
|
||||
returnValue, returnValueLocalVarIndex)
|
||||
})
|
||||
}
|
||||
0 ->
|
||||
actions.add({ restoreStack(methodNode, inlineMarker, savedStackDescriptor) })
|
||||
else ->
|
||||
throw AssertionError("Inline method should not leave more than 1 value on stack")
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -181,7 +184,7 @@ class FixStackMethodTransformer : MethodTransformer() {
|
||||
inlineMarker: AbstractInsnNode,
|
||||
localVariablesManager: LocalVariablesManager
|
||||
) {
|
||||
val savedStackValues = analyzer.savedStacks[inlineMarker]
|
||||
val savedStackValues = analyzer.getStackToSpill(inlineMarker)
|
||||
if (savedStackValues != null) {
|
||||
val savedStackDescriptor = localVariablesManager.allocateVariablesForBeforeInlineMarker(inlineMarker, savedStackValues)
|
||||
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) {
|
||||
while (frame.stackSize > expectedStackSize) {
|
||||
val top = frame.pop()
|
||||
insertBefore(node, getPopInstruction(top))
|
||||
for (stackValue in stackContent.subList(expectedStackSize, stackContent.size)) {
|
||||
insert(node, getPopInstruction(stackValue))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 2 +3 4 2 7 10
|
||||
// 2 3 4 2 7 10
|
||||
+12
@@ -4345,6 +4345,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
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")
|
||||
public void testPopSizes() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testKt1770() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||
|
||||
@@ -4345,6 +4345,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
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")
|
||||
public void testPopSizes() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testKt1770() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||
|
||||
+12
@@ -4345,6 +4345,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
|
||||
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")
|
||||
public void testPopSizes() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testKt1770() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||
|
||||
@@ -10,13 +10,14 @@ stepOverWhileWithInline.kt:13
|
||||
stepOverWhileWithInline.kt:14
|
||||
stepOverWhileWithInline.kt:15
|
||||
stepOverWhileWithInline.kt:13
|
||||
stepOverWhileWithInline.kt:18
|
||||
stepOverWhileWithInline.kt:19
|
||||
stepOverWhileWithInline.kt:20
|
||||
stepOverWhileWithInline.kt:21
|
||||
stepOverWhileWithInline.kt:23
|
||||
stepOverWhileWithInline.kt:24
|
||||
stepOverWhileWithInline.kt:25
|
||||
stepOverWhileWithInline.kt:26
|
||||
stepOverWhileWithInline.kt:27
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+12
@@ -5084,6 +5084,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
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")
|
||||
public void testPopSizes() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testKt1770() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/fullJdk/regressions/kt1770.kt");
|
||||
|
||||
Reference in New Issue
Block a user