From f4a7c839d34d34b0c9927fb4a0bde70bd314403c Mon Sep 17 00:00:00 2001 From: Ilmir Usmanov Date: Tue, 24 Sep 2019 21:37:51 +0300 Subject: [PATCH] JVM_IR: Generate as much LINENUMBER information as possible inside suspend functions and lambdas. Otherwise, when state-machine builder splits instructions and adds LINENUMBER between states, the information becomes corrupted. --- .../backend/jvm/codegen/ExpressionCodegen.kt | 5 +- .../coroutines/debug/debuggerMetadata_ir.kt | 108 ++++++++++++++++++ .../box/coroutines/debug/elvisLineNumber.kt | 2 +- .../codegen/BlackBoxCodegenTestGenerated.java | 5 + .../LightAnalysisModeTestGenerated.java | 5 + .../ir/IrBlackBoxCodegenTestGenerated.java | 5 + 6 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index fac4a672d2f..1e50ad7c20d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -149,7 +149,10 @@ class ExpressionCodegen( if (fileEntry != null) { val lineNumber = fileEntry.getLineNumber(offset) + 1 assert(lineNumber > 0) - if (lastLineNumber != lineNumber) { + // State-machine builder splits the sequence of instructions into states inside state-machine, adding additional LINENUMBERs + // between them for debugger to stop on suspension. Thus, it requires as much LINENUMBER information as possible to be present, + // otherwise, any exception will have incorrect line number. See elvisLineNumber.kt test. + if (lastLineNumber != lineNumber || irFunction.isSuspend || irFunction.isInvokeSuspendOfLambda(context)) { lastLineNumber = lineNumber mv.visitLineNumber(lineNumber, markNewLabel()) } diff --git a/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt b/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt new file mode 100644 index 00000000000..be23b6319ba --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt @@ -0,0 +1,108 @@ +// IGNORE_BACKEND: JVM +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// WITH_COROUTINES + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") + +import helpers.* +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* +import kotlin.coroutines.jvm.internal.* + +suspend fun getSpilledToVariable() = suspendCoroutineUninterceptedOrReturn> { + (it as BaseContinuationImpl).getSpilledVariableFieldMapping() +} + +fun Array.toMap(): Map { + val res = hashMapOf() + for (i in 0..(size - 1) step 2) { + res[get(i)] = get(i + 1) + } + return res +} + +var continuation: Continuation? = null + +suspend fun suspendHere() = suspendCoroutineUninterceptedOrReturn { + continuation = it + COROUTINE_SUSPENDED +} + +suspend fun dummy() {} + +suspend fun named(): String { + dummy() + val s1 = "" + val s2 = "" + val s3 = "" + val s4 = "" + val s5 = "" + val s6 = "" + val s7 = "" + val s8 = "" + val s9 = "" + val map = getSpilledToVariable().toMap() + return map["L$0"] + map["L$1"] + map["L$2"] + map["L$3"] + map["L$4"] + map["L$5"] + map["L$6"] + map["L$7"] + map["L$8"] +} + +suspend fun suspended() { + dummy() + val ss = "" + suspendHere() +} + +suspend fun multipleLocalsInOneSlot() { + for (first in 0 until 1) { + suspendHere() + } + for (second in 0 until 1) { + suspendHere() + } +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var res: String = "" + builder { + res = named() + } + if (res != "s1s2s3s4s5s6s7s8s9") { + return "" + res + } + builder { + dummy() + val a = "" + res = getSpilledToVariable().toMap()["L$0"] ?: "lambda fail" + } + if (res != "a") { + return "" + res + } + + builder { + suspended() + } + res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["L$0"] ?: "suspended fail" + if (res != "ss") { + return "" + res + } + + builder { + multipleLocalsInOneSlot() + } + // TODO: Do not spill 0, since it is constant. + res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["I$1"] ?: "multipleLocalsInOneSlot fail 1" + if (res != "first") { + return "" + res + } + continuation!!.resumeWith(Result.success(Unit)) + res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["I$1"] ?: "multipleLocalsInOneSlot fail 2" + if (res != "second") { + return "" + res + } + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt b/compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt index d53ca035e40..148c543450a 100644 --- a/compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt +++ b/compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt @@ -1,4 +1,4 @@ -// IGNORE_BACKEND: JVM_IR + // TARGET_BACKEND: JVM // WITH_RUNTIME // WITH_COROUTINES diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d5ec76559ef..feee105d155 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -6880,6 +6880,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata.kt"); } + @TestMetadata("debuggerMetadata_ir.kt") + public void testDebuggerMetadata_ir() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt"); + } + @TestMetadata("elvisLineNumber.kt") public void testElvisLineNumber() throws Exception { runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index dd7492acc94..987df8a81ee 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -6867,6 +6867,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) public static class Debug extends AbstractLightAnalysisModeTest { + @TestMetadata("debuggerMetadata_ir.kt") + public void ignoreDebuggerMetadata_ir() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt"); + } + private void runTest(String testDataFilePath) throws Exception { KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); } diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 0aedc693784..ccc1ae654d7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -6335,6 +6335,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata.kt"); } + @TestMetadata("debuggerMetadata_ir.kt") + public void testDebuggerMetadata_ir() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt"); + } + @TestMetadata("elvisLineNumber.kt") public void testElvisLineNumber() throws Exception { runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt");