Check variables, captured by outer object when searching for suspend crossinline parameters

This commit is contained in:
Ilmir Usmanov
2019-04-17 19:33:33 +03:00
parent 7c14f4c6ae
commit a793055ec2
14 changed files with 387 additions and 27 deletions
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.CodegenUtil;
import org.jetbrains.kotlin.backend.common.bridges.Bridge;
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure;
import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
import org.jetbrains.kotlin.codegen.context.*;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
@@ -1645,7 +1646,7 @@ public class FunctionCodegen {
}
@Nullable
public Map<DeclarationDescriptor, EnclosedValueDescriptor> getCaptureVariables() {
return owner.closure == null ? null : owner.closure.getCaptureVariables();
public CalculatedClosure getClosure() {
return owner.closure;
}
}
@@ -8,10 +8,11 @@ package org.jetbrains.kotlin.codegen.coroutines
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.builtins.isSuspendFunctionTypeOrSubtype
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.CAPTURES_CROSSINLINE_LAMBDA
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.CLOSURE
import org.jetbrains.kotlin.codegen.context.ClosureContext
import org.jetbrains.kotlin.codegen.context.EnclosedValueDescriptor
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.inline.coroutines.SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor
import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.METHOD_FOR_FUNCTION
@@ -26,6 +27,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -479,7 +481,7 @@ class CoroutineCodegenForLambda private constructor(
MethodNodeCopyingMethodVisitor(
SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor(
stateMachineBuilder, access, name, desc, v.thisName,
isCapturedSuspendLambda = { isCapturedSuspendLambda(closure.captureVariables, it.name) }
isCapturedSuspendLambda = { isCapturedSuspendLambda(closure, it.name, state.bindingContext) }
), access, name, desc,
newMethod = { origin, newAccess, newName, newDesc ->
functionCodegen.newMethod(origin, newAccess, newName, newDesc, null, null)
@@ -528,13 +530,19 @@ class CoroutineCodegenForLambda private constructor(
}
}
fun isCapturedSuspendLambda(captureVariables: Map<DeclarationDescriptor, EnclosedValueDescriptor>, name: String): Boolean {
for ((param, value) in captureVariables) {
fun isCapturedSuspendLambda(closure: CalculatedClosure, name: String, bindingContext: BindingContext): Boolean {
for ((param, value) in closure.captureVariables) {
if (param !is ValueParameterDescriptor) continue
if (value.fieldName != name) continue
return param.type.isSuspendFunctionTypeOrSubtype
}
return false
val classDescriptor = closure.capturedOuterClassDescriptor ?: return false
return isCapturedSuspendLambda(classDescriptor, name, bindingContext)
}
fun isCapturedSuspendLambda(classDescriptor: ClassDescriptor, name: String, bindingContext: BindingContext): Boolean {
val closure = bindingContext[CLOSURE, classDescriptor] ?: return false
return isCapturedSuspendLambda(closure, name, bindingContext)
}
private class AddEndLabelMethodVisitor(
@@ -76,10 +76,11 @@ open class SuspendFunctionGenerationStrategy(
access, name, desc, containingClassInternalName,
isCapturedSuspendLambda = {
isCapturedSuspendLambda(
functionCodegen.captureVariables.sure {
functionCodegen.closure.sure {
"Anonymous object should have closure"
},
it.name
it.name,
state.bindingContext
)
}
), access, name, desc,
@@ -484,9 +484,9 @@ class AnonymousObjectTransformer(
alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false),
alreadyAddedParam != null
)
if (info is PsiExpressionLambda && info.captureVariables.any { it.value.fieldName == desc.fieldName }) {
if (info is PsiExpressionLambda && info.closure.captureVariables.any { it.value.fieldName == desc.fieldName }) {
recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend(
isCapturedSuspendLambda(info.captureVariables, desc.fieldName)
isCapturedSuspendLambda(info.closure, desc.fieldName, inliningContext.state.bindingContext)
)
}
val composed = StackValue.field(
@@ -230,7 +230,8 @@ class PsiExpressionLambda(
private val labels: Set<String>
private var closure: CalculatedClosure
var closure: CalculatedClosure
private set
init {
val bindingContext = typeMapper.bindingContext
@@ -307,6 +308,4 @@ class PsiExpressionLambda(
val isPropertyReference: Boolean
get() = propertyReferenceInfo != null
val captureVariables = closure.captureVariables
}
@@ -10,13 +10,13 @@ import org.jetbrains.kotlin.codegen.AsmUtil.CAPTURED_THIS_FIELD
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
import org.jetbrains.kotlin.codegen.coroutines.*
import org.jetbrains.kotlin.codegen.coroutines.getLastParameterIndex
import org.jetbrains.kotlin.codegen.coroutines.replaceFakeContinuationsWithRealOnes
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.findPreviousOrNull
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.config.isReleaseCoroutines
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.utils.addToStdlib.cast
@@ -98,7 +98,7 @@ class CoroutineTransformer(
ArrayUtil.toStringArray(node.exceptions)
)
) {
val stateMachineBuilder = surroundNoinlineCallsWithMarkersIfNeeded(
val stateMachineBuilder = surroundNoinlineCallsWithMarkers(
node,
CoroutineTransformerMethodVisitor(
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
@@ -137,7 +137,7 @@ class CoroutineTransformer(
ArrayUtil.toStringArray(node.exceptions)
)
) {
val stateMachineBuilder = surroundNoinlineCallsWithMarkersIfNeeded(
val stateMachineBuilder = surroundNoinlineCallsWithMarkers(
node,
CoroutineTransformerMethodVisitor(
createNewMethodFrom(node, name), node.access, name, node.desc, null, null,
@@ -165,16 +165,34 @@ class CoroutineTransformer(
}
}
private fun surroundNoinlineCallsWithMarkersIfNeeded(node: MethodNode, delegate: MethodVisitor): MethodVisitor =
if (capturedParams.any { it.functionalArgument?.isSuspendLambda() == true })
SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor(
delegate, node.access, node.name, node.desc, classBuilder.thisName, this::isCapturedSuspendLambda
)
else
delegate
private fun surroundNoinlineCallsWithMarkers(node: MethodNode, delegate: MethodVisitor): MethodVisitor =
SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor(
delegate, node.access, node.name, node.desc, classBuilder.thisName, this::fieldIsCapturedSuspendLambda
)
private fun isCapturedSuspendLambda(field: FieldInsnNode): Boolean =
capturedParams.find { it.newFieldName == field.name }?.functionalArgument?.isSuspendLambda() == true
private fun fieldIsCapturedSuspendLambda(field: FieldInsnNode): Boolean =
capturedParams.find { it.newFieldName == field.name }?.let { it.functionalArgument?.isSuspendLambda() == true }
?: isSuspendLambdaCapturedByOuterObjectOrLambda(field)
// We cannot find the lambda in captured parameters: it came from object outside of the our reach:
// this can happen when the lambda capture by non-transformed closure:
// inline fun inlineMe(crossinline c: suspend() -> Unit) = suspend { c() }
// inline fun inlineMe2(crossinline c: suspend() -> Unit) = suspend { inlineMe { c() }() }
// Suppose, we inline inlineMe into inlineMe2: the only knowledge we have about inlineMe$1 is captured receiver (this$0)
// Thus, transformed lambda from inlineMe, inlineMe3$$inlined$inlineMe2$1 contains the following bytecode
// ALOAD 0
// GETFIELD inlineMe2$1$invokeSuspend$$inlined$inlineMe$1.this$0 : LScratchKt$inlineMe2$1;
// GETFIELD inlineMe2$1.$c : Lkotlin/jvm/functions/Function1;
// Since inlineMe2's lambda is outside of reach of the inliner, find crossinline parameter from compilation context:
private fun isSuspendLambdaCapturedByOuterObjectOrLambda(field: FieldInsnNode): Boolean {
val functionDescriptor = inliningContext.root.sourceCompilerForInline.compilationContextFunctionDescriptor
val classDescriptor = functionDescriptor.findContainingClassOrLambda() ?: return false
return isCapturedSuspendLambda(classDescriptor, field.name, inliningContext.state.bindingContext)
}
private tailrec fun DeclarationDescriptor.findContainingClassOrLambda(): ClassDescriptor? =
if (containingDeclaration is ClassDescriptor) containingDeclaration as ClassDescriptor
else containingDeclaration?.findContainingClassOrLambda()
private fun createNewMethodFrom(node: MethodNode, name: String): MethodVisitor {
return classBuilder.newMethod(
@@ -0,0 +1,283 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// COMMON_COROUTINES_TEST
// WITH_RUNTIME
// WITH_COROUTINES
// CHECK_STATE_MACHINE
// FILE: inlineMe.kt
package test
import helpers.*
interface SuspendRunnable {
suspend fun run1()
suspend fun run2()
}
inline fun inlineMe(crossinline c1: suspend () -> Unit, crossinline c2: suspend () -> Unit) = object : SuspendRunnable {
override suspend fun run1() {
c1(); c1()
}
override suspend fun run2() {
c2(); c2()
}
}
inline fun inlineMe2(crossinline c1: suspend () -> Unit, crossinline c2: suspend () -> Unit) = inlineMe({ c1(); c1() }) { c2(); c2() }
inline fun inlineMe3(crossinline c1: suspend () -> Unit, crossinline c2: suspend () -> Unit) = object : SuspendRunnable {
override suspend fun run1() {
val sr = inlineMe({ c1(); c1() }) { c2(); c2() }
sr.run1()
sr.run2()
}
override suspend fun run2() {
val sr = inlineMe2({ c1(); c1() }) { c2(); c2() }
sr.run1()
sr.run2()
}
}
inline fun inlineMe4(crossinline c1: suspend () -> Unit, crossinline c2: suspend () -> Unit) = object : SuspendRunnable {
override suspend fun run1() {
val sr = suspend {
c1();
c2()
}
sr()
sr()
}
override suspend fun run2() {
val sr = object : SuspendRunnable {
override suspend fun run1() {
c1(); c1()
}
override suspend fun run2() {
c2(); c2()
}
}
sr.run1()
sr.run2()
}
}
inline fun inlineMe5(crossinline c1: suspend () -> Unit) = inlineMe({ c1(); c1() }) {
StateMachineChecker.suspendHere()
}
// FILE: A.java
import test.InlineMeKt;
import helpers.CoroutineUtilKt;
import helpers.EmptyContinuation;
import kotlin.Unit;
public class A {
public static Object call() {
return InlineMeKt.inlineMe((continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation),
(continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation));
}
public static Object call2() {
return InlineMeKt.inlineMe2((continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation),
(continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation));
}
public static Object call3() {
return InlineMeKt.inlineMe3((continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation),
(continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation));
}
public static Object call4() {
return InlineMeKt.inlineMe4((continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation),
(continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation));
}
public static Object call5() {
return InlineMeKt.inlineMe5((continuation) -> CoroutineUtilKt.getStateMachineChecker().suspendHere(continuation));
}
}
// FILE: box.kt
import test.*
import helpers.*
import COROUTINES_PACKAGE.*
fun builder(c: suspend () -> Unit) {
c.startCoroutine(CheckStateMachineContinuation)
}
fun box(): String {
builder {
(A.call() as SuspendRunnable).run1()
}
StateMachineChecker.check(2)
StateMachineChecker.reset()
builder {
(A.call() as SuspendRunnable).run2()
}
StateMachineChecker.check(2)
StateMachineChecker.reset()
builder {
(A.call2() as SuspendRunnable).run1()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
(A.call2() as SuspendRunnable).run2()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
(A.call3() as SuspendRunnable).run1()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
(A.call3() as SuspendRunnable).run2()
}
StateMachineChecker.check(16)
StateMachineChecker.reset()
builder {
(A.call4() as SuspendRunnable).run1()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
(A.call4() as SuspendRunnable).run2()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
(A.call5() as SuspendRunnable).run1()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
(A.call5() as SuspendRunnable).run2()
}
StateMachineChecker.check(2)
StateMachineChecker.reset()
builder {
inlineMe({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run1()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
inlineMe({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run2()
}
StateMachineChecker.check(4)
StateMachineChecker.reset()
builder {
inlineMe2 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run1()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
inlineMe2 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run2()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
inlineMe3 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run1()
}
StateMachineChecker.check(16)
StateMachineChecker.reset()
builder {
inlineMe3 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run2()
}
StateMachineChecker.check(32)
StateMachineChecker.reset()
builder {
inlineMe4 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run1()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
inlineMe4 ({
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}) {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run2()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
inlineMe5 {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run1()
}
StateMachineChecker.check(8)
StateMachineChecker.reset()
builder {
inlineMe5 {
StateMachineChecker.suspendHere()
StateMachineChecker.suspendHere()
}.run2()
}
StateMachineChecker.check(2)
StateMachineChecker.reset()
return "OK"
}
@@ -261,7 +261,13 @@ public final class CrossinlineKt$filter$$inlined$source$1 {
}
@kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class CrossinlineKt$fold$$inlined$consumeEach$1$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
field label: int
synthetic field result: java.lang.Object
synthetic final field this$0: CrossinlineKt$fold$$inlined$consumeEach$1
@@ -271,6 +271,11 @@ public final class CrossinlineKt$filter$$inlined$source$1 {
@kotlin.Metadata
public final class CrossinlineKt$fold$$inlined$consumeEach$1$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field L$4: java.lang.Object
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
synthetic final field this$0: CrossinlineKt$fold$$inlined$consumeEach$1
@@ -140,7 +140,12 @@ public final class flow/InnerObjectRetransformationKt$flow$1 {
}
@kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class flow/InnerObjectRetransformationKt$flowWith$$inlined$flow$1$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
field label: int
synthetic field result: java.lang.Object
synthetic final field this$0: flow.InnerObjectRetransformationKt$flowWith$$inlined$flow$1
@@ -149,6 +149,10 @@ public final class flow/InnerObjectRetransformationKt$flow$1 {
@kotlin.Metadata
public final class flow/InnerObjectRetransformationKt$flowWith$$inlined$flow$1$1 {
field L$0: java.lang.Object
field L$1: java.lang.Object
field L$2: java.lang.Object
field L$3: java.lang.Object
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
synthetic final field this$0: flow.InnerObjectRetransformationKt$flowWith$$inlined$flow$1
@@ -7426,6 +7426,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
public void testReturnObject_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/returnObject.kt", "kotlin.coroutines");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/localFunctions")
@@ -7426,6 +7426,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
public void testReturnObject_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/returnObject.kt", "kotlin.coroutines");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/localFunctions")
@@ -7426,6 +7426,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
public void testReturnObject_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/returnObject.kt", "kotlin.coroutines");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_2() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines.experimental");
}
@TestMetadata("severalCaptures.kt")
public void testSeveralCaptures_1_3() throws Exception {
runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/javaInterop/severalCaptures.kt", "kotlin.coroutines");
}
}
@TestMetadata("compiler/testData/codegen/box/coroutines/localFunctions")