diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index f07468ede65..3220dc5b864 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -49,6 +49,8 @@ private const val COROUTINES_METADATA_METHOD_NAME_JVM_NAME = "m" private const val COROUTINES_METADATA_CLASS_NAME_JVM_NAME = "c" private const val COROUTINES_METADATA_VERSION_JVM_NAME = "v" +const val SUSPEND_FUNCTION_CONTINUATION_PARAMETER = "\$completion" + class CoroutineTransformerMethodVisitor( delegate: MethodVisitor, access: Int, @@ -105,6 +107,8 @@ class CoroutineTransformerMethodVisitor( if (isForNamedFunction) { ReturnUnitMethodTransformer.transform(containingClassInternalName, methodNode) + addCompletionParameterToLVT(methodNode) + if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) { dropSuspensionMarkers(methodNode, suspensionPoints) return @@ -197,6 +201,32 @@ class CoroutineTransformerMethodVisitor( } } + private fun addCompletionParameterToLVT(methodNode: MethodNode) { + val index = + /* all args */ Type.getMethodType(methodNode.desc).argumentTypes.fold(0) { a, b -> a + b.size } + + /* this */ (if (isStatic(methodNode.access)) 0 else 1) - + /* only last */ 1 + val startLabel = with(methodNode.instructions) { + if (first is LabelNode) first as LabelNode + else LabelNode().also { insertBefore(first, it) } + } + + val endLabel = with(methodNode.instructions) { + if (last is LabelNode) last as LabelNode + else LabelNode().also { insert(last, it) } + } + methodNode.localVariables.add( + LocalVariableNode( + SUSPEND_FUNCTION_CONTINUATION_PARAMETER, + languageVersionSettings.continuationAsmType().descriptor, + null, + startLabel, + endLabel, + index + ) + ) + } + private fun findSuspensionPointLineNumber(suspensionPoint: SuspensionPoint) = suspensionPoint.suspensionCallBegin.findPreviousOrNull { it is LineNumberNode } as LineNumberNode? @@ -238,15 +268,11 @@ class CoroutineTransformerMethodVisitor( } private fun fixLvtForParameters(methodNode: MethodNode, startLabel: LabelNode, endLabel: LabelNode) { - // We need to skip continuation, since the inliner likes to remap variables there. - // But this is not a problem, since we have separate $continuation LVT entry - val paramsNum = - /* this */ (if (internalNameForDispatchReceiver != null) 1 else 0) + - /* real params */ Type.getArgumentTypes(methodNode.desc).size - - /* no continuation */ if (isForNamedFunction) 1 else 0 + /* this */ (if (isStatic(methodNode.access)) 0 else 1) + + /* real params */ Type.getArgumentTypes(methodNode.desc).fold(0) { a, b -> a + b.size } - for (i in 0..paramsNum) { + for (i in 0 until paramsNum) { fixRangeOfLvtRecord(methodNode, i, startLabel, endLabel) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt index 133ffa784b8..a4d34f8bd3e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/variableLiveness.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.codegen.optimization.common +import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_CONTINUATION_PARAMETER import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.org.objectweb.asm.Type @@ -80,7 +81,7 @@ private fun useVar( val index = node.instructions.indexOf(insn) node.localVariables.filter { // Inliner fake variables, despite being present in LVT, are not read, thus are always dead - !it.name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) && !it.name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) && + !it.name.isInvisibleDebuggerVariable() && node.instructions.indexOf(it.start) < index && index < node.instructions.indexOf(it.end) && Type.getType(it.desc).sort == typeAnnotatedFrame?.getLocal(it.index)?.type?.sort }.forEach { @@ -93,3 +94,8 @@ private fun useVar( frame.markAlive(insn.`var`) } } + +private fun String.isInvisibleDebuggerVariable(): Boolean = + startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) || + startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) || + this == SUSPEND_FUNCTION_CONTINUATION_PARAMETER diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticSimple.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticSimple.kt new file mode 100644 index 00000000000..ddc5a9a5d1e --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticSimple.kt @@ -0,0 +1,10 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +class A { + suspend fun foo() {} +} + +// METHOD : A.foo(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=this TYPE=LA; INDEX=0 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=1 diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticStateMachine.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticStateMachine.kt new file mode 100644 index 00000000000..9c860ba7074 --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticStateMachine.kt @@ -0,0 +1,16 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +class A { + suspend fun foo() {} + suspend fun foo1(l: Long) { + foo() + foo() + } +} + +// METHOD : A.foo1(JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=this TYPE=LA; INDEX=0 +// VARIABLE : NAME=l TYPE=J INDEX=1 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=3 +// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=5 \ No newline at end of file diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimple.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimple.kt new file mode 100644 index 00000000000..5f31de6ab74 --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimple.kt @@ -0,0 +1,7 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +suspend fun foo() {} + +// METHOD : StaticSimpleKt.foo(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=0 diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimpleReceiver.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimpleReceiver.kt new file mode 100644 index 00000000000..82ca6f58225 --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimpleReceiver.kt @@ -0,0 +1,10 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +class A + +suspend fun A.foo() {} + +// METHOD : StaticSimpleReceiverKt.foo(LA;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=$receiver TYPE=LA; INDEX=0 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=1 \ No newline at end of file diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachine.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachine.kt new file mode 100644 index 00000000000..464cd2c779b --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachine.kt @@ -0,0 +1,13 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +suspend fun foo() {} +suspend fun foo1(l: Long) { + foo() + foo() +} + +// METHOD : StaticStateMachineKt.foo1(JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=l TYPE=J INDEX=0 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=2 +// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=4 \ No newline at end of file diff --git a/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt new file mode 100644 index 00000000000..e8ae813e3a4 --- /dev/null +++ b/compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt @@ -0,0 +1,16 @@ +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +class A + +suspend fun A.foo() {} +suspend fun A.foo1(l: Long) { + foo() + foo() +} + +// METHOD : StaticStateMachineReceiverKt.foo1(LA;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +// VARIABLE : NAME=$receiver TYPE=LA; INDEX=0 +// VARIABLE : NAME=l TYPE=J INDEX=1 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=3 +// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=5 \ No newline at end of file diff --git a/compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda/inline.kt b/compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda/inline.kt index 34db8db6e87..1d745b2becf 100644 --- a/compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda/inline.kt +++ b/compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda/inline.kt @@ -14,4 +14,5 @@ suspend fun test() = foo(A("O", "K")) { (x_param, y_param) -> x_param + y_param // VARIABLE : NAME=y_param TYPE=Ljava/lang/String; INDEX=8 // VARIABLE : NAME=$i$a$-foo-InlineKt$test$2 TYPE=I INDEX=5 // VARIABLE : NAME=a$iv TYPE=LA; INDEX=1 -// VARIABLE : NAME=$i$f$foo TYPE=I INDEX=2 \ No newline at end of file +// VARIABLE : NAME=$i$f$foo TYPE=I INDEX=2 +// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=0 \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java index 056026adcac..aa6c74d2650 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java @@ -109,6 +109,49 @@ public class CheckLocalVariablesTableTestGenerated extends AbstractCheckLocalVar runTest("compiler/testData/checkLocalVariablesTable/underscoreNames.kt"); } + @TestMetadata("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CompletionInSuspendFunction extends AbstractCheckLocalVariablesTableTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInCompletionInSuspendFunction() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("nonStaticSimple.kt") + public void testNonStaticSimple() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticSimple.kt"); + } + + @TestMetadata("nonStaticStateMachine.kt") + public void testNonStaticStateMachine() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticStateMachine.kt"); + } + + @TestMetadata("staticSimple.kt") + public void testStaticSimple() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimple.kt"); + } + + @TestMetadata("staticSimpleReceiver.kt") + public void testStaticSimpleReceiver() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimpleReceiver.kt"); + } + + @TestMetadata("staticStateMachine.kt") + public void testStaticStateMachine() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachine.kt"); + } + + @TestMetadata("staticStateMachineReceiver.kt") + public void testStaticStateMachineReceiver() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt"); + } + } + @TestMetadata("compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java index e2cfb102ae0..d5e368a2dfa 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java @@ -109,6 +109,49 @@ public class IrCheckLocalVariablesTableTestGenerated extends AbstractIrCheckLoca runTest("compiler/testData/checkLocalVariablesTable/underscoreNames.kt"); } + @TestMetadata("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CompletionInSuspendFunction extends AbstractIrCheckLocalVariablesTableTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInCompletionInSuspendFunction() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); + } + + @TestMetadata("nonStaticSimple.kt") + public void testNonStaticSimple() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticSimple.kt"); + } + + @TestMetadata("nonStaticStateMachine.kt") + public void testNonStaticStateMachine() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/nonStaticStateMachine.kt"); + } + + @TestMetadata("staticSimple.kt") + public void testStaticSimple() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimple.kt"); + } + + @TestMetadata("staticSimpleReceiver.kt") + public void testStaticSimpleReceiver() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticSimpleReceiver.kt"); + } + + @TestMetadata("staticStateMachine.kt") + public void testStaticStateMachine() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachine.kt"); + } + + @TestMetadata("staticStateMachineReceiver.kt") + public void testStaticStateMachineReceiver() throws Exception { + runTest("compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt"); + } + } + @TestMetadata("compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)