JS: support stack unwinding convention in coroutines
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
// CHECK_NOT_CALLED: suspendInline_die06n$
|
// CHECK_NOT_CALLED: suspendInline_61zpoe$
|
||||||
// CHECK_NOT_CALLED: suspendInline_nesahw$
|
// CHECK_NOT_CALLED: suspendInline_6r51u9$
|
||||||
// CHECK_NOT_CALLED: suspendInline_grpnnl$
|
// CHECK_NOT_CALLED: suspendInline
|
||||||
class Controller {
|
class Controller {
|
||||||
fun withValue(v: String, x: Continuation<String>) {
|
fun withValue(v: String, x: Continuation<String>) {
|
||||||
x.resume(v)
|
x.resume(v)
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
// CHECK_NOT_CALLED: suspendInline_die06n$
|
// CHECK_NOT_CALLED: suspendInline_61zpoe$
|
||||||
// CHECK_NOT_CALLED: suspendInline_nesahw$
|
// CHECK_NOT_CALLED: suspendInline_6r51u9$
|
||||||
// CHECK_NOT_CALLED: suspendInline_grpnnl$
|
// CHECK_NOT_CALLED: suspendInline
|
||||||
class Controller {
|
class Controller {
|
||||||
suspend inline fun suspendInline(v: String): String = v
|
suspend inline fun suspendInline(v: String): String = v
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS
|
|
||||||
class Controller {
|
class Controller {
|
||||||
suspend fun suspendHere(): Int = suspendWithCurrentContinuation { x ->
|
suspend fun suspendHere(): Int = suspendWithCurrentContinuation { x ->
|
||||||
1
|
1
|
||||||
|
|||||||
+2
@@ -103,6 +103,8 @@ var JsNameRef.coroutineResult by MetadataProperty(default = false)
|
|||||||
*/
|
*/
|
||||||
var JsNameRef.coroutineController by MetadataProperty(default = false)
|
var JsNameRef.coroutineController by MetadataProperty(default = false)
|
||||||
|
|
||||||
|
var JsFunction.suspendObjectRef: JsExpression? by MetadataProperty(default = null)
|
||||||
|
|
||||||
var JsFunction.continuationInterfaceRef: JsExpression? by MetadataProperty(default = null)
|
var JsFunction.continuationInterfaceRef: JsExpression? by MetadataProperty(default = null)
|
||||||
|
|
||||||
var JsName.imported by MetadataProperty(default = false)
|
var JsName.imported by MetadataProperty(default = false)
|
||||||
|
|||||||
@@ -361,7 +361,18 @@ class CoroutineBodyTransformer(
|
|||||||
|
|
||||||
private fun handleSuspend(invocation: JsInvocation) {
|
private fun handleSuspend(invocation: JsInvocation) {
|
||||||
val invokeExpression = if (invocation.isFakeSuspend) invocation.arguments.getOrNull(0) else invocation
|
val invokeExpression = if (invocation.isFakeSuspend) invocation.arguments.getOrNull(0) else invocation
|
||||||
currentStatements += JsReturn(invokeExpression)
|
val suspendObjectVar = context.suspendObjectVar
|
||||||
|
val statements = if (invokeExpression == null || suspendObjectVar == null) {
|
||||||
|
listOf(JsReturn(invokeExpression))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val resultRef = JsNameRef(context.resultFieldName, JsLiteral.THIS).apply { sideEffects = SideEffectKind.DEPENDS_ON_STATE }
|
||||||
|
val invocationStatement = JsAstUtils.assignment(resultRef, invokeExpression).makeStmt()
|
||||||
|
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), JsAstUtils.pureFqn(suspendObjectVar, null))
|
||||||
|
val suspendIfNeeded = JsIf(suspendCondition, JsReturn())
|
||||||
|
listOf(invocationStatement, suspendIfNeeded, JsBreak())
|
||||||
|
}
|
||||||
|
currentStatements += statements
|
||||||
currentBlock = suspendTarget!!
|
currentBlock = suspendTarget!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -48,7 +48,7 @@ class CoroutineFunctionTransformer(
|
|||||||
function.scope.declareName(throwId)
|
function.scope.declareName(throwId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = CoroutineTransformationContext(function.scope)
|
val context = CoroutineTransformationContext(function.scope, function.suspendObjectRef != null)
|
||||||
val bodyTransformer = CoroutineBodyTransformer(program, context, throwName)
|
val bodyTransformer = CoroutineBodyTransformer(program, context, throwName)
|
||||||
bodyTransformer.preProcess(body)
|
bodyTransformer.preProcess(body)
|
||||||
body.statements.forEach { it.accept(bodyTransformer) }
|
body.statements.forEach { it.accept(bodyTransformer) }
|
||||||
@@ -87,15 +87,15 @@ class CoroutineFunctionTransformer(
|
|||||||
val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet()
|
val parameterNames = (function.parameters.map { it.name } + innerFunction?.parameters?.map { it.name }.orEmpty()).toSet()
|
||||||
|
|
||||||
constructor.body.statements.run {
|
constructor.body.statements.run {
|
||||||
assign(context.stateFieldName, program.getNumberLiteral(0))
|
assignToField(context.stateFieldName, program.getNumberLiteral(0))
|
||||||
assign(context.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex))
|
assignToField(context.exceptionStateName, program.getNumberLiteral(globalCatchBlockIndex))
|
||||||
if (hasFinallyBlocks) {
|
if (hasFinallyBlocks) {
|
||||||
assign(context.finallyPathFieldName, JsLiteral.NULL)
|
assignToField(context.finallyPathFieldName, JsLiteral.NULL)
|
||||||
}
|
}
|
||||||
assign(context.controllerFieldName, controllerName.makeRef())
|
assignToField(context.controllerFieldName, controllerName.makeRef())
|
||||||
for (localVariable in localVariables) {
|
for (localVariable in localVariables) {
|
||||||
val value = if (localVariable !in parameterNames) JsLiteral.NULL else localVariable.makeRef()
|
val value = if (localVariable !in parameterNames) JsLiteral.NULL else localVariable.makeRef()
|
||||||
assign(function.scope.getFieldName(localVariable), value)
|
assignToField(function.scope.getFieldName(localVariable), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,12 @@ class CoroutineFunctionTransformer(
|
|||||||
functionWithBody.body.statements.clear()
|
functionWithBody.body.statements.clear()
|
||||||
|
|
||||||
resumeFunction.body.statements.apply {
|
resumeFunction.body.statements.apply {
|
||||||
assign(context.resultFieldName, resumeParameter.makeRef())
|
assignToField(context.resultFieldName, resumeParameter.makeRef())
|
||||||
|
if (context.suspendObjectVar != null) {
|
||||||
|
add(JsAstUtils.newVar(context.suspendObjectVar!!, function.suspendObjectRef!!.deepCopy()).apply {
|
||||||
|
synthetic = true
|
||||||
|
})
|
||||||
|
}
|
||||||
this += coroutineBody
|
this += coroutineBody
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +254,7 @@ class CoroutineFunctionTransformer(
|
|||||||
return functions.mapNotNull { it as? FunctionDescriptor }.firstOrNull { it.kind.isReal }
|
return functions.mapNotNull { it as? FunctionDescriptor }.firstOrNull { it.kind.isReal }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<JsStatement>.assign(fieldName: JsName, value: JsExpression) {
|
private fun MutableList<JsStatement>.assignToField(fieldName: JsName, value: JsExpression) {
|
||||||
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt()
|
this += JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), value).makeStmt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.js.coroutine
|
|||||||
|
|
||||||
import com.google.dart.compiler.backend.js.ast.JsScope
|
import com.google.dart.compiler.backend.js.ast.JsScope
|
||||||
|
|
||||||
class CoroutineTransformationContext(private val scope: JsScope) {
|
class CoroutineTransformationContext(private val scope: JsScope, private val stackUnwinding: Boolean) {
|
||||||
val entryBlock = CoroutineBlock()
|
val entryBlock = CoroutineBlock()
|
||||||
val globalCatchBlock = CoroutineBlock()
|
val globalCatchBlock = CoroutineBlock()
|
||||||
val resultFieldName by lazy { scope.declareFreshName("\$result") }
|
val resultFieldName by lazy { scope.declareFreshName("\$result") }
|
||||||
@@ -27,4 +27,5 @@ class CoroutineTransformationContext(private val scope: JsScope) {
|
|||||||
val controllerFieldName by lazy { scope.declareFreshName("\$controller") }
|
val controllerFieldName by lazy { scope.declareFreshName("\$controller") }
|
||||||
val exceptionStateName by lazy { scope.declareFreshName("\$exceptionState") }
|
val exceptionStateName by lazy { scope.declareFreshName("\$exceptionState") }
|
||||||
val finallyPathFieldName by lazy { scope.declareFreshName("\$finallyPath") }
|
val finallyPathFieldName by lazy { scope.declareFreshName("\$finallyPath") }
|
||||||
|
val suspendObjectVar by lazy { if (stackUnwinding) scope.declareFreshName("\$suspendObject") else null }
|
||||||
}
|
}
|
||||||
+1
-7
@@ -5786,13 +5786,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
@TestMetadata("suspendInCycle.kt")
|
@TestMetadata("suspendInCycle.kt")
|
||||||
public void testSuspendInCycle() throws Exception {
|
public void testSuspendInCycle() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/stackUnwinding/suspendInCycle.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/stackUnwinding/suspendInCycle.kt");
|
||||||
try {
|
doTest(fileName);
|
||||||
doTest(fileName);
|
|
||||||
}
|
|
||||||
catch (Throwable ignore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-6
@@ -16,9 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.translate.callTranslator
|
package org.jetbrains.kotlin.js.translate.callTranslator
|
||||||
|
|
||||||
import com.google.dart.compiler.backend.js.ast.JsExpression
|
import com.google.dart.compiler.backend.js.ast.*
|
||||||
import com.google.dart.compiler.backend.js.ast.JsInvocation
|
|
||||||
import com.google.dart.compiler.backend.js.ast.JsNameRef
|
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.*
|
import com.google.dart.compiler.backend.js.ast.metadata.*
|
||||||
import org.jetbrains.kotlin.backend.common.getBuiltInSuspendWithCurrentContinuation
|
import org.jetbrains.kotlin.backend.common.getBuiltInSuspendWithCurrentContinuation
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
@@ -35,7 +33,6 @@ import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
|
|||||||
import org.jetbrains.kotlin.js.translate.utils.setInlineCallMetadata
|
import org.jetbrains.kotlin.js.translate.utils.setInlineCallMetadata
|
||||||
import org.jetbrains.kotlin.psi.Call.CallType
|
import org.jetbrains.kotlin.psi.Call.CallType
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides
|
import org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides
|
||||||
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnVariable
|
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnVariable
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
@@ -160,12 +157,13 @@ private fun translateFunctionCall(
|
|||||||
return callExpression
|
return callExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateCallWithContinuation(context: TranslationContext,resolvedCall: ResolvedCall<out FunctionDescriptor>): JsExpression {
|
private fun translateCallWithContinuation(context: TranslationContext, resolvedCall: ResolvedCall<out FunctionDescriptor>): JsExpression {
|
||||||
val arguments = CallArgumentTranslator.translate(resolvedCall, null, context)
|
val arguments = CallArgumentTranslator.translate(resolvedCall, null, context)
|
||||||
val coroutineArgument = TranslationUtils.getEnclosingContinuationParameter(context)
|
val coroutineArgument = TranslationUtils.getEnclosingContinuationParameter(context)
|
||||||
val invocation = JsInvocation(arguments.valueArguments[0], ReferenceTranslator.translateAsValueReference(coroutineArgument, context))
|
val invocation = JsInvocation(arguments.valueArguments[0], ReferenceTranslator.translateAsValueReference(coroutineArgument, context))
|
||||||
invocation.inlineStrategy = InlineStrategy.IN_PLACE
|
invocation.inlineStrategy = InlineStrategy.IN_PLACE
|
||||||
return invocation
|
context.currentBlock.statements += JsReturn(invocation)
|
||||||
|
return JsLiteral.NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun computeExplicitReceiversForInvoke(
|
fun computeExplicitReceiversForInvoke(
|
||||||
|
|||||||
+15
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateF
|
|||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction
|
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
|
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
|
||||||
import org.jetbrains.kotlin.psi.KtParameter
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
@@ -42,6 +43,10 @@ import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
|||||||
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
||||||
|
|
||||||
class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslator(context) {
|
class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslator(context) {
|
||||||
|
companion object {
|
||||||
|
private val SUSPEND_FQ_NAME = "kotlin.coroutines.Suspend"
|
||||||
|
}
|
||||||
|
|
||||||
fun translate(
|
fun translate(
|
||||||
declaration: KtDeclarationWithBody,
|
declaration: KtDeclarationWithBody,
|
||||||
continuationType: ClassDescriptor? = null,
|
continuationType: ClassDescriptor? = null,
|
||||||
@@ -83,6 +88,14 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
|||||||
invokingContext.getInnerNameForDescriptor(descriptor)
|
invokingContext.getInnerNameForDescriptor(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val suspendObjectRef = if (descriptor.isCoroutineLambda && KotlinBuiltIns.isUnit(descriptor.returnType!!)) {
|
||||||
|
val suspendObjectDescriptor = context().currentModule.builtIns.getBuiltInClassByFqName(FqName(SUSPEND_FQ_NAME))
|
||||||
|
ReferenceTranslator.translateAsValueReference(suspendObjectDescriptor, context())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
if (tracker.hasCapturedExceptContaining()) {
|
if (tracker.hasCapturedExceptContaining()) {
|
||||||
val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda)
|
val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda)
|
||||||
lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor)
|
lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor)
|
||||||
@@ -94,6 +107,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
|||||||
}
|
}
|
||||||
lambdaCreator.name.staticRef = lambdaCreator
|
lambdaCreator.name.staticRef = lambdaCreator
|
||||||
lambdaCreator.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
lambdaCreator.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
||||||
|
lambdaCreator.suspendObjectRef = suspendObjectRef
|
||||||
return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext),
|
return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext),
|
||||||
invokingContext)
|
invokingContext)
|
||||||
}
|
}
|
||||||
@@ -105,6 +119,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
|
|||||||
invokingContext.addDeclarationStatement(lambda.makeStmt())
|
invokingContext.addDeclarationStatement(lambda.makeStmt())
|
||||||
lambda.name.staticRef = lambda
|
lambda.name.staticRef = lambda
|
||||||
lambda.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
lambda.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference()
|
||||||
|
lambda.suspendObjectRef = suspendObjectRef
|
||||||
return getReferenceToLambda(invokingContext, descriptor, lambda.name)
|
return getReferenceToLambda(invokingContext, descriptor, lambda.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -112,7 +112,7 @@ public final class ReferenceTranslator {
|
|||||||
private static JsExpression getLazyReferenceToObject(@NotNull ClassDescriptor descriptor, @NotNull TranslationContext context) {
|
private static JsExpression getLazyReferenceToObject(@NotNull ClassDescriptor descriptor, @NotNull TranslationContext context) {
|
||||||
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
||||||
JsExpression qualifier = context.getInnerReference(container);
|
JsExpression qualifier = context.getInnerReference(container);
|
||||||
return JsAstUtils.pureFqn(context.getNameForDescriptor(descriptor), qualifier);
|
return new JsNameRef(context.getNameForDescriptor(descriptor), qualifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) {
|
private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user