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.coroutines.CoroutineCodegen;
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.inline.*;
import org.jetbrains.kotlin.codegen.intrinsics.*;
@@ -1665,26 +1666,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
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(
resolvedCall.getValueArgumentsByIndex().get(1).getArguments().get(0).getArgumentExpression(),
StackValue.thisOrOuter(this, coroutineClassDescriptor, false, false));
genCoroutineInstanceValueFromResolvedCall(resolvedCall));
return invokeFunction(resolvedCall, StackValue.none());
}
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
private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) {
Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor);
@@ -2477,6 +2483,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@NotNull
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);
ClassDescriptor superCallTarget = getSuperCallTarget(call);
@@ -198,9 +198,7 @@ class CoroutineTransformerMethodVisitor(
private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
val call = suspension.suspensionCall
val method = Method(call.name, call.desc)
val newParameters = method.argumentTypes + CONTINUATION_INTERFACE_ASM_TYPE
call.desc = Method(method.name, Type.VOID_TYPE, newParameters).descriptor
call.desc = Method(method.name, Type.VOID_TYPE, method.argumentTypes).descriptor
val continuationLabel = LabelNode()
@@ -213,9 +211,6 @@ class CoroutineTransformerMethodVisitor(
FieldInsnNode(
Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor)))
// Pass continuation
insertBefore(call, VarInsnNode(Opcodes.ALOAD, 0))
val nextInsnAfterCall = call.next
// Exit
@@ -16,11 +16,20 @@
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.coroutines.CONTINUATION_INTERFACE_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.Type
val CONTINUATION_INTERFACE_ASM_TYPE = Type.getObjectType(JvmClassName.byFqNameWithoutInnerClasses(CONTINUATION_INTERFACE_FQ_NAME).internalName)
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
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
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_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)
setOriginal(newOriginal)
setValueParameters(valueParameters.subList(0, valueParameters.size - 1))
setSignatureChange()
}.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);
}
@TestMetadata("defaultParametersInSuspend.kt")
public void testDefaultParametersInSuspend() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/defaultParametersInSuspend.kt");
doTest(fileName);
}
@TestMetadata("generate.kt")
public void testGenerate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/generate.kt");