Add $completion entry to suspend functions' LVT
#KT-28810 Fixed
This commit is contained in:
+33
-7
@@ -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_CLASS_NAME_JVM_NAME = "c"
|
||||||
private const val COROUTINES_METADATA_VERSION_JVM_NAME = "v"
|
private const val COROUTINES_METADATA_VERSION_JVM_NAME = "v"
|
||||||
|
|
||||||
|
const val SUSPEND_FUNCTION_CONTINUATION_PARAMETER = "\$completion"
|
||||||
|
|
||||||
class CoroutineTransformerMethodVisitor(
|
class CoroutineTransformerMethodVisitor(
|
||||||
delegate: MethodVisitor,
|
delegate: MethodVisitor,
|
||||||
access: Int,
|
access: Int,
|
||||||
@@ -105,6 +107,8 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
if (isForNamedFunction) {
|
if (isForNamedFunction) {
|
||||||
ReturnUnitMethodTransformer.transform(containingClassInternalName, methodNode)
|
ReturnUnitMethodTransformer.transform(containingClassInternalName, methodNode)
|
||||||
|
|
||||||
|
addCompletionParameterToLVT(methodNode)
|
||||||
|
|
||||||
if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) {
|
if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) {
|
||||||
dropSuspensionMarkers(methodNode, suspensionPoints)
|
dropSuspensionMarkers(methodNode, suspensionPoints)
|
||||||
return
|
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) =
|
private fun findSuspensionPointLineNumber(suspensionPoint: SuspensionPoint) =
|
||||||
suspensionPoint.suspensionCallBegin.findPreviousOrNull { it is LineNumberNode } as LineNumberNode?
|
suspensionPoint.suspensionCallBegin.findPreviousOrNull { it is LineNumberNode } as LineNumberNode?
|
||||||
|
|
||||||
@@ -238,15 +268,11 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fixLvtForParameters(methodNode: MethodNode, startLabel: LabelNode, endLabel: LabelNode) {
|
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 =
|
val paramsNum =
|
||||||
/* this */ (if (internalNameForDispatchReceiver != null) 1 else 0) +
|
/* this */ (if (isStatic(methodNode.access)) 0 else 1) +
|
||||||
/* real params */ Type.getArgumentTypes(methodNode.desc).size -
|
/* real params */ Type.getArgumentTypes(methodNode.desc).fold(0) { a, b -> a + b.size }
|
||||||
/* no continuation */ if (isForNamedFunction) 1 else 0
|
|
||||||
|
|
||||||
for (i in 0..paramsNum) {
|
for (i in 0 until paramsNum) {
|
||||||
fixRangeOfLvtRecord(methodNode, i, startLabel, endLabel)
|
fixRangeOfLvtRecord(methodNode, i, startLabel, endLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.common
|
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.codegen.optimization.transformer.MethodTransformer
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
@@ -80,7 +81,7 @@ private fun useVar(
|
|||||||
val index = node.instructions.indexOf(insn)
|
val index = node.instructions.indexOf(insn)
|
||||||
node.localVariables.filter {
|
node.localVariables.filter {
|
||||||
// Inliner fake variables, despite being present in LVT, are not read, thus are always dead
|
// 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) &&
|
node.instructions.indexOf(it.start) < index && index < node.instructions.indexOf(it.end) &&
|
||||||
Type.getType(it.desc).sort == typeAnnotatedFrame?.getLocal(it.index)?.type?.sort
|
Type.getType(it.desc).sort == typeAnnotatedFrame?.getLocal(it.index)?.type?.sort
|
||||||
}.forEach {
|
}.forEach {
|
||||||
@@ -93,3 +94,8 @@ private fun useVar(
|
|||||||
frame.markAlive(insn.`var`)
|
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
|
||||||
|
|||||||
+10
@@ -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
|
||||||
Vendored
+16
@@ -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
|
||||||
+7
@@ -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
|
||||||
Vendored
+10
@@ -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
|
||||||
Vendored
+13
@@ -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
|
||||||
compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt
Vendored
+16
@@ -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
|
||||||
+2
-1
@@ -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=y_param TYPE=Ljava/lang/String; INDEX=8
|
||||||
// VARIABLE : NAME=$i$a$-foo-InlineKt$test$2 TYPE=I INDEX=5
|
// VARIABLE : NAME=$i$a$-foo-InlineKt$test$2 TYPE=I INDEX=5
|
||||||
// VARIABLE : NAME=a$iv TYPE=LA; INDEX=1
|
// VARIABLE : NAME=a$iv TYPE=LA; INDEX=1
|
||||||
// VARIABLE : NAME=$i$f$foo TYPE=I INDEX=2
|
// VARIABLE : NAME=$i$f$foo TYPE=I INDEX=2
|
||||||
|
// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=0
|
||||||
+43
@@ -109,6 +109,49 @@ public class CheckLocalVariablesTableTestGenerated extends AbstractCheckLocalVar
|
|||||||
runTest("compiler/testData/checkLocalVariablesTable/underscoreNames.kt");
|
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")
|
@TestMetadata("compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Generated
+43
@@ -109,6 +109,49 @@ public class IrCheckLocalVariablesTableTestGenerated extends AbstractIrCheckLoca
|
|||||||
runTest("compiler/testData/checkLocalVariablesTable/underscoreNames.kt");
|
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")
|
@TestMetadata("compiler/testData/checkLocalVariablesTable/destructuringInSuspendLambda")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user