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.
This commit is contained in:
+4
-1
@@ -149,7 +149,10 @@ class ExpressionCodegen(
|
|||||||
if (fileEntry != null) {
|
if (fileEntry != null) {
|
||||||
val lineNumber = fileEntry.getLineNumber(offset) + 1
|
val lineNumber = fileEntry.getLineNumber(offset) + 1
|
||||||
assert(lineNumber > 0)
|
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
|
lastLineNumber = lineNumber
|
||||||
mv.visitLineNumber(lineNumber, markNewLabel())
|
mv.visitLineNumber(lineNumber, markNewLabel())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Array<String>> {
|
||||||
|
(it as BaseContinuationImpl).getSpilledVariableFieldMapping()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Array<String>.toMap(): Map<String, String> {
|
||||||
|
val res = hashMapOf<String, String>()
|
||||||
|
for (i in 0..(size - 1) step 2) {
|
||||||
|
res[get(i)] = get(i + 1)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
var continuation: Continuation<Unit>? = null
|
||||||
|
|
||||||
|
suspend fun suspendHere() = suspendCoroutineUninterceptedOrReturn<Unit> {
|
||||||
|
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"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
|
|||||||
+5
@@ -6880,6 +6880,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata.kt");
|
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")
|
@TestMetadata("elvisLineNumber.kt")
|
||||||
public void testElvisLineNumber() throws Exception {
|
public void testElvisLineNumber() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt");
|
runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt");
|
||||||
|
|||||||
+5
@@ -6867,6 +6867,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class Debug extends AbstractLightAnalysisModeTest {
|
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 {
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -6335,6 +6335,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
runTest("compiler/testData/codegen/box/coroutines/debug/debuggerMetadata.kt");
|
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")
|
@TestMetadata("elvisLineNumber.kt")
|
||||||
public void testElvisLineNumber() throws Exception {
|
public void testElvisLineNumber() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt");
|
runTest("compiler/testData/codegen/box/coroutines/debug/elvisLineNumber.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user