diff --git a/.idea/dictionaries/svyatoslav_kuzmich.xml b/.idea/dictionaries/svyatoslav_kuzmich.xml index c5c5f0f09d1..e53219b6c9b 100644 --- a/.idea/dictionaries/svyatoslav_kuzmich.xml +++ b/.idea/dictionaries/svyatoslav_kuzmich.xml @@ -18,6 +18,7 @@ sqrt testsuite uninstantiable + unintercepted unlinkable vtable wabt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 61c4b2778c6..67270e3b369 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -94,9 +94,10 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner } - JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction -> + // TODO: Generate calls to main as IR->IR lowering and reference coroutineEmptyContinuation directly + JsMainFunctionDetector(context).getMainFunctionOrNull(modules.last())?.let { mainFunction -> rootDeclarations += mainFunction - if (mainFunction.isSuspend) { + if (mainFunction.isLoweredSuspendFunction(context)) { rootDeclarations += context.coroutineEmptyContinuation.owner } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt index 8ce110f8881..8801ab9557c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt @@ -5,22 +5,119 @@ package org.jetbrains.kotlin.ir.backend.js +import org.jetbrains.kotlin.backend.common.BackendContext import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.atMostOne +import org.jetbrains.kotlin.backend.common.ir.Symbols import org.jetbrains.kotlin.backend.common.ir.isOverridableOrOverrides +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.backend.js.utils.isDispatchReceiver -import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.ir.util.getPropertyGetter +import org.jetbrains.kotlin.ir.util.getPropertySetter +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.scopes.MemberScope interface JsCommonBackendContext : CommonBackendContext { override val mapping: JsMapping val inlineClassesUtils: InlineClassesUtils + val coroutineSymbols: JsCommonCoroutineSymbols + + val catchAllThrowableType: IrType + get() = irBuiltIns.throwableType + val es6mode: Boolean get() = false } +// TODO: investigate if it could be removed +internal fun BackendContext.lazy2(fn: () -> T) = lazy { irFactory.stageController.withInitialIr(fn) } + +class JsCommonCoroutineSymbols( + symbolTable: SymbolTable, + module: ModuleDescriptor, + val context: JsCommonBackendContext +) { + val coroutinePackage = module.getPackage(COROUTINE_PACKAGE_FQNAME) + val coroutineIntrinsicsPackage = module.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQNAME) + + val coroutineImpl = + symbolTable.referenceClass(findClass(coroutinePackage.memberScope, COROUTINE_IMPL_NAME)) + + val coroutineImplLabelPropertyGetter by context.lazy2 { coroutineImpl.getPropertyGetter("state")!!.owner } + val coroutineImplLabelPropertySetter by context.lazy2 { coroutineImpl.getPropertySetter("state")!!.owner } + val coroutineImplResultSymbolGetter by context.lazy2 { coroutineImpl.getPropertyGetter("result")!!.owner } + val coroutineImplResultSymbolSetter by context.lazy2 { coroutineImpl.getPropertySetter("result")!!.owner } + val coroutineImplExceptionPropertyGetter by context.lazy2 { coroutineImpl.getPropertyGetter("exception")!!.owner } + val coroutineImplExceptionPropertySetter by context.lazy2 { coroutineImpl.getPropertySetter("exception")!!.owner } + val coroutineImplExceptionStatePropertyGetter by context.lazy2 { coroutineImpl.getPropertyGetter("exceptionState")!!.owner } + val coroutineImplExceptionStatePropertySetter by context.lazy2 { coroutineImpl.getPropertySetter("exceptionState")!!.owner } + + val continuationClass = symbolTable.referenceClass( + coroutinePackage.memberScope.getContributedClassifier( + CONTINUATION_NAME, + NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor + ) + + val coroutineSuspendedGetter = symbolTable.referenceSimpleFunction( + coroutineIntrinsicsPackage.memberScope.getContributedVariables( + COROUTINE_SUSPENDED_NAME, + NoLookupLocation.FROM_BACKEND + ).filterNot { it.isExpect }.single().getter!! + ) + + val coroutineGetContext: IrSimpleFunctionSymbol + get() { + val contextGetter = + continuationClass.owner.declarations.filterIsInstance() + .atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME } + ?: continuationClass.owner.declarations.filterIsInstance() + .atMostOne { it.name == CONTINUATION_CONTEXT_PROPERTY_NAME }?.getter!! + return contextGetter.symbol + } + + val coroutineContextProperty: PropertyDescriptor + get() { + val vars = coroutinePackage.memberScope.getContributedVariables( + COROUTINE_CONTEXT_NAME, + NoLookupLocation.FROM_BACKEND + ) + return vars.single() + } + + companion object { + private val INTRINSICS_PACKAGE_NAME = Name.identifier("intrinsics") + private val COROUTINE_SUSPENDED_NAME = Name.identifier("COROUTINE_SUSPENDED") + private val COROUTINE_CONTEXT_NAME = Name.identifier("coroutineContext") + private val COROUTINE_IMPL_NAME = Name.identifier("CoroutineImpl") + private val CONTINUATION_NAME = Name.identifier("Continuation") + private val CONTINUATION_CONTEXT_GETTER_NAME = Name.special("") + private val CONTINUATION_CONTEXT_PROPERTY_NAME = Name.identifier("context") + private val COROUTINE_PACKAGE_FQNAME = FqName.fromSegments(listOf("kotlin", "coroutines")) + private val COROUTINE_INTRINSICS_PACKAGE_FQNAME = COROUTINE_PACKAGE_FQNAME.child(INTRINSICS_PACKAGE_NAME) + } +} + +internal fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor = + memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor + +internal fun findFunctions(memberScope: MemberScope, name: Name): List = + memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList() + interface InlineClassesUtils { fun isTypeInlined(type: IrType): Boolean diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index a0cd7334b8a..d44ede3db36 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -144,23 +144,11 @@ class JsIrBackendContext( companion object { val KOTLIN_PACKAGE_FQN = FqName.fromSegments(listOf("kotlin")) - private val INTRINSICS_PACKAGE_NAME = Name.identifier("intrinsics") - private val COROUTINE_SUSPENDED_NAME = Name.identifier("COROUTINE_SUSPENDED") - private val COROUTINE_CONTEXT_NAME = Name.identifier("coroutineContext") - private val COROUTINE_IMPL_NAME = Name.identifier("CoroutineImpl") - private val CONTINUATION_NAME = Name.identifier("Continuation") // TODO: what is more clear way reference this getter? - private val CONTINUATION_CONTEXT_GETTER_NAME = Name.special("") - - private val CONTINUATION_CONTEXT_PROPERTY_NAME = Name.identifier("context") private val REFLECT_PACKAGE_FQNAME = KOTLIN_PACKAGE_FQN.child(Name.identifier("reflect")) private val JS_PACKAGE_FQNAME = KOTLIN_PACKAGE_FQN.child(Name.identifier("js")) private val JS_INTERNAL_PACKAGE_FQNAME = JS_PACKAGE_FQNAME.child(Name.identifier("internal")) - private val COROUTINE_PACKAGE_FQNAME_12 = FqName.fromSegments(listOf("kotlin", "coroutines", "experimental")) - private val COROUTINE_PACKAGE_FQNAME_13 = FqName.fromSegments(listOf("kotlin", "coroutines")) - private val COROUTINE_PACKAGE_FQNAME = COROUTINE_PACKAGE_FQNAME_13 - private val COROUTINE_INTRINSICS_PACKAGE_FQNAME = COROUTINE_PACKAGE_FQNAME.child(INTRINSICS_PACKAGE_NAME) // TODO: due to name clash those weird suffix is required, remove it once `MemberNameGenerator` is implemented private val COROUTINE_SUSPEND_OR_RETURN_JS_NAME = "suspendCoroutineUninterceptedOrReturnJS" @@ -171,12 +159,13 @@ class JsIrBackendContext( private val internalPackage = module.getPackage(JS_PACKAGE_FQNAME) - private val coroutinePackage = module.getPackage(COROUTINE_PACKAGE_FQNAME) - private val coroutineIntrinsicsPackage = module.getPackage(COROUTINE_INTRINSICS_PACKAGE_FQNAME) val dynamicType: IrDynamicType = IrDynamicTypeImpl(null, emptyList(), Variance.INVARIANT) val intrinsics = JsIntrinsics(irBuiltIns, this) + override val catchAllThrowableType: IrType + get() = dynamicType + override val sharedVariablesManager = JsSharedVariablesManager(this) override val internalPackageFqn = JS_PACKAGE_FQNAME @@ -193,6 +182,9 @@ class JsIrBackendContext( fun getOperatorByName(name: Name, type: IrSimpleType) = operatorMap[name]?.get(type.classifier) + override val coroutineSymbols = + JsCommonCoroutineSymbols(symbolTable, module, this) + override val ir = object : Ir(this, irModuleFragment) { override val symbols = object : Symbols(this@JsIrBackendContext, irBuiltIns, symbolTable) { override val throwNullPointerException = @@ -220,14 +212,9 @@ class JsIrBackendContext( override val stringBuilder get() = TODO("not implemented") override val coroutineImpl = - symbolTable.referenceClass(findClass(coroutinePackage.memberScope, COROUTINE_IMPL_NAME)) + coroutineSymbols.coroutineImpl override val coroutineSuspendedGetter = - symbolTable.referenceSimpleFunction( - coroutineIntrinsicsPackage.memberScope.getContributedVariables( - COROUTINE_SUSPENDED_NAME, - NoLookupLocation.FROM_BACKEND - ).filterNot { it.isExpect }.single().getter!! - ) + coroutineSymbols.coroutineSuspendedGetter private val _arraysContentEquals = getFunctions(FqName("kotlin.collections.contentEquals")).mapNotNull { if (it.extensionReceiverParameter != null && it.extensionReceiverParameter!!.type.isNullable()) @@ -241,7 +228,7 @@ class JsIrBackendContext( override val getContinuation = symbolTable.referenceSimpleFunction(getJsInternalFunction("getContinuation")) - override val coroutineContextGetter = symbolTable.referenceSimpleFunction(context.coroutineContextProperty.getter!!) + override val coroutineContextGetter = symbolTable.referenceSimpleFunction(context.coroutineSymbols.coroutineContextProperty.getter!!) override val suspendCoroutineUninterceptedOrReturn = symbolTable.referenceSimpleFunction(getJsInternalFunction(COROUTINE_SUSPEND_OR_RETURN_JS_NAME)) @@ -308,31 +295,10 @@ class JsIrBackendContext( getIrClass(JS_INTERNAL_PACKAGE_FQNAME.child(Name.identifier("${it.identifier}CompanionObject"))) } - val coroutineImpl = ir.symbols.coroutineImpl - val continuationClass = symbolTable.referenceClass( - coroutinePackage.memberScope.getContributedClassifier( - CONTINUATION_NAME, - NoLookupLocation.FROM_BACKEND - ) as ClassDescriptor - ) // Top-level functions forced to be loaded - val coroutineSuspendOrReturn = ir.symbols.suspendCoroutineUninterceptedOrReturn - val coroutineSuspendGetter = ir.symbols.coroutineSuspendedGetter - val coroutineGetContext: IrSimpleFunctionSymbol - get() { - val contextGetter = - continuationClass.owner.declarations.filterIsInstance() - .atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME } - ?: continuationClass.owner.declarations.filterIsInstance() - .atMostOne { it.name == CONTINUATION_CONTEXT_PROPERTY_NAME }?.getter!! - return contextGetter.symbol - } - - val coroutineGetContextJs - get() = ir.symbols.coroutineGetContext val coroutineEmptyContinuation = symbolTable.referenceProperty( getProperty( @@ -348,14 +314,6 @@ class JsIrBackendContext( ) ) - val coroutineContextProperty: PropertyDescriptor - get() { - val vars = coroutinePackage.memberScope.getContributedVariables( - COROUTINE_CONTEXT_NAME, - NoLookupLocation.FROM_BACKEND - ) - return vars.single() - } val newThrowableSymbol = symbolTable.referenceSimpleFunction(getJsInternalFunction("newThrowable")) val extendThrowableSymbol = symbolTable.referenceSimpleFunction(getJsInternalFunction("extendThrowable")) @@ -368,15 +326,6 @@ class JsIrBackendContext( val suiteFun = getFunctions(FqName("kotlin.test.suite")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) } val testFun = getFunctions(FqName("kotlin.test.test")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) } - val coroutineImplLabelPropertyGetter by lazy2 { ir.symbols.coroutineImpl.getPropertyGetter("state")!!.owner } - val coroutineImplLabelPropertySetter by lazy2 { ir.symbols.coroutineImpl.getPropertySetter("state")!!.owner } - val coroutineImplResultSymbolGetter by lazy2 { ir.symbols.coroutineImpl.getPropertyGetter("result")!!.owner } - val coroutineImplResultSymbolSetter by lazy2 { ir.symbols.coroutineImpl.getPropertySetter("result")!!.owner } - val coroutineImplExceptionPropertyGetter by lazy2 { ir.symbols.coroutineImpl.getPropertyGetter("exception")!!.owner } - val coroutineImplExceptionPropertySetter by lazy2 { ir.symbols.coroutineImpl.getPropertySetter("exception")!!.owner } - val coroutineImplExceptionStatePropertyGetter by lazy2 { ir.symbols.coroutineImpl.getPropertyGetter("exceptionState")!!.owner } - val coroutineImplExceptionStatePropertySetter by lazy2 { ir.symbols.coroutineImpl.getPropertySetter("exceptionState")!!.owner } - val primitiveClassProperties by lazy2 { primitiveClassesObject.owner.declarations.filterIsInstance() } @@ -407,12 +356,6 @@ class JsIrBackendContext( }.toMap() } - private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor = - memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor - - private fun findFunctions(memberScope: MemberScope, name: Name): List = - memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList() - private fun findProperty(memberScope: MemberScope, name: Name): List = memberScope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND).toList() @@ -442,7 +385,4 @@ class JsIrBackendContext( /*TODO*/ print(message) } - - // TODO: investigate if it could be removed - fun lazy2(fn: () -> T) = lazy { irFactory.stageController.withInitialIr(fn) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index e6a94d26f33..dcb62961788 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -19,8 +19,7 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.backend.js.lower.* import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering import org.jetbrains.kotlin.ir.backend.js.lower.cleanup.CleanupLowering -import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendArityStoreLowering -import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering +import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.* import org.jetbrains.kotlin.ir.backend.js.lower.inline.* import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -474,6 +473,29 @@ private val suspendFunctionsLoweringPhase = makeBodyLoweringPhase( description = "Transform suspend functions into CoroutineImpl instance and build state machine" ) +private val addContinuationToNonLocalSuspendFunctionsLoweringPhase = makeDeclarationTransformerPhase( + ::AddContinuationToNonLocalSuspendFunctionsLowering, + name = "AddContinuationToNonLocalSuspendFunctionsLowering", + description = "Add explicit continuation as last parameter of non-local suspend functions" +) + +private val addContinuationToLocalSuspendFunctionsLoweringPhase = makeBodyLoweringPhase( + ::AddContinuationToLocalSuspendFunctionsLowering, + name = "AddContinuationToLocalSuspendFunctionsLowering", + description = "Add explicit continuation as last parameter of local suspend functions" +) + + +private val addContinuationToFunctionCallsLoweringPhase = makeBodyLoweringPhase( + ::AddContinuationToFunctionCallsLowering, + name = "AddContinuationToFunctionCallsLowering", + description = "Replace suspend function calls with calls with continuation", + prerequisite = setOf( + addContinuationToLocalSuspendFunctionsLoweringPhase, + addContinuationToNonLocalSuspendFunctionsLoweringPhase, + ) +) + private val privateMembersLoweringPhase = makeDeclarationTransformerPhase( ::PrivateMembersLowering, name = "PrivateMembersLowering", @@ -812,6 +834,10 @@ private val loweringList = listOf( suspendFunctionsLoweringPhase, propertyReferenceLoweringPhase, interopCallableReferenceLoweringPhase, + jsSuspendArityStorePhase, + addContinuationToNonLocalSuspendFunctionsLoweringPhase, + addContinuationToLocalSuspendFunctionsLoweringPhase, + addContinuationToFunctionCallsLoweringPhase, returnableBlockLoweringPhase, rangeContainsLoweringPhase, forLoopsLoweringPhase, @@ -855,7 +881,6 @@ private val loweringList = listOf( callsLoweringPhase, cleanupLoweringPhase, validateIrAfterLowering, - jsSuspendArityStorePhase ) // TODO comment? Eliminate ModuleLowering's? Don't filter them here? diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionCallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionCallsLowering.kt new file mode 100644 index 00000000000..2ccda86fca1 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionCallsLowering.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.lower.coroutines + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.ir.isSuspend +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.runOnFilePostfix +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.ir.util.irCall +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +/** + * Add continuation to suspend function calls. Requires [AddContinuationToLocalSuspendFunctionsLowering] and + * [AddContinuationToNonLocalSuspendFunctionsLowering] to transform function declarations first. + * + * Additionally materialize continuation for `getContinuation` intrinsic calls. + */ +class AddContinuationToFunctionCallsLowering(val context: JsCommonBackendContext) : BodyLoweringPass { + override fun lower(irFile: IrFile) { + runOnFilePostfix(irFile, withLocalDeclarations = true) + } + + override fun lower(irBody: IrBody, container: IrDeclaration) { + val continuation: IrValueParameter by lazy { + val function = container as IrSimpleFunction + if (function.overriddenSymbols + .any { it.owner.name.asString() == "doResume" && it.owner.parent == context.coroutineSymbols.coroutineImpl.owner } + ) { + function.dispatchReceiverParameter!! + } else { + function.valueParameters.last() + } + } + + val builder by lazy { context.createIrBuilder(container.symbol) } + fun getContinuation() = builder.irGet(continuation) + + irBody.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitBody(body: IrBody): IrBody { + // Nested bodies are covered by separate `lower` invocation + return body + } + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid() + + if (!expression.isSuspend) { + if (expression.symbol == context.ir.symbols.getContinuation) + return getContinuation() + return expression + } + + val oldFun = expression.symbol.owner + val newFun: IrSimpleFunction = + context.mapping.suspendFunctionsToFunctionWithContinuations[oldFun] + ?: error("No mapping for ${oldFun.fqNameWhenAvailable}") + + return irCall( + expression, + newFun.symbol, + newReturnType = newFun.returnType, + newSuperQualifierSymbol = expression.superQualifierSymbol + ).also { + it.putValueArgument(it.valueArgumentsCount - 1, getContinuation()) + } + } + }) + } +} + diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionsLowering.kt new file mode 100644 index 00000000000..c0e54d0c07f --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/AddContinuationToFunctionsLowering.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.lower.coroutines + +import org.jetbrains.kotlin.backend.common.* +import org.jetbrains.kotlin.backend.common.ir.* +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.isNullable +import org.jetbrains.kotlin.ir.types.typeWith +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +/** + * Replaces suspend functions with regular non-suspend functions with additional + * continuation parameter `$cont` of type [kotlin.coroutines.Continuation]. + * + * Replaces return type with `Any?` or `Any` (for non-nullable types) to indicate that suspend + * functions can return special values like [kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED] + * which might not be a subtype of original return type. + */ +class AddContinuationToNonLocalSuspendFunctionsLowering(val context: JsCommonBackendContext) : DeclarationTransformer { + override fun transformFlat(declaration: IrDeclaration): List? = + if (declaration is IrSimpleFunction && declaration.isSuspend) { + listOf(transformSuspendFunction(context, declaration)) + } else { + null + } +} + +/** + * Similar to [AddContinuationToNonLocalSuspendFunctionsLowering] but processes local functions. + * Useful for Kotlin/JS IR backend which keeps local declarations up until code generation. + */ +class AddContinuationToLocalSuspendFunctionsLowering(val context: JsCommonBackendContext) : BodyLoweringPass { + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement { + declaration.transformChildrenVoid() + return if (declaration.isSuspend) { + transformSuspendFunction(context, declaration) + } else { + declaration + } + } + }) + } +} + + +private fun transformSuspendFunction(context: JsCommonBackendContext, function: IrSimpleFunction): IrSimpleFunction { + val newFunctionWithContinuation = function.getOrCreateFunctionWithContinuationStub(context) + // Using custom mapping because number of parameters doesn't match + val parameterMapping = function.explicitParameters.zip(newFunctionWithContinuation.explicitParameters).toMap() + val newBody = function.moveBodyTo(newFunctionWithContinuation, parameterMapping) + + // Since we are changing return type to Any, function can no longer return unit implicitly. + if ( + function.returnType == context.irBuiltIns.unitType && + newBody is IrBlockBody && + newBody.statements.lastOrNull() !is IrReturn + ) { + // Adding explicit return of Unit. + newBody.statements += context.createIrBuilder(newFunctionWithContinuation.symbol).irReturnUnit() + } + + newFunctionWithContinuation.body = newBody + return newFunctionWithContinuation +} + + +private fun IrSimpleFunction.getOrCreateFunctionWithContinuationStub(context: JsCommonBackendContext): IrSimpleFunction { + return context.mapping.suspendFunctionsToFunctionWithContinuations.getOrPut(this) { + createSuspendFunctionStub(context) + } +} + +private fun IrSimpleFunction.createSuspendFunctionStub(context: JsCommonBackendContext): IrSimpleFunction { + require(this.isSuspend) + return factory.buildFun { + updateFrom(this@createSuspendFunctionStub) + isSuspend = false + name = this@createSuspendFunctionStub.name + origin = this@createSuspendFunctionStub.origin + returnType = loweredSuspendFunctionReturnType(this@createSuspendFunctionStub, context.irBuiltIns) + }.also { function -> + function.parent = parent + + function.annotations += annotations + function.metadata = metadata + + function.copyAttributes(this) + function.copyTypeParametersFrom(this) + val substitutionMap = makeTypeParameterSubstitutionMap(this, function) + function.copyReceiverParametersFrom(this, substitutionMap) + + function.overriddenSymbols += overriddenSymbols.map { + it.owner.getOrCreateFunctionWithContinuationStub(context).symbol + } + function.valueParameters = valueParameters.map { it.copyTo(function) } + + function.addValueParameter( + "\$cont", + continuationType(context).substitute(substitutionMap), + IrDeclarationOrigin.CONTINUATION + ) + } +} + +private fun IrFunction.continuationType(context: JsCommonBackendContext): IrType { + return context.coroutineSymbols.continuationClass.typeWith(returnType) +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/JsSuspendFunctionsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/JsSuspendFunctionsLowering.kt index c28e902a7a9..0a19e5be368 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/JsSuspendFunctionsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/JsSuspendFunctionsLowering.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.ir.isSuspend import org.jetbrains.kotlin.backend.common.lower.FinallyBlocksLowering import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins import org.jetbrains.kotlin.backend.common.lower.ReturnableBlockTransformer +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.builders.* @@ -29,16 +30,18 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.addToStdlib.assertedCast -class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunctionsLowering(ctx) { +class JsSuspendFunctionsLowering(ctx: JsCommonBackendContext) : AbstractSuspendFunctionsLowering(ctx) { - private val coroutineImplExceptionPropertyGetter = ctx.coroutineImplExceptionPropertyGetter - private val coroutineImplExceptionPropertySetter = ctx.coroutineImplExceptionPropertySetter - private val coroutineImplExceptionStatePropertyGetter = ctx.coroutineImplExceptionStatePropertyGetter - private val coroutineImplExceptionStatePropertySetter = ctx.coroutineImplExceptionStatePropertySetter - private val coroutineImplLabelPropertySetter = ctx.coroutineImplLabelPropertySetter - private val coroutineImplLabelPropertyGetter = ctx.coroutineImplLabelPropertyGetter - private val coroutineImplResultSymbolGetter = ctx.coroutineImplResultSymbolGetter - private val coroutineImplResultSymbolSetter = ctx.coroutineImplResultSymbolSetter + val coroutineSymbols = ctx.coroutineSymbols + + private val coroutineImplExceptionPropertyGetter = coroutineSymbols.coroutineImplExceptionPropertyGetter + private val coroutineImplExceptionPropertySetter = coroutineSymbols.coroutineImplExceptionPropertySetter + private val coroutineImplExceptionStatePropertyGetter = coroutineSymbols.coroutineImplExceptionStatePropertyGetter + private val coroutineImplExceptionStatePropertySetter = coroutineSymbols.coroutineImplExceptionStatePropertySetter + private val coroutineImplLabelPropertySetter = coroutineSymbols.coroutineImplLabelPropertySetter + private val coroutineImplLabelPropertyGetter = coroutineSymbols.coroutineImplLabelPropertyGetter + private val coroutineImplResultSymbolGetter = coroutineSymbols.coroutineImplResultSymbolGetter + private val coroutineImplResultSymbolSetter = coroutineSymbols.coroutineImplResultSymbolSetter private var coroutineId = 0 @@ -53,7 +56,7 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct argumentToPropertiesMap: Map ) { val returnableBlockTransformer = ReturnableBlockTransformer(context) - val finallyBlockTransformer = FinallyBlocksLowering(context, context.dynamicType) + val finallyBlockTransformer = FinallyBlocksLowering(context, context.catchAllThrowableType) val simplifiedFunction = transformingFunction.transform(finallyBlockTransformer, null).transform(returnableBlockTransformer, null) as IrFunction @@ -117,7 +120,22 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct coroutineImplExceptionStatePropertySetter, coroutineImplLabelPropertySetter, thisReceiver, - suspendResult.symbol + getSuspendResultAsType = { type -> + JsIrBuilder.buildImplicitCast( + JsIrBuilder.buildGetValue(suspendResult.symbol), + type + ) + }, + setSuspendResultValue = { value -> + JsIrBuilder.buildSetVariable( + suspendResult.symbol, + JsIrBuilder.buildImplicitCast( + value, + context.irBuiltIns.anyNType + ), + unit + ) + } ) body.acceptVoid(stateMachineBuilder) @@ -179,9 +197,9 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct } } - stateMachineFunction.transform(LiveLocalsTransformer(localToPropertyMap, { JsIrBuilder.buildGetValue(thisReceiver) }, unit), null) // TODO find out why some parents are incorrect stateMachineFunction.body!!.patchDeclarationParents(stateMachineFunction) + stateMachineFunction.transform(LiveLocalsTransformer(localToPropertyMap, { JsIrBuilder.buildGetValue(thisReceiver) }, unit), null) } private fun assignStateIds(entryState: SuspendState, subject: IrVariableSymbol, switch: IrWhen, rootLoop: IrLoop) { @@ -235,16 +253,18 @@ class JsSuspendFunctionsLowering(ctx: JsIrBackendContext) : AbstractSuspendFunct } override fun IrBuilderWithScope.generateDelegatedCall(expectedType: IrType, delegatingCall: IrExpression): IrExpression { - val fromType = (delegatingCall as? IrCall)?.symbol?.owner?.returnType ?: delegatingCall.type - if (!needUnboxingOrUnit(fromType, expectedType)) return delegatingCall + val functionReturnType = (delegatingCall as? IrCall)?.symbol?.owner?.let { function -> + loweredSuspendFunctionReturnType(function, context.irBuiltIns) + } ?: delegatingCall.type - val ctx = this@JsSuspendFunctionsLowering.context - return irComposite(resultType = fromType) { - val tmp = createTmpVariable(delegatingCall, irType = fromType) - val coroutineSuspended = irCall(ctx.coroutineSuspendGetter) + if (!needUnboxingOrUnit(functionReturnType, expectedType)) return delegatingCall + + return irComposite(resultType = expectedType) { + val tmp = createTmpVariable(delegatingCall, irType = functionReturnType) + val coroutineSuspended = irCall(coroutineSymbols.coroutineSuspendedGetter) val condition = irEqeqeq(irGet(tmp), coroutineSuspended) - +irIfThen(fromType, condition, irReturn(irReinterpretCast(irGet(tmp), expectedType))) - +irGet(tmp) + +irIfThen(context.irBuiltIns.unitType, condition, irReturn(irGet(tmp))) + +irImplicitCast(irGet(tmp), expectedType) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt index b2f2134b30d..482deaf0c15 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/StateMachineBuilder.kt @@ -77,7 +77,8 @@ class StateMachineBuilder( private val exStateSymbolSetter: IrSimpleFunction, private val stateSymbolSetter: IrSimpleFunction, private val thisSymbol: IrValueParameterSymbol, - private val suspendResult: IrVariableSymbol + private val getSuspendResultAsType: (IrType) -> IrExpression, + private val setSuspendResultValue: (IrExpression) -> IrStatement ) : IrElementVisitorVoid { private val loopMap = mutableMapOf() @@ -270,7 +271,6 @@ class StateMachineBuilder( } private fun implicitCast(value: IrExpression, toType: IrType) = JsIrBuilder.buildImplicitCast(value, toType) - private fun reinterpretCast(value: IrExpression, toType: IrType) = JsIrBuilder.buildReinterpretCast(value, toType) override fun visitCall(expression: IrCall) { super.visitCall(expression) @@ -295,11 +295,11 @@ class StateMachineBuilder( } } - addStatement(JsIrBuilder.buildSetVariable(suspendResult, reinterpretCast(result, anyN), unit)) + addStatement(setSuspendResultValue(result)) - val irReturn = JsIrBuilder.buildReturn(function, JsIrBuilder.buildGetValue(suspendResult), nothing) + val irReturn = JsIrBuilder.buildReturn(function, getSuspendResultAsType(anyN), nothing) val check = JsIrBuilder.buildCall(eqeqeqSymbol).apply { - putValueArgument(0, JsIrBuilder.buildGetValue(suspendResult)) + putValueArgument(0, getSuspendResultAsType(anyN)) putValueArgument(1, JsIrBuilder.buildCall(context.ir.symbols.coroutineSuspendedGetter)) } @@ -318,18 +318,17 @@ class StateMachineBuilder( unboxState?.let { buildUnboxingState(it, continueState, expectedType) } updateState(continueState) - val functionReturnType = expression.symbol.owner.returnType - addStatement(reinterpretCast(JsIrBuilder.buildGetValue(suspendResult), functionReturnType)) + addStatement(getSuspendResultAsType(expression.type)) } } private fun buildUnboxingState(unboxState: SuspendState, continueState: SuspendState, expectedType: IrType) { unboxState.successors += continueState updateState(unboxState) - val result = JsIrBuilder.buildGetValue(suspendResult) + val result = getSuspendResultAsType(anyN) val tmp = JsIrBuilder.buildVar(expectedType, function.owner, name = "unboxed", initializer = result) addStatement(tmp) - addStatement(JsIrBuilder.buildSetVariable(suspendResult, reinterpretCast(JsIrBuilder.buildGetValue(tmp.symbol), anyN), anyN)) + addStatement(setSuspendResultValue(JsIrBuilder.buildGetValue(tmp.symbol))) doDispatch(continueState) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/SuspendLoweringUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/SuspendLoweringUtils.kt index 10fe01f50d9..c3c0f43c258 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/SuspendLoweringUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/coroutines/SuspendLoweringUtils.kt @@ -6,9 +6,12 @@ package org.jetbrains.kotlin.ir.backend.js.lower.coroutines import org.jetbrains.kotlin.backend.common.ir.isSuspend +import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.utils.isDispatchReceiver +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl @@ -17,7 +20,7 @@ import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.isUnit +import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.visitors.* open class SuspendableNodesCollector(private val suspendableNodes: MutableSet) : IrElementVisitorVoid { @@ -82,7 +85,12 @@ class LiveLocalsTransformer( ) : IrElementTransformerVoid() { override fun visitGetValue(expression: IrGetValue): IrExpression { - val field = localMap[expression.symbol] ?: return expression + val field = localMap[expression.symbol] + ?: return if (expression.symbol.owner.isDispatchReceiver) + receiver() + else + expression + return expression.run { IrGetFieldImpl(startOffset, endOffset, field, type, receiver(), origin) } } @@ -99,7 +107,10 @@ class LiveLocalsTransformer( return if (initializer != null) { declaration.run { IrSetFieldImpl(startOffset, endOffset, field, receiver(), initializer, unitType) } } else { - JsIrBuilder.buildComposite(declaration.type) + JsIrBuilder.buildComposite(unitType) } } -} \ No newline at end of file +} + +fun loweredSuspendFunctionReturnType(function: IrFunction, irBuiltIns: IrBuiltIns): IrType = + if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 69e49c44715..1fe77739ed2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -349,12 +349,16 @@ class IrModuleToJsTransformer( return statements } - private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List { + private fun generateMainArguments( + generateArgv: Boolean, + generateContinuation: Boolean, + rootContext: JsGenerationContext + ): List { val mainArguments = this.mainArguments!! val mainArgumentsArray = - if (mainFunction.valueParameters.isNotEmpty()) JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) else null + if (generateArgv) JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) else null - val continuation = if (mainFunction.isSuspend) { + val continuation = if (generateContinuation) { backendContext.coroutineEmptyContinuation.owner .let { it.getter!! } .let { rootContext.getNameForStaticFunction(it) } @@ -365,11 +369,14 @@ class IrModuleToJsTransformer( } private fun generateCallToMain(modules: Iterable, rootContext: JsGenerationContext): List { + // TODO: Generate calls to main as IR->IR lowering if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists - val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(modules.last()) + val mainFunction = JsMainFunctionDetector(backendContext).getMainFunctionOrNull(modules.last()) return mainFunction?.let { val jsName = rootContext.getNameForStaticFunction(it) - listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt()) + val generateArgv = it.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false + val generateContinuation = it.isLoweredSuspendFunction(backendContext) + listOf(JsInvocation(jsName.makeRef(), generateMainArguments(generateArgv, generateContinuation, rootContext)).makeStmt()) } ?: emptyList() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt index cd25b8e2367..abdf528920f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIntrinsicTransformers.kt @@ -121,21 +121,9 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) { addIfNotNull(intrinsics.jsCode) { _, _ -> error("Should not be called") } - add(intrinsics.jsGetContinuation) { _, context: JsGenerationContext -> - context.continuation - } + add(intrinsics.jsGetContinuation) { _, _ -> error("getContinuation should be lowered") } - add(backendContext.ir.symbols.returnIfSuspended) { call, context -> - val args = translateCallArguments(call, context) - args[0] - } - - add(intrinsics.jsCoroutineContext) { _, context: JsGenerationContext -> - val contextGetter = backendContext.coroutineGetContext - val getterName = context.getNameForStaticFunction(contextGetter.owner) - val continuation = context.continuation - JsInvocation(JsNameRef(getterName, continuation)) - } + add(intrinsics.jsCoroutineContext) { _, _ -> error("coroutineContext should be lowered") } add(intrinsics.jsArrayLength) { call, context -> val args = translateCallArguments(call, context) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 4be7a82fc87..c186a5cd3bf 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -76,9 +76,7 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat declaration.extensionReceiverParameter?.let { function.addParameter(functionContext.getNameForValueDeclaration(it)) } functionParams.forEach { function.addParameter(it) } - if (declaration.isSuspend) { - function.addParameter(JsName(Namer.CONTINUATION)) // TODO: Use namer? - } + check(!declaration.isSuspend) { "All Suspend functions should be lowered" } return function } @@ -336,9 +334,8 @@ fun translateCallArguments( .dropLastWhile { it == null } .map { it ?: JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1)) } - return if (expression.symbol.isSuspend) { - arguments + context.continuation - } else arguments + check(!expression.symbol.isSuspend) { "Suspend functions should be lowered" } + return arguments } private fun IrMemberAccessExpression<*>.validWithNullArgs() = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt index 8fce5fc8199..ce7f68c131c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsGenerationContext.kt @@ -49,17 +49,6 @@ class JsGenerationContext( ) } - val continuation - get() = if (isCoroutineDoResume()) { - JsThisRef() - } else { - if (currentFunction!!.isSuspend) { - JsNameRef(Namer.CONTINUATION) - } else { - JsNameRef(this.getNameForValueDeclaration(currentFunction.valueParameters.last())) - } - } - fun getNameForValueDeclaration(declaration: IrDeclarationWithName): JsName { val name = localNames!!.variableNames.names[declaration] ?: error("Variable name is not found ${declaration.name}") @@ -76,12 +65,5 @@ class JsGenerationContext( return JsName(name) } - private fun isCoroutineDoResume(): Boolean { - val overriddenSymbols = (currentFunction as? IrSimpleFunction)?.overriddenSymbols ?: return false - return overriddenSymbols.any { - it.owner.name.asString() == "doResume" && it.owner.parent == staticContext.coroutineImplDeclaration - } - } - fun checkIfJsCode(symbol: IrFunctionSymbol): Boolean = symbol == staticContext.backendContext.intrinsics.jsCode } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt index 9e3fec31d63..a53b7d369ce 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/JsMainFunctionDetector.kt @@ -5,18 +5,16 @@ package org.jetbrains.kotlin.ir.backend.js.utils -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.types.Variance -object JsMainFunctionDetector { - +class JsMainFunctionDetector(val context: JsCommonBackendContext) { private fun IrSimpleFunction.isSuitableForMainParametersSize(allowEmptyParameters: Boolean): Boolean = when (valueParameters.size) { - 1 -> true + 1, 2 -> true 0 -> allowEmptyParameters else -> false } @@ -24,24 +22,20 @@ object JsMainFunctionDetector { private fun IrSimpleFunction.isMain(allowEmptyParameters: Boolean): Boolean { if (typeParameters.isNotEmpty()) return false if (!isSuitableForMainParametersSize(allowEmptyParameters)) return false - if (!returnType.isUnit()) return false + val isLoweredSuspendFunction = isLoweredSuspendFunction(context) + if (!returnType.isUnit() && + !(isLoweredSuspendFunction && + (returnType == context.irBuiltIns.anyNType || + returnType == context.irBuiltIns.anyType))) + return false + if (name.asString() != "main") return false if (extensionReceiverParameter != null) return false if (valueParameters.size == 1) { - val parameter = valueParameters.single() - - if (!parameter.type.isArray()) return false - - val type = parameter.type as IrSimpleType - - if (type.arguments.size != 1) return false - - val argument = type.arguments.single() as? IrTypeProjection ?: return false - - if (argument.variance == Variance.IN_VARIANCE) return false - - return argument.type.isString() + return isLoweredSuspendFunction || valueParameters.single().isStringArrayParameter() + } else if (valueParameters.size == 2) { + return valueParameters[0].isStringArrayParameter() && isLoweredSuspendFunction } else { require(allowEmptyParameters) require(valueParameters.isEmpty()) @@ -73,4 +67,30 @@ object JsMainFunctionDetector { return resultPair?.second } -} \ No newline at end of file +} + + +fun IrValueParameter.isStringArrayParameter(): Boolean { + val type = this.type as? IrSimpleType ?: return false + + if (!type.isArray()) return false + + if (type.arguments.size != 1) return false + + val argument = type.arguments.single() as? IrTypeProjection ?: return false + + if (argument.variance == Variance.IN_VARIANCE) return false + + return argument.type.isString() +} + +fun IrFunction.isLoweredSuspendFunction(context: JsCommonBackendContext): Boolean { + val parameter = valueParameters.lastOrNull() ?: return false + val type = parameter.type as? IrSimpleType ?: return false + return type.classifier == context.coroutineSymbols.continuationClass +} + +fun IrValueParameter.isContinuationParameter(context: JsCommonBackendContext): Boolean { + val type = this.type as? IrSimpleType ?: return false + return type.classifier == context.coroutineSymbols.continuationClass +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt index 576287bfd48..96d9317b739 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt @@ -23,8 +23,6 @@ object Namer { val PROTOTYPE_NAME = "prototype" val CONSTRUCTOR_NAME = "constructor" - val CONTINUATION = "\$cont" - val JS_ERROR = JsNameRef("Error") val JS_OBJECT = JsNameRef("Object") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt index d02e4941a5e..155b5194f60 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor import org.jetbrains.kotlin.ir.* import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsCommonCoroutineSymbols import org.jetbrains.kotlin.ir.backend.js.JsMapping import org.jetbrains.kotlin.ir.backend.js.lower.JsInnerClassesSupport import org.jetbrains.kotlin.ir.builders.declarations.buildFun @@ -57,6 +58,9 @@ class WasmBackendContext( override val mapping = JsMapping(irFactory) + override val coroutineSymbols = + JsCommonCoroutineSymbols(symbolTable, module,this) + val innerClassesSupport = JsInnerClassesSupport(mapping, irFactory) override val internalPackageFqn = FqName("kotlin.wasm") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index 7bf0212e2de..7c795afdc2d 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -13,6 +13,9 @@ import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorI import org.jetbrains.kotlin.backend.common.phaser.* import org.jetbrains.kotlin.backend.wasm.lower.* import org.jetbrains.kotlin.ir.backend.js.lower.* +import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AddContinuationToFunctionCallsLowering +import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AddContinuationToNonLocalSuspendFunctionsLowering +import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.RemoveInlineDeclarationsWithReifiedTypeParametersLowering import org.jetbrains.kotlin.ir.backend.js.lower.inline.WrapInlineDeclarationsWithReifiedTypeParametersLowering import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -80,14 +83,6 @@ private val lateinitUsageLoweringPhase = makeWasmModulePhase( description = "Insert checks for lateinit field references" ) - -// TODO make all lambda-related stuff work with IrFunctionExpression and drop this phase -private val provisionalFunctionExpressionPhase = makeWasmModulePhase( - { ProvisionalFunctionExpressionLowering() }, - name = "FunctionExpression", - description = "Transform IrFunctionExpression to a local function reference" -) - private val wrapInlineDeclarationsWithReifiedTypeParametersPhase = makeWasmModulePhase( ::WrapInlineDeclarationsWithReifiedTypeParametersLowering, name = "WrapInlineDeclarationsWithReifiedTypeParametersPhase", @@ -247,6 +242,27 @@ private val innerClassConstructorCallsLoweringPhase = makeWasmModulePhase( description = "Replace inner class constructor invocation" ) +private val suspendFunctionsLoweringPhase = makeWasmModulePhase( + ::JsSuspendFunctionsLowering, + name = "SuspendFunctionsLowering", + description = "Transform suspend functions into CoroutineImpl instance and build state machine" +) + +private val addContinuationToNonLocalSuspendFunctionsLoweringPhase = makeWasmModulePhase( + ::AddContinuationToNonLocalSuspendFunctionsLowering, + name = "AddContinuationToNonLocalSuspendFunctionsLowering", + description = "Add explicit continuation as last parameter of suspend functions" +) + +private val addContinuationToFunctionCallsLoweringPhase = makeWasmModulePhase( + ::AddContinuationToFunctionCallsLowering, + name = "AddContinuationToFunctionCallsLowering", + description = "Replace suspend function calls with calls with continuation", + prerequisite = setOf( + addContinuationToNonLocalSuspendFunctionsLoweringPhase, + ) +) + private val defaultArgumentStubGeneratorPhase = makeWasmModulePhase( ::DefaultArgumentStubGenerator, name = "DefaultArgumentStubGenerator", @@ -461,7 +477,6 @@ val wasmPhases = NamedCompilerPhase( wrapInlineDeclarationsWithReifiedTypeParametersPhase then functionInliningPhase then - provisionalFunctionExpressionPhase then lateinitNullableFieldsPhase then lateinitDeclarationLoweringPhase then lateinitUsageLoweringPhase then @@ -483,8 +498,6 @@ val wasmPhases = NamedCompilerPhase( propertiesLoweringPhase then primaryConstructorLoweringPhase then delegateToPrimaryConstructorLoweringPhase then - initializersLoweringPhase then - initializersCleanupLoweringPhase then // Common prefix ends enumEntryInstancesLoweringPhase then @@ -495,8 +508,12 @@ val wasmPhases = NamedCompilerPhase( enumUsageLoweringPhase then enumEntryRemovalLoweringPhase then -// TODO: Requires stdlib -// suspendFunctionsLoweringPhase then + suspendFunctionsLoweringPhase then + initializersLoweringPhase then + initializersCleanupLoweringPhase then + + addContinuationToNonLocalSuspendFunctionsLoweringPhase then + addContinuationToFunctionCallsLoweringPhase then stringConstructorLowering then tryCatchCanonicalization then diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt index 4f7c6727bd9..583ffdb9673 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmSymbols.kt @@ -55,24 +55,20 @@ class WasmSymbols( get() = TODO() override val stringBuilder = getIrClass(FqName("kotlin.text.StringBuilder")) - override val coroutineImpl - get() = TODO() - override val coroutineSuspendedGetter - get() = TODO() - override val getContinuation - get() = TODO() - override val coroutineContextGetter by lazy { - context.irFactory.addFunction(context.getExcludedPackageFragment(FqName("kotlin.excluded"))) { - name = Name.identifier("coroutineContextGetter\$Stub") - }.symbol - } - - override val suspendCoroutineUninterceptedOrReturn - get() = TODO() - override val coroutineGetContext - get() = TODO() - override val returnIfSuspended - get() = TODO() + override val coroutineImpl = + context.coroutineSymbols.coroutineImpl + override val coroutineSuspendedGetter = + context.coroutineSymbols.coroutineSuspendedGetter + override val getContinuation = + getInternalFunction("getContinuation") + override val coroutineContextGetter = + symbolTable.referenceSimpleFunction(context.coroutineSymbols.coroutineContextProperty.getter!!) + override val suspendCoroutineUninterceptedOrReturn = + getInternalFunction("suspendCoroutineUninterceptedOrReturn") + override val coroutineGetContext = + getInternalFunction("getCoroutineContext") + override val returnIfSuspended = + getInternalFunction("returnIfSuspended") override val functionAdapter: IrClassSymbol get() = TODO() @@ -153,6 +149,9 @@ class WasmSymbols( val exportString = getInternalFunction("exportString") val unsafeGetScratchRawMemory = getInternalFunction("unsafeGetScratchRawMemory") + val startCoroutineUninterceptedOrReturnIntrinsics = + (0..2).map { getInternalFunction("startCoroutineUninterceptedOrReturnIntrinsic$it") } + // KProperty implementations val kLocalDelegatedPropertyImpl: IrClassSymbol = this.getInternalClass("KLocalDelegatedPropertyImpl") val kLocalDelegatedMutablePropertyImpl: IrClassSymbol = this.getInternalClass("KLocalDelegatedMutablePropertyImpl") diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt index a8c5c4a99fe..77b7d14a5a7 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/BuiltInsLowering.kt @@ -21,10 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.types.isNullable -import org.jetbrains.kotlin.ir.util.functions -import org.jetbrains.kotlin.ir.util.irCall -import org.jetbrains.kotlin.ir.util.isFunction -import org.jetbrains.kotlin.ir.util.isNullConst +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.util.OperatorNameConventions @@ -121,6 +118,11 @@ class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass { putValueArgument(0, call.getValueArgument(0)) } } + in symbols.startCoroutineUninterceptedOrReturnIntrinsics -> { + val arity = symbols.startCoroutineUninterceptedOrReturnIntrinsics.indexOf(symbol) + val newSymbol = irBuiltins.suspendFunctionN(arity).getSimpleFunction("invoke")!! + return irCall(call, newSymbol, argumentsAsReceivers = true) + } } return call diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt index c9c50a86a39..29f221a1a24 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt @@ -78,6 +78,7 @@ interface IrDeclarationOrigin { object SYNTHETIC_JAVA_PROPERTY_DELEGATE : IrDeclarationOriginImpl("SYNTHETIC_JAVA_PROPERTY_DELEGATE", isSynthetic = true) object FIELD_FOR_OUTER_THIS : IrDeclarationOriginImpl("FIELD_FOR_OUTER_THIS", isSynthetic = true) + object CONTINUATION : IrDeclarationOriginImpl("CONTINUATION", isSynthetic = true) val isSynthetic: Boolean get() = false } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index 267d28dfeb9..a93360987b1 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -36,6 +36,9 @@ class JsMapping(val state: JsMappingState) : DefaultMapping(state) { val enumEntryToCorrespondingField = state.newDeclarationToDeclarationMapping() val enumClassToInitEntryInstancesFun = state.newDeclarationToDeclarationMapping() + val suspendFunctionsToFunctionWithContinuations = + state.newDeclarationToDeclarationMapping() + val suspendArityStore = state.newDeclarationToDeclarationCollectionMapping>() } diff --git a/libraries/stdlib/js-ir/runtime/coroutineInternalJS.kt b/libraries/stdlib/js-ir/runtime/coroutineInternalJS.kt index 8caddd9ca29..4a2a22e3237 100644 --- a/libraries/stdlib/js-ir/runtime/coroutineInternalJS.kt +++ b/libraries/stdlib/js-ir/runtime/coroutineInternalJS.kt @@ -14,8 +14,9 @@ internal fun getContinuation(): Continuation { throw Exception("Implement // Do we really need this intrinsic in JS? @PublishedApi +@Suppress("UNCHECKED_CAST") internal suspend fun returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T { - throw Exception("Implemented as intrinsic") + return argument as T } @PublishedApi diff --git a/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Coroutines.kt b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Coroutines.kt new file mode 100644 index 00000000000..53ca8fcfd74 --- /dev/null +++ b/libraries/stdlib/wasm/internal/kotlin/wasm/internal/Coroutines.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("RedundantSuspendModifier") + +package kotlin.wasm.internal + +import kotlin.coroutines.* + +@PublishedApi +@ExcludedFromCodegen +internal fun getContinuation(): Continuation = + implementedAsIntrinsic + +@PublishedApi +@Suppress("UNCHECKED_CAST") +internal suspend fun returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T = + argument as T + +@PublishedApi +internal fun interceptContinuationIfNeeded( + context: CoroutineContext, + continuation: Continuation +) = context[ContinuationInterceptor]?.interceptContinuation(continuation) ?: continuation + + +@PublishedApi +internal suspend fun getCoroutineContext(): CoroutineContext = getContinuation().context + +@PublishedApi +internal suspend fun suspendCoroutineUninterceptedOrReturn(block: (Continuation) -> Any?): T = + returnIfSuspended(block(getContinuation())) + +@ExcludedFromCodegen +@PublishedApi +internal fun startCoroutineUninterceptedOrReturnIntrinsic0( + f: (suspend () -> T), + completion: Continuation +): Any? { + implementedAsIntrinsic +} + +@ExcludedFromCodegen +@PublishedApi +internal fun startCoroutineUninterceptedOrReturnIntrinsic1( + f: (suspend R.() -> T), + receiver: R, + completion: Continuation +): Any? { + implementedAsIntrinsic +} + +@ExcludedFromCodegen +@PublishedApi +internal fun startCoroutineUninterceptedOrReturnIntrinsic2( + f: (suspend R.(P) -> T), + receiver: R, + param: P, + completion: Continuation +): Any? { + implementedAsIntrinsic +} diff --git a/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutineImpl.kt b/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutineImpl.kt new file mode 100644 index 00000000000..1a695365714 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutineImpl.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.coroutines + +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED + +@SinceKotlin("1.3") +internal abstract class CoroutineImpl(private val resultContinuation: Continuation?) : Continuation { + protected var state = 0 + protected var exceptionState = 0 + protected var result: Any? = null + protected var exception: Throwable? = null + protected var finallyPath: Array? = null + + private val _context: CoroutineContext? = resultContinuation?.context + + public override val context: CoroutineContext get() = _context!! + + private var intercepted_: Continuation? = null + + public fun intercepted(): Continuation = + intercepted_ + ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this) + .also { intercepted_ = it } + + override fun resumeWith(result: Result) { + var current = this + var currentResult: Any? = result.getOrNull() + var currentException: Throwable? = result.exceptionOrNull() + + // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume + while (true) { + with(current) { + // Set result and exception fields in the current continuation + if (currentException == null) { + this.result = currentResult + } else { + state = exceptionState + exception = currentException + } + + try { + val outcome = doResume() + if (outcome === COROUTINE_SUSPENDED) return + currentResult = outcome + currentException = null + } catch (exception: Throwable) { // Catch all exceptions + currentResult = null + currentException = exception + } + + releaseIntercepted() // this state machine instance is terminating + + val completion = resultContinuation!! + + if (completion is CoroutineImpl) { + // unrolling recursion via loop + current = completion + } else { + // top-level completion reached -- invoke and return + if (currentException != null) { + completion.resumeWithException(currentException!!) + } else { + completion.resume(currentResult) + } + return + } + } + } + } + + private fun releaseIntercepted() { + val intercepted = intercepted_ + if (intercepted != null && intercepted !== this) { + context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted) + } + this.intercepted_ = CompletedContinuation // just in case + } + + protected abstract fun doResume(): Any? + + public open fun create(completion: Continuation<*>): Continuation { + throw UnsupportedOperationException("create(Continuation) has not been overridden") + } + + public open fun create(value: Any?, completion: Continuation<*>): Continuation { + throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden") + } +} + +internal object CompletedContinuation : Continuation { + override val context: CoroutineContext + get() = error("This continuation is already complete") + + override fun resumeWith(result: Result) { + error("This continuation is already complete") + } + + override fun toString(): String = "This continuation is already complete" +} diff --git a/libraries/stdlib/wasm/src/kotlin/coroutines/Coroutines.kt b/libraries/stdlib/wasm/src/kotlin/coroutines/Coroutines.kt index 0961934ca18..cd91bb6c88d 100644 --- a/libraries/stdlib/wasm/src/kotlin/coroutines/Coroutines.kt +++ b/libraries/stdlib/wasm/src/kotlin/coroutines/Coroutines.kt @@ -1,21 +1,53 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package kotlin.coroutines +import kotlin.coroutines.intrinsics.CoroutineSingletons.* +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED + @PublishedApi @SinceKotlin("1.3") -internal actual class SafeContinuation : Continuation { - actual internal constructor(delegate: Continuation, initialResult: Any?) { TODO("Wasm stdlib: Coroutines") } +internal actual class SafeContinuation +internal actual constructor( + private val delegate: Continuation, + initialResult: Any? +) : Continuation { + @PublishedApi + internal actual constructor(delegate: Continuation) : this(delegate, UNDECIDED) + + public actual override val context: CoroutineContext + get() = delegate.context + + private var result: Any? = initialResult + + public actual override fun resumeWith(result: Result) { + val cur = this.result + when { + cur === UNDECIDED -> { + this.result = result.value + } + cur === COROUTINE_SUSPENDED -> { + this.result = RESUMED + delegate.resumeWith(result) + } + else -> throw IllegalStateException("Already resumed") + } + } @PublishedApi - actual internal constructor(delegate: Continuation) { TODO("Wasm stdlib: Coroutines") } - - @PublishedApi - actual internal fun getOrThrow(): Any? = TODO("Wasm stdlib: Coroutines") - - actual override val context: CoroutineContext = TODO("Wasm stdlib: Coroutines") - actual override fun resumeWith(result: Result): Unit { TODO("Wasm stdlib: Coroutines") } -} + internal actual fun getOrThrow(): Any? { + if (result === UNDECIDED) { + result = COROUTINE_SUSPENDED + return COROUTINE_SUSPENDED + } + val result = this.result + return when { + result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream + result is Result.Failure -> throw result.exception + else -> result // either COROUTINE_SUSPENDED or data + } + } +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutinesIntrinsics.kt b/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutinesIntrinsics.kt index d8959b4a719..c6668bec2de 100644 --- a/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutinesIntrinsics.kt +++ b/libraries/stdlib/wasm/src/kotlin/coroutines/CoroutinesIntrinsics.kt @@ -1,14 +1,12 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package kotlin.coroutines.intrinsics -import kotlin.coroutines.Continuation -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext -import kotlin.internal.InlineOnly +import kotlin.coroutines.* +import kotlin.wasm.internal.* /** * Starts an unintercepted coroutine without a receiver and with result type [T] and executes it until its first suspension. @@ -22,10 +20,11 @@ import kotlin.internal.InlineOnly * This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of the suspended * coroutine using a reference to the suspending function. */ -@SinceKotlin("1.3") +@Suppress("UNCHECKED_CAST") +@kotlin.internal.InlineOnly public actual inline fun (suspend () -> T).startCoroutineUninterceptedOrReturn( completion: Continuation -): Any? = TODO("Wasm stdlib: Coroutines intrinsics") +): Any? = startCoroutineUninterceptedOrReturnIntrinsic0(this, completion) /** * Starts an unintercepted coroutine with receiver type [R] and result type [T] and executes it until its first suspension. @@ -39,29 +38,81 @@ public actual inline fun (suspend () -> T).startCoroutineUninterceptedOrRetu * This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of the suspended * coroutine using a reference to the suspending function. */ -@SinceKotlin("1.3") +@Suppress("UNCHECKED_CAST") +@kotlin.internal.InlineOnly public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedOrReturn( receiver: R, completion: Continuation -): Any? = TODO("Wasm stdlib: Coroutines intrinsics") +): Any? = startCoroutineUninterceptedOrReturnIntrinsic1(this, receiver, completion) -@InlineOnly +@Suppress("UNCHECKED_CAST") +@kotlin.internal.InlineOnly internal actual inline fun (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn( receiver: R, param: P, completion: Continuation -): Any? = TODO("Wasm stdlib: Coroutines intrinsics") +): Any? = startCoroutineUninterceptedOrReturnIntrinsic2(this, receiver, param, completion) -@SinceKotlin("1.3") +/** + * Creates unintercepted coroutine without receiver and with result type [T]. + * This function creates a new, fresh instance of suspendable computation every time it is invoked. + * + * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. + * The [completion] continuation is invoked when coroutine completes with result or exception. + * + * This function returns unintercepted continuation. + * Invocation of `resume(Unit)` starts coroutine immediately in the invoker's call stack without going through the + * [ContinuationInterceptor] that might be present in the completion's [CoroutineContext]. + * It is the invoker's responsibility to ensure that a proper invocation context is established. + * Note that [completion] of this function may get invoked in an arbitrary context. + * + * [Continuation.intercepted] can be used to acquire the intercepted continuation. + * Invocation of `resume(Unit)` on intercepted continuation guarantees that execution of + * both the coroutine and [completion] happens in the invocation context established by + * [ContinuationInterceptor]. + * + * Repeated invocation of any resume function on the resulting continuation corrupts the + * state machine of the coroutine and may result in arbitrary behaviour or exception. + */ +@Suppress("UNCHECKED_CAST") public actual fun (suspend () -> T).createCoroutineUnintercepted( completion: Continuation -): Continuation = TODO("Wasm stdlib: Coroutines intrinsics") +): Continuation { + return createCoroutineFromSuspendFunction(completion) { + this.startCoroutineUninterceptedOrReturn(completion) + } +} -@SinceKotlin("1.3") +/** + * Creates unintercepted coroutine with receiver type [R] and result type [T]. + * This function creates a new, fresh instance of suspendable computation every time it is invoked. + * + * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. + * The [completion] continuation is invoked when coroutine completes with result or exception. + * + * This function returns unintercepted continuation. + * Invocation of `resume(Unit)` starts coroutine immediately in the invoker's call stack without going through the + * [ContinuationInterceptor] that might be present in the completion's [CoroutineContext]. + * It is the invoker's responsibility to ensure that a proper invocation context is established. + * Note that [completion] of this function may get invoked in an arbitrary context. + * + * [Continuation.intercepted] can be used to acquire the intercepted continuation. + * Invocation of `resume(Unit)` on intercepted continuation guarantees that execution of + * both the coroutine and [completion] happens in the invocation context established by + * [ContinuationInterceptor]. + * + * Repeated invocation of any resume function on the resulting continuation corrupts the + * state machine of the coroutine and may result in arbitrary behaviour or exception. + */ +@Suppress("UNCHECKED_CAST") public actual fun (suspend R.() -> T).createCoroutineUnintercepted( receiver: R, completion: Continuation -): Continuation = TODO("Wasm stdlib: Coroutines intrinsics") +): Continuation { + return createCoroutineFromSuspendFunction(completion) { + this.startCoroutineUninterceptedOrReturn(receiver, completion) + } +} /** * Intercepts this continuation with [ContinuationInterceptor]. @@ -72,5 +123,18 @@ public actual fun (suspend R.() -> T).createCoroutineUnintercepted( * * If this function is invoked on other [Continuation] instances it returns `this` continuation unchanged. */ -@SinceKotlin("1.3") -public actual fun Continuation.intercepted(): Continuation = TODO("Wasm stdlib: Coroutines intrinsics") +public actual fun Continuation.intercepted(): Continuation = + (this as? CoroutineImpl)?.intercepted() ?: this + +@Suppress("UNCHECKED_CAST") +private inline fun createCoroutineFromSuspendFunction( + completion: Continuation, + crossinline block: () -> Any? +): Continuation { + return object : CoroutineImpl(completion as Continuation) { + override fun doResume(): Any? { + exception?.let { throw it } + return block() + } + } +} \ No newline at end of file