Make suspension calls generation more stable

Instead of performing signature change during transformation
it's convinient to make just when generating corresponding call,
for example there is no need to think about default parameters as they just work as is

See comment above replaceSuspensionFunctionViewWithRealDescriptor for clarification
This commit is contained in:
Denis Zharkov
2016-05-26 14:10:26 +03:00
parent 84ae28992e
commit b1189eff23
6 changed files with 149 additions and 23 deletions
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
import org.jetbrains.kotlin.codegen.context.*; import org.jetbrains.kotlin.codegen.context.*;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegen; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegen;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
import org.jetbrains.kotlin.codegen.coroutines.ResolvedCallWithRealDescriptor;
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension; import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension;
import org.jetbrains.kotlin.codegen.inline.*; import org.jetbrains.kotlin.codegen.inline.*;
import org.jetbrains.kotlin.codegen.intrinsics.*; import org.jetbrains.kotlin.codegen.intrinsics.*;
@@ -1665,26 +1666,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
tempVariables.put(argumentExpression, valueToReturn); tempVariables.put(argumentExpression, valueToReturn);
} }
// Currently only handleResult members are supported
ReceiverValue dispatchReceiver = resolvedCall.getDispatchReceiver();
assert dispatchReceiver != null : "Dispatch receiver is null for handleResult to " + resolvedCall.getResultingDescriptor();
assert dispatchReceiver instanceof ExtensionReceiver
: "Argument for handleResult call to " + resolvedCall.getResultingDescriptor() +
" should be a coroutine receiver parameter, but " + dispatchReceiver + " found";
ClassDescriptor coroutineClassDescriptor =
bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, ((ExtensionReceiver) dispatchReceiver).getDeclarationDescriptor());
assert coroutineClassDescriptor != null : "Coroutine class descriptor should not be null";
// second argument for handleResult is always Continuation<T> ('this'-object in current implementation)
tempVariables.put( tempVariables.put(
resolvedCall.getValueArgumentsByIndex().get(1).getArguments().get(0).getArgumentExpression(), resolvedCall.getValueArgumentsByIndex().get(1).getArguments().get(0).getArgumentExpression(),
StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false)); genCoroutineInstanceValueFromResolvedCall(resolvedCall));
return invokeFunction(resolvedCall, StackValue.none()); return invokeFunction(resolvedCall, StackValue.none());
} }
return null; return null;
} }
@NotNull
private StackValue genCoroutineInstanceValueFromResolvedCall(ResolvedCall<?> resolvedCall) {
// Currently only handleResult/suspend members are supported
ReceiverValue dispatchReceiver = resolvedCall.getDispatchReceiver();
assert dispatchReceiver != null : "Dispatch receiver is null for handleResult/suspend to " + resolvedCall.getResultingDescriptor();
assert dispatchReceiver instanceof ExtensionReceiver
: "Argument for handleResult call to " + resolvedCall.getResultingDescriptor() +
" should be a coroutine receiver parameter, but " + dispatchReceiver + " found";
ClassDescriptor coroutineClassDescriptor =
bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, ((ExtensionReceiver) dispatchReceiver).getDeclarationDescriptor());
assert coroutineClassDescriptor != null : "Coroutine class descriptor should not be null";
// second argument for handleResult is always Continuation<T> ('this'-object in current implementation)
return StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false);
}
@NotNull @NotNull
private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) { private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) {
Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor); Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor);
@@ -2477,6 +2483,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@NotNull @NotNull
public StackValue invokeFunction(@NotNull Call call, @NotNull ResolvedCall<?> resolvedCall, @NotNull StackValue receiver) { public StackValue invokeFunction(@NotNull Call call, @NotNull ResolvedCall<?> resolvedCall, @NotNull StackValue receiver) {
ResolvedCallWithRealDescriptor callWithRealDescriptor =
CoroutineCodegenUtilKt.replaceSuspensionFunctionViewWithRealDescriptor(resolvedCall, state.getProject());
if (callWithRealDescriptor != null) {
tempVariables.put(callWithRealDescriptor.getFakeThisExpression(), genCoroutineInstanceValueFromResolvedCall(resolvedCall));
return invokeFunction(callWithRealDescriptor.getResolvedCall(), receiver);
}
FunctionDescriptor fd = accessibleFunctionDescriptor(resolvedCall); FunctionDescriptor fd = accessibleFunctionDescriptor(resolvedCall);
ClassDescriptor superCallTarget = getSuperCallTarget(call); ClassDescriptor superCallTarget = getSuperCallTarget(call);
@@ -198,9 +198,7 @@ class CoroutineTransformerMethodVisitor(
private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode { private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
val call = suspension.suspensionCall val call = suspension.suspensionCall
val method = Method(call.name, call.desc) val method = Method(call.name, call.desc)
val newParameters = method.argumentTypes + CONTINUATION_INTERFACE_ASM_TYPE call.desc = Method(method.name, Type.VOID_TYPE, method.argumentTypes).descriptor
call.desc = Method(method.name, Type.VOID_TYPE, newParameters).descriptor
val continuationLabel = LabelNode() val continuationLabel = LabelNode()
@@ -213,9 +211,6 @@ class CoroutineTransformerMethodVisitor(
FieldInsnNode( FieldInsnNode(
Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor))) Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor)))
// Pass continuation
insertBefore(call, VarInsnNode(Opcodes.ALOAD, 0))
val nextInsnAfterCall = call.next val nextInsnAfterCall = call.next
// Exit // Exit
@@ -16,11 +16,20 @@
package org.jetbrains.kotlin.codegen.coroutines package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.coroutines.CONTINUATION_INTERFACE_FQ_NAME import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
val CONTINUATION_INTERFACE_ASM_TYPE = Type.getObjectType(JvmClassName.byFqNameWithoutInnerClasses(CONTINUATION_INTERFACE_FQ_NAME).internalName) import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCallImpl
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.types.TypeConstructorSubstitution
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
// These classes do not actually exist at runtime // These classes do not actually exist at runtime
val CONTINUATION_METHOD_ANNOTATION_DESC = "Lkotlin/ContinuationMethod;" val CONTINUATION_METHOD_ANNOTATION_DESC = "Lkotlin/ContinuationMethod;"
@@ -30,3 +39,65 @@ const val SUSPENSION_POINT_MARKER_NAME = "suspensionPoint"
const val COROUTINE_CONTROLLER_FIELD_NAME = "controller" const val COROUTINE_CONTROLLER_FIELD_NAME = "controller"
const val COROUTINE_LABEL_FIELD_NAME = "label" const val COROUTINE_LABEL_FIELD_NAME = "label"
data class ResolvedCallWithRealDescriptor(val resolvedCall: ResolvedCall<*>, val fakeThisExpression: KtExpression)
// Resolved calls to suspension function contain descriptors as they visible within coroutines:
// E.g. `fun <V> await(f: CompletableFuture<V>): V` instead of `fun <V> await(f: CompletableFuture<V>, machine: Continuation<V>): Unit`
// See `createCoroutineSuspensionFunctionView` and it's usages for clarification
// But for call generation it's convenient to have `machine` (continuation) parameter/argument within resolvedCall.
// So this function returns resolved call with descriptor looking like `fun <V> await(f: CompletableFuture<V>, machine: Continuation<V>): V`
// and fake `this` expression that used as argument for second parameter
fun ResolvedCall<*>.replaceSuspensionFunctionViewWithRealDescriptor(
project: Project
): ResolvedCallWithRealDescriptor? {
val function = candidateDescriptor as? FunctionDescriptor ?: return null
if (!function.isSuspend) return null
val initialSignatureDescriptor = function.initialSignatureDescriptor ?: return null
val newCandidateDescriptor =
initialSignatureDescriptor.createCustomCopy {
// Here we know that last parameter should be Continuation<T> where T is return type
setReturnType(it.valueParameters.last().type.arguments.single().type)
}
val newCall = ResolvedCallImpl(
call,
newCandidateDescriptor,
dispatchReceiver, extensionReceiver, explicitReceiverKind,
null, DelegatingBindingTrace(BindingTraceContext().bindingContext, "Temporary trace for unwrapped suspension function"),
TracingStrategy.EMPTY, MutableDataFlowInfoForArguments.WithoutArgumentsCheck(DataFlowInfo.EMPTY))
this.valueArguments.forEach {
newCall.recordValueArgument(newCandidateDescriptor.valueParameters[it.key.index], it.value)
}
val psiFactory = KtPsiFactory(project)
val arguments = psiFactory.createCallArguments("(this)").arguments.single()
val thisExpression = arguments.getArgumentExpression()!!
newCall.recordValueArgument(
newCandidateDescriptor.valueParameters.last(),
ExpressionValueArgument(arguments))
newCall.setResultingSubstitutor(
TypeConstructorSubstitution.createByParametersMap(typeArguments.mapValues { it.value.asTypeProjection() }).buildSubstitutor())
return ResolvedCallWithRealDescriptor(newCall, thisExpression)
}
private fun FunctionDescriptor.createCustomCopy(
copySettings: FunctionDescriptor.CopyBuilder<out FunctionDescriptor>.(FunctionDescriptor) -> FunctionDescriptor.CopyBuilder<out FunctionDescriptor>
): FunctionDescriptor {
val newOriginal =
if (original !== this)
original.createCustomCopy(copySettings)
else
null
val result = newCopyBuilder().copySettings(this).setOriginal(newOriginal).build()!!
result.overriddenDescriptors = this.overriddenDescriptors.map { it.createCustomCopy(copySettings) }
return result
}
@@ -36,6 +36,7 @@ fun SimpleFunctionDescriptor.createCoroutineSuspensionFunctionView(): SimpleFunc
setReturnType(returnType) setReturnType(returnType)
setOriginal(newOriginal) setOriginal(newOriginal)
setValueParameters(valueParameters.subList(0, valueParameters.size - 1)) setValueParameters(valueParameters.subList(0, valueParameters.size - 1))
setSignatureChange()
}.build()!! }.build()!!
} }
@@ -0,0 +1,41 @@
class Controller {
suspend fun suspendHere(a: String = "abc", i: Int = 2, x: Continuation<String>) {
x.resume(a + "#" + (i + 1))
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
c(Controller()).resume(Unit)
}
fun box(): String {
var result = "OK"
builder {
var a = suspendHere()
if (a != "abc#3") {
result = "fail 1: $a"
throw RuntimeException(result)
}
a = suspendHere("cde")
if (a != "cde#3") {
result = "fail 2: $a"
throw RuntimeException(result)
}
a = suspendHere(i = 6)
if (a != "abc#7") {
result = "fail 3: $a"
throw RuntimeException(result)
}
a = suspendHere("xyz", 9)
if (a != "xyz#10") {
result = "fail 4: $a"
throw RuntimeException(result)
}
}
return result
}
@@ -4111,6 +4111,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines"), Pattern.compile("^(.+)\\.kt$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("defaultParametersInSuspend.kt")
public void testDefaultParametersInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt");
doTest(fileName);
}
@TestMetadata("generate.kt") @TestMetadata("generate.kt")
public void testGenerate() throws Exception { public void testGenerate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/generate.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/generate.kt");