Add $completion entry to suspend functions' LVT

#KT-28810 Fixed
This commit is contained in:
Ilmir Usmanov
2019-02-20 17:55:11 +03:00
parent 677987c85f
commit ec7315e6da
11 changed files with 200 additions and 9 deletions
@@ -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)
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
// VARIABLE : NAME=$i$f$foo TYPE=I INDEX=2
// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=0
@@ -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)
@@ -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)