Simplify coroutine generation in JS backend
Stop making aliasing suspend function descriptor with reference to instance of state machine. This may cause problems in some cases, for example, when compiling recursive suspend function. See KT-17281. Instead, make alias for synthetic continuation parameter. This additionally required some refactoring, e.g. *always* generating continuation parameter during codegen.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
import kotlin.coroutines.experimental.*
|
||||
import kotlin.coroutines.experimental.intrinsics.*
|
||||
|
||||
fun box(): String {
|
||||
var result = 0
|
||||
|
||||
builder {
|
||||
result = factorial(4)
|
||||
}
|
||||
|
||||
while (postponed != null) {
|
||||
postponed!!()
|
||||
}
|
||||
|
||||
if (result != 24) return "fail1: $result"
|
||||
if (log != "1;1;2;6;24;") return "fail2: $log"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
|
||||
suspend fun factorial(a: Int): Int = if (a > 0) suspendHere(factorial(a - 1) * a) else suspendHere(1)
|
||||
|
||||
suspend fun suspendHere(value: Int): Int = suspendCoroutineOrReturn { x ->
|
||||
postponed = {
|
||||
log += "$value;"
|
||||
x.resume(value)
|
||||
}
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(handleResultContinuation {
|
||||
postponed = null
|
||||
})
|
||||
}
|
||||
|
||||
var postponed: (() -> Unit)? = { }
|
||||
|
||||
var log = ""
|
||||
+6
@@ -5114,6 +5114,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("recursiveSuspend.kt")
|
||||
public void testRecursiveSuspend() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/recursiveSuspend.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnByLabel.kt")
|
||||
public void testReturnByLabel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/returnByLabel.kt");
|
||||
|
||||
@@ -5114,6 +5114,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("recursiveSuspend.kt")
|
||||
public void testRecursiveSuspend() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/recursiveSuspend.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnByLabel.kt")
|
||||
public void testReturnByLabel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/returnByLabel.kt");
|
||||
|
||||
@@ -5114,6 +5114,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("recursiveSuspend.kt")
|
||||
public void testRecursiveSuspend() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/recursiveSuspend.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnByLabel.kt")
|
||||
public void testReturnByLabel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/returnByLabel.kt");
|
||||
|
||||
+12
-26
@@ -28,7 +28,8 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
private val innerFunction = function.getInnerFunction()
|
||||
private val functionWithBody = innerFunction ?: function
|
||||
private val body = functionWithBody.body
|
||||
private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables()).toMutableSet()
|
||||
private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables() -
|
||||
functionWithBody.parameters.last().name).toMutableSet()
|
||||
private val className = function.scope.parent.declareFreshName("Coroutine\$${name ?: "anonymous"}")
|
||||
|
||||
fun transform(): List<JsStatement> {
|
||||
@@ -61,29 +62,20 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
if (context.metadata.hasReceiver) {
|
||||
constructor.parameters += JsParameter(context.receiverFieldName)
|
||||
}
|
||||
constructor.parameters += function.parameters.map { JsParameter(it.name) }
|
||||
if (innerFunction != null) {
|
||||
constructor.parameters += innerFunction.parameters.map { JsParameter(it.name) }
|
||||
}
|
||||
|
||||
val lastParameter = function.parameters.lastOrNull()?.name
|
||||
val parameters = function.parameters + innerFunction?.parameters.orEmpty()
|
||||
constructor.parameters += parameters.map { JsParameter(it.name) }
|
||||
val lastParameter = parameters.lastOrNull()?.name
|
||||
|
||||
val controllerName = if (context.metadata.hasController) {
|
||||
function.scope.declareFreshName("controller").apply { constructor.parameters += JsParameter(this) }
|
||||
function.scope.declareFreshName("controller").apply {
|
||||
constructor.parameters.add(constructor.parameters.lastIndex, JsParameter(this))
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
|
||||
val interceptorRef = if (context.metadata.isLambda) {
|
||||
val interceptorName = function.scope.declareFreshName("interceptor")
|
||||
constructor.parameters += JsParameter(interceptorName)
|
||||
interceptorName.makeRef()
|
||||
}
|
||||
else {
|
||||
lastParameter!!.makeRef()
|
||||
}
|
||||
|
||||
val interceptorRef = lastParameter!!.makeRef()
|
||||
val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet()
|
||||
|
||||
constructor.body.statements.run {
|
||||
@@ -156,20 +148,14 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f
|
||||
if (context.metadata.hasReceiver) {
|
||||
instantiation.arguments += JsLiteral.THIS
|
||||
}
|
||||
instantiation.arguments += function.parameters.map { it.name.makeRef() }
|
||||
if (innerFunction != null) {
|
||||
instantiation.arguments += innerFunction.parameters.map { it.name.makeRef() }
|
||||
}
|
||||
val parameters = function.parameters + innerFunction?.parameters.orEmpty()
|
||||
instantiation.arguments += parameters.dropLast(1).map { it.name.makeRef() }
|
||||
|
||||
if (function.coroutineMetadata!!.hasController) {
|
||||
instantiation.arguments += JsLiteral.THIS
|
||||
}
|
||||
|
||||
if (context.metadata.isLambda) {
|
||||
val interceptorParamName = functionWithBody.scope.declareFreshName("interceptor")
|
||||
functionWithBody.parameters += JsParameter(interceptorParamName)
|
||||
instantiation.arguments += interceptorParamName.makeRef()
|
||||
}
|
||||
instantiation.arguments += parameters.last().name.makeRef()
|
||||
|
||||
val suspendedName = functionWithBody.scope.declareFreshName("suspended")
|
||||
functionWithBody.parameters += JsParameter(suspendedName)
|
||||
|
||||
@@ -5829,6 +5829,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("recursiveSuspend.kt")
|
||||
public void testRecursiveSuspend() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/recursiveSuspend.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnByLabel.kt")
|
||||
public void testReturnByLabel() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/returnByLabel.kt");
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ private fun translateFunctionCall(
|
||||
if (resolvedCall.resultingDescriptor.isSuspend) {
|
||||
if (context.isInStateMachine) {
|
||||
context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply { isSuspend = true })
|
||||
val coroutineRef = TranslationUtils.translateContinuationArgument(context, resolvedCall)
|
||||
val coroutineRef = TranslationUtils.translateContinuationArgument(context)
|
||||
return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply {
|
||||
sideEffects = SideEffectKind.DEPENDS_ON_STATE
|
||||
coroutineResult = true
|
||||
|
||||
+1
-2
@@ -21,7 +21,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
@@ -109,7 +108,7 @@ public class TranslationContext {
|
||||
}
|
||||
if (declarationDescriptor instanceof FunctionDescriptor) {
|
||||
FunctionDescriptor function = (FunctionDescriptor) declarationDescriptor;
|
||||
if (function.isSuspend() && !(function instanceof AnonymousFunctionDescriptor)) {
|
||||
if (function.isSuspend()) {
|
||||
ClassDescriptor continuationDescriptor =
|
||||
DescriptorUtilKt.findContinuationClassDescriptor(getCurrentModule(), NoLookupLocation.FROM_BACKEND);
|
||||
|
||||
|
||||
+1
-8
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters
|
||||
import org.jetbrains.kotlin.js.translate.expression.translateFunction
|
||||
import org.jetbrains.kotlin.js.translate.expression.wrapWithInlineMetadata
|
||||
import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor
|
||||
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
|
||||
import org.jetbrains.kotlin.js.translate.utils.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
|
||||
@@ -99,17 +98,11 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
|
||||
context: TranslationContext
|
||||
): JsExpression {
|
||||
val function = context.getFunctionObject(descriptor)
|
||||
var innerContext = context.newDeclaration(descriptor).translateAndAliasParameters(descriptor, function.parameters)
|
||||
val innerContext = context.newDeclaration(descriptor).translateAndAliasParameters(descriptor, function.parameters)
|
||||
|
||||
if (descriptor.isSuspend) {
|
||||
if (descriptor.requiresStateMachineTransformation(context)) {
|
||||
function.fillCoroutineMetadata(context, descriptor, hasController = false, isLambda = false)
|
||||
innerContext = innerContext.innerContextWithAliased(descriptor, JsAstUtils.stateMachineReceiver())
|
||||
}
|
||||
else {
|
||||
val continuationRef = ReferenceTranslator.translateAsValueReference(
|
||||
innerContext.continuationParameterDescriptor!!, innerContext)
|
||||
innerContext = innerContext.innerContextWithAliased(descriptor, continuationRef)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -29,6 +29,8 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext
|
||||
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator.shouldBeInlined
|
||||
import org.jetbrains.kotlin.js.translate.utils.BindingUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.requiresStateMachineTransformation
|
||||
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
||||
@@ -66,6 +68,12 @@ fun TranslationContext.translateAndAliasParameters(
|
||||
if (continuationDescriptor != null) {
|
||||
val jsParameter = JsParameter(getNameForDescriptor(continuationDescriptor))
|
||||
targetList += jsParameter
|
||||
aliases[continuationDescriptor] = if (!descriptor.requiresStateMachineTransformation(this)) {
|
||||
JsAstUtils.pureFqn(jsParameter.name, null)
|
||||
}
|
||||
else {
|
||||
JsAstUtils.stateMachineReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
return this.innerContextWithDescriptorsAliased(aliases)
|
||||
|
||||
+1
-17
@@ -45,14 +45,8 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
|
||||
val lambda = invokingContext.getFunctionObject(descriptor)
|
||||
|
||||
val aliases = mutableMapOf<DeclarationDescriptor, JsExpression>()
|
||||
if (descriptor.isCoroutineLambda) {
|
||||
aliases.put(descriptor, JsAstUtils.stateMachineReceiver())
|
||||
}
|
||||
|
||||
val functionContext = invokingContext
|
||||
.newFunctionBodyWithUsageTracker(lambda, descriptor)
|
||||
.innerContextWithDescriptorsAliased(aliases)
|
||||
.translateAndAliasParameters(descriptor, lambda.parameters)
|
||||
|
||||
descriptor.valueParameters.forEach {
|
||||
@@ -76,8 +70,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
}
|
||||
lambdaCreator.name.staticRef = lambdaCreator
|
||||
lambdaCreator.fillCoroutineMetadata(invokingContext, descriptor)
|
||||
return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext),
|
||||
invokingContext)
|
||||
return lambdaCreator.withCapturedParameters(descriptor, functionContext, invokingContext)
|
||||
}
|
||||
|
||||
lambda.name = invokingContext.getInnerNameForDescriptor(descriptor)
|
||||
@@ -112,15 +105,6 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
||||
}
|
||||
}
|
||||
|
||||
private fun CallableMemberDescriptor.wrapContextForCoroutineIfNecessary(context: TranslationContext): TranslationContext {
|
||||
return if (isCoroutineLambda) {
|
||||
context.innerContextWithDescriptorsAliased(mapOf(this to JsAstUtils.stateMachineReceiver()))
|
||||
}
|
||||
else {
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
fun JsFunction.withCapturedParameters(
|
||||
descriptor: CallableMemberDescriptor,
|
||||
context: TranslationContext,
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ class CallArgumentTranslator private constructor(
|
||||
val callableDescriptor = resolvedCall.resultingDescriptor
|
||||
if (callableDescriptor is FunctionDescriptor && callableDescriptor.isSuspend) {
|
||||
val facadeName = context().getNameForDescriptor(TranslationUtils.getCoroutineProperty(context(), "facade"))
|
||||
result.add(JsAstUtils.pureFqn(facadeName, TranslationUtils.translateContinuationArgument(context(), resolvedCall)))
|
||||
result.add(JsAstUtils.pureFqn(facadeName, TranslationUtils.translateContinuationArgument(context())))
|
||||
}
|
||||
|
||||
removeLastUndefinedArguments(result)
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.descriptors.impl.LocalVariableAccessorDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
import org.jetbrains.kotlin.js.backend.ast.*;
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator;
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer;
|
||||
import org.jetbrains.kotlin.js.translate.context.TemporaryConstVariable;
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
||||
@@ -38,7 +37,6 @@ import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
|
||||
import org.jetbrains.kotlin.serialization.deserialization.FindClassInModuleKt;
|
||||
@@ -81,7 +79,7 @@ public final class TranslationUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getAccessorFunctionName(@NotNull FunctionDescriptor descriptor) {
|
||||
private static String getAccessorFunctionName(@NotNull FunctionDescriptor descriptor) {
|
||||
boolean isGetter = descriptor instanceof PropertyGetterDescriptor || descriptor instanceof LocalVariableAccessorDescriptor.Getter;
|
||||
return isGetter ? "get" : "set";
|
||||
}
|
||||
@@ -227,12 +225,6 @@ public final class TranslationUtils {
|
||||
return Translation.translateAsExpression(left, context, block);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression translateRightExpression(@NotNull TranslationContext context,
|
||||
@NotNull KtBinaryExpression expression) {
|
||||
return translateRightExpression(context, expression, context.dynamicContext().jsBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression translateRightExpression(
|
||||
@NotNull TranslationContext context,
|
||||
@@ -350,19 +342,13 @@ public final class TranslationUtils {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression translateContinuationArgument(@NotNull TranslationContext context, @NotNull ResolvedCall<?> resolvedCall) {
|
||||
CallableDescriptor continuationDescriptor =
|
||||
context.bindingContext().get(BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.getCall());
|
||||
|
||||
if (continuationDescriptor == null) {
|
||||
continuationDescriptor = getEnclosingContinuationParameter(context);
|
||||
}
|
||||
|
||||
public static JsExpression translateContinuationArgument(@NotNull TranslationContext context) {
|
||||
CallableDescriptor continuationDescriptor = getEnclosingContinuationParameter(context);
|
||||
return ReferenceTranslator.translateAsValueReference(continuationDescriptor, context);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static VariableDescriptor getEnclosingContinuationParameter(@NotNull TranslationContext context) {
|
||||
private static VariableDescriptor getEnclosingContinuationParameter(@NotNull TranslationContext context) {
|
||||
VariableDescriptor result = context.getContinuationParameterDescriptor();
|
||||
if (result == null) {
|
||||
assert context.getParent() != null;
|
||||
@@ -395,13 +381,6 @@ public final class TranslationUtils {
|
||||
.iterator().next();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionDescriptor getCoroutineResumeFunction(@NotNull TranslationContext context) {
|
||||
return getCoroutineBaseClass(context).getUnsubstitutedMemberScope()
|
||||
.getContributedFunctions(Name.identifier("resume"), NoLookupLocation.FROM_DESERIALIZATION)
|
||||
.iterator().next();
|
||||
}
|
||||
|
||||
public static boolean isOverridableFunctionWithDefaultParameters(@NotNull FunctionDescriptor descriptor) {
|
||||
return DescriptorUtilsKt.hasOrInheritsParametersWithDefaultValue(descriptor) &&
|
||||
!(descriptor instanceof ConstructorDescriptor) &&
|
||||
|
||||
Reference in New Issue
Block a user