[Wasm] Support coroutines

- Reuse JS IR Suspend function lowering
  - Fix types
  - Disable reinterpretCast-based optimization for inline
    classes
- Add basic support to Wasm stdlib
- Explicitly transform suspend functions into regular functions
  with continuations
- Clean suspend function handling from JS IR codegen
This commit is contained in:
Svyatoslav Kuzmich
2021-10-11 15:57:38 +03:00
parent e3f826d1b9
commit 3bce0cc055
27 changed files with 817 additions and 238 deletions
+1
View File
@@ -18,6 +18,7 @@
<w>sqrt</w>
<w>testsuite</w>
<w>uninstantiable</w>
<w>unintercepted</w>
<w>unlinkable</w>
<w>vtable</w>
<w>wabt</w>
@@ -94,9 +94,10 @@ private fun buildRoots(modules: Iterable<IrModuleFragment>, 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
}
}
@@ -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 <T> 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<IrSimpleFunction>()
.atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME }
?: continuationClass.owner.declarations.filterIsInstance<IrProperty>()
.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("<get-context>")
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<SimpleFunctionDescriptor> =
memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList()
interface InlineClassesUtils {
fun isTypeInlined(type: IrType): Boolean
@@ -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("<get-context>")
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<JsIrBackendContext>(this, irModuleFragment) {
override val symbols = object : Symbols<JsIrBackendContext>(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<IrSimpleFunction>()
.atMostOne { it.name == CONTINUATION_CONTEXT_GETTER_NAME }
?: continuationClass.owner.declarations.filterIsInstance<IrProperty>()
.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<IrProperty>()
}
@@ -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<SimpleFunctionDescriptor> =
memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList()
private fun findProperty(memberScope: MemberScope, name: Name): List<PropertyDescriptor> =
memberScope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND).toList()
@@ -442,7 +385,4 @@ class JsIrBackendContext(
/*TODO*/
print(message)
}
// TODO: investigate if it could be removed
fun <T> lazy2(fn: () -> T) = lazy { irFactory.stageController.withInitialIr(fn) }
}
@@ -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<Lowering>(
suspendFunctionsLoweringPhase,
propertyReferenceLoweringPhase,
interopCallableReferenceLoweringPhase,
jsSuspendArityStorePhase,
addContinuationToNonLocalSuspendFunctionsLoweringPhase,
addContinuationToLocalSuspendFunctionsLoweringPhase,
addContinuationToFunctionCallsLoweringPhase,
returnableBlockLoweringPhase,
rangeContainsLoweringPhase,
forLoopsLoweringPhase,
@@ -855,7 +881,6 @@ private val loweringList = listOf<Lowering>(
callsLoweringPhase,
cleanupLoweringPhase,
validateIrAfterLowering,
jsSuspendArityStorePhase
)
// TODO comment? Eliminate ModuleLowering's? Don't filter them here?
@@ -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())
}
}
})
}
}
@@ -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<IrDeclaration>? =
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)
}
@@ -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<JsIrBackendContext>(ctx) {
class JsSuspendFunctionsLowering(ctx: JsCommonBackendContext) : AbstractSuspendFunctionsLowering<JsCommonBackendContext>(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<IrValueParameter, IrField>
) {
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)
}
}
@@ -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<IrLoop, LoopBounds>()
@@ -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)
}
@@ -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<IrElement>) : 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)
}
}
}
}
fun loweredSuspendFunctionReturnType(function: IrFunction, irBuiltIns: IrBuiltIns): IrType =
if (function.returnType.isNullable()) irBuiltIns.anyNType else irBuiltIns.anyType
@@ -349,12 +349,16 @@ class IrModuleToJsTransformer(
return statements
}
private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List<JsExpression> {
private fun generateMainArguments(
generateArgv: Boolean,
generateContinuation: Boolean,
rootContext: JsGenerationContext
): List<JsExpression> {
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<IrModuleFragment>, rootContext: JsGenerationContext): List<JsStatement> {
// 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()
}
@@ -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)
@@ -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() =
@@ -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
}
@@ -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
}
}
}
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
}
@@ -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")
@@ -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")
@@ -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
@@ -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")
@@ -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
@@ -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
}
@@ -36,6 +36,9 @@ class JsMapping(val state: JsMappingState) : DefaultMapping(state) {
val enumEntryToCorrespondingField = state.newDeclarationToDeclarationMapping<IrEnumEntry, IrField>()
val enumClassToInitEntryInstancesFun = state.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val suspendFunctionsToFunctionWithContinuations =
state.newDeclarationToDeclarationMapping<IrSimpleFunction, IrSimpleFunction>()
val suspendArityStore = state.newDeclarationToDeclarationCollectionMapping<IrClass, Collection<IrSimpleFunction>>()
}
@@ -14,8 +14,9 @@ internal fun <T> getContinuation(): Continuation<T> { throw Exception("Implement
// Do we really need this intrinsic in JS?
@PublishedApi
@Suppress("UNCHECKED_CAST")
internal suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T {
throw Exception("Implemented as intrinsic")
return argument as T
}
@PublishedApi
@@ -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 <T> getContinuation(): Continuation<T> =
implementedAsIntrinsic
@PublishedApi
@Suppress("UNCHECKED_CAST")
internal suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T =
argument as T
@PublishedApi
internal fun <T> interceptContinuationIfNeeded(
context: CoroutineContext,
continuation: Continuation<T>
) = context[ContinuationInterceptor]?.interceptContinuation(continuation) ?: continuation
@PublishedApi
internal suspend fun getCoroutineContext(): CoroutineContext = getContinuation<Any?>().context
@PublishedApi
internal suspend fun <T> suspendCoroutineUninterceptedOrReturn(block: (Continuation<T>) -> Any?): T =
returnIfSuspended<T>(block(getContinuation<T>()))
@ExcludedFromCodegen
@PublishedApi
internal fun <T> startCoroutineUninterceptedOrReturnIntrinsic0(
f: (suspend () -> T),
completion: Continuation<T>
): Any? {
implementedAsIntrinsic
}
@ExcludedFromCodegen
@PublishedApi
internal fun <R, T> startCoroutineUninterceptedOrReturnIntrinsic1(
f: (suspend R.() -> T),
receiver: R,
completion: Continuation<T>
): Any? {
implementedAsIntrinsic
}
@ExcludedFromCodegen
@PublishedApi
internal fun <R, P, T> startCoroutineUninterceptedOrReturnIntrinsic2(
f: (suspend R.(P) -> T),
receiver: R,
param: P,
completion: Continuation<T>
): Any? {
implementedAsIntrinsic
}
@@ -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<Any?>?) : Continuation<Any?> {
protected var state = 0
protected var exceptionState = 0
protected var result: Any? = null
protected var exception: Throwable? = null
protected var finallyPath: Array<Int>? = null
private val _context: CoroutineContext? = resultContinuation?.context
public override val context: CoroutineContext get() = _context!!
private var intercepted_: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted_
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted_ = it }
override fun resumeWith(result: Result<Any?>) {
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<Unit> {
throw UnsupportedOperationException("create(Continuation) has not been overridden")
}
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
}
}
internal object CompletedContinuation : Continuation<Any?> {
override val context: CoroutineContext
get() = error("This continuation is already complete")
override fun resumeWith(result: Result<Any?>) {
error("This continuation is already complete")
}
override fun toString(): String = "This continuation is already complete"
}
@@ -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<in T> : Continuation<T> {
actual internal constructor(delegate: Continuation<T>, initialResult: Any?) { TODO("Wasm stdlib: Coroutines") }
internal actual class SafeContinuation<in T>
internal actual constructor(
private val delegate: Continuation<T>,
initialResult: Any?
) : Continuation<T> {
@PublishedApi
internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
public actual override val context: CoroutineContext
get() = delegate.context
private var result: Any? = initialResult
public actual override fun resumeWith(result: Result<T>) {
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<T>) { 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<T>): 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
}
}
}
@@ -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 <T> (suspend () -> T).startCoroutineUninterceptedOrReturn(
completion: Continuation<T>
): 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 <T> (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 <R, T> (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
completion: Continuation<T>
): Any? = TODO("Wasm stdlib: Coroutines intrinsics")
): Any? = startCoroutineUninterceptedOrReturnIntrinsic1(this, receiver, completion)
@InlineOnly
@Suppress("UNCHECKED_CAST")
@kotlin.internal.InlineOnly
internal actual inline fun <R, P, T> (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
param: P,
completion: Continuation<T>
): 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 <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> = TODO("Wasm stdlib: Coroutines intrinsics")
): Continuation<Unit> {
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 <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> = TODO("Wasm stdlib: Coroutines intrinsics")
): Continuation<Unit> {
return createCoroutineFromSuspendFunction(completion) {
this.startCoroutineUninterceptedOrReturn(receiver, completion)
}
}
/**
* Intercepts this continuation with [ContinuationInterceptor].
@@ -72,5 +123,18 @@ public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
*
* If this function is invoked on other [Continuation] instances it returns `this` continuation unchanged.
*/
@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> = TODO("Wasm stdlib: Coroutines intrinsics")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? CoroutineImpl)?.intercepted() ?: this
@Suppress("UNCHECKED_CAST")
private inline fun <T> createCoroutineFromSuspendFunction(
completion: Continuation<T>,
crossinline block: () -> Any?
): Continuation<Unit> {
return object : CoroutineImpl(completion as Continuation<Any?>) {
override fun doResume(): Any? {
exception?.let { throw it }
return block()
}
}
}