Suspend Functions
2 tests muted due to duplicate variables found by the validator If memory serves: - there is a `suspend inline fun` and a callable reference to it - the suspend function doesn't remove the original function in this case anymore - the duplicate `var`'s are inside the function body and the callable reference state machine body
This commit is contained in:
committed by
Anton Bannykh
parent
94d2a79d2e
commit
4c4b0ef013
@@ -12,6 +12,7 @@ import kotlin.reflect.KProperty
|
||||
interface Mapping {
|
||||
val defaultArgumentsDispatchFunction: Delegate<IrFunction, IrFunction>
|
||||
val defaultArgumentsOriginalFunction: Delegate<IrFunction, IrFunction>
|
||||
val suspendFunctionToCoroutineConstructor: Delegate<IrFunction, IrConstructor>
|
||||
val lateInitFieldToNullableField: Delegate<IrField, IrField>
|
||||
val inlineClassMemberToStatic: Delegate<IrFunction, IrSimpleFunction>
|
||||
|
||||
@@ -32,6 +33,7 @@ open class DefaultMapping : Mapping {
|
||||
|
||||
override val defaultArgumentsDispatchFunction: Mapping.Delegate<IrFunction, IrFunction> = newMapping()
|
||||
override val defaultArgumentsOriginalFunction: Mapping.Delegate<IrFunction, IrFunction> = newMapping()
|
||||
override val suspendFunctionToCoroutineConstructor: Mapping.Delegate<IrFunction, IrConstructor> = newMapping()
|
||||
override val lateInitFieldToNullableField: Mapping.Delegate<IrField, IrField> = newMapping()
|
||||
override val inlineClassMemberToStatic: Mapping.Delegate<IrFunction, IrSimpleFunction> = newMapping()
|
||||
|
||||
|
||||
+84
-64
@@ -38,7 +38,9 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val context: C) : FileLoweringPass {
|
||||
abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val context: C) : BodyLoweringPass {
|
||||
|
||||
private var IrFunction.coroutineConstructor by context.mapping.suspendFunctionToCoroutineConstructor
|
||||
|
||||
protected object STATEMENT_ORIGIN_COROUTINE_IMPL : IrStatementOriginImpl("COROUTINE_IMPL")
|
||||
protected object DECLARATION_ORIGIN_COROUTINE_IMPL : IrDeclarationOriginImpl("COROUTINE_IMPL")
|
||||
@@ -61,53 +63,38 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
delegatingCall
|
||||
|
||||
private val builtCoroutines = mutableMapOf<IrFunction, BuiltCoroutine>()
|
||||
private val suspendLambdas = mutableMapOf<IrFunction, IrFunctionReference>()
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
markSuspendLambdas(irFile)
|
||||
buildCoroutines(irFile)
|
||||
transformCallableReferencesToSuspendLambdas(irFile)
|
||||
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||
transformCallableReferencesToSuspendLambdas(irBody)
|
||||
|
||||
if (container.isLambda) return
|
||||
|
||||
tryTransformSuspendFunction(container, null)
|
||||
}
|
||||
|
||||
private fun buildCoroutines(irFile: IrFile) {
|
||||
irFile.transformDeclarationsFlat(::tryTransformSuspendFunction)
|
||||
irFile.acceptVoid(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
declaration.acceptChildrenVoid(this)
|
||||
declaration.transformDeclarationsFlat(::tryTransformSuspendFunction)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Suppress since it is used in native
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
protected fun IrCall.isReturnIfSuspendedCall() =
|
||||
symbol.owner.run { fqNameWhenAvailable == context.internalPackageFqn.child(Name.identifier("returnIfSuspended")) }
|
||||
|
||||
private fun tryTransformSuspendFunction(element: IrElement) =
|
||||
if (element is IrSimpleFunction && element.isSuspend && element.modality != Modality.ABSTRACT)
|
||||
transformSuspendFunction(element, suspendLambdas[element])
|
||||
else null
|
||||
private fun tryTransformSuspendFunction(element: IrElement, functionReference: IrFunctionReference?) {
|
||||
if (element is IrSimpleFunction && element.isSuspend && element.modality != Modality.ABSTRACT) {
|
||||
|
||||
private fun markSuspendLambdas(irElement: IrElement) {
|
||||
irElement.acceptChildrenVoid(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
transformCallableReferencesToSuspendLambdas(element)
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference) {
|
||||
expression.acceptChildrenVoid(this)
|
||||
transformSuspendFunction(element, functionReference)?.let { result ->
|
||||
result.forEach { declaration ->
|
||||
if (declaration !== element) {
|
||||
// TODO Use proper means to emerge declarations
|
||||
element.file.declarations += declaration
|
||||
declaration.parent = element.file
|
||||
|
||||
if (expression.isSuspend) {
|
||||
suspendLambdas[expression.symbol.owner] = expression
|
||||
// TODO investigate IrJsCodegenBoxTestGenerated$Coroutines$ControlFlow.testBreakFinally_1_3
|
||||
declaration.patchDeclarationParents(declaration.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformCallableReferencesToSuspendLambdas(irElement: IrElement) {
|
||||
@@ -118,16 +105,33 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
|
||||
if (!expression.isSuspend)
|
||||
return expression
|
||||
val coroutine = builtCoroutines[expression.symbol.owner]
|
||||
?: throw Error("Non-local callable reference to suspend lambda: $expression")
|
||||
val constructorParameters = coroutine.coroutineConstructor.valueParameters
|
||||
|
||||
val coroutineConstructor = if (expression.symbol.owner.isLambda) {
|
||||
if (expression.symbol.owner in builtCoroutines) {
|
||||
error("Lambda revisiting?")
|
||||
}
|
||||
|
||||
tryTransformSuspendFunction(expression.symbol.owner, expression)
|
||||
|
||||
builtCoroutines[expression.symbol.owner]?.coroutineConstructor
|
||||
?: throw Error("Non-local callable reference to suspend lambda: $expression")
|
||||
} else {
|
||||
expression.symbol.owner.coroutineConstructor ?: run {
|
||||
|
||||
tryTransformSuspendFunction(expression.symbol.owner, expression)
|
||||
|
||||
builtCoroutines[expression.symbol.owner]!!.coroutineConstructor
|
||||
}
|
||||
}
|
||||
|
||||
val constructorParameters = coroutineConstructor.valueParameters
|
||||
val expressionArguments = expression.getArguments().map { it.second }
|
||||
assert(constructorParameters.size == expressionArguments.size) {
|
||||
"Inconsistency between callable reference to suspend lambda and the corresponding coroutine"
|
||||
}
|
||||
val irBuilder = context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset)
|
||||
irBuilder.run {
|
||||
return irCall(coroutine.coroutineConstructor.symbol).apply {
|
||||
return irCall(coroutineConstructor.symbol).apply {
|
||||
expressionArguments.forEachIndexed { index, argument ->
|
||||
putValueArgument(index, argument)
|
||||
}
|
||||
@@ -144,7 +148,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
}
|
||||
|
||||
private fun transformSuspendFunction(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?) =
|
||||
when (val suspendFunctionKind = getSuspendFunctionKind(irFunction)) {
|
||||
when (val suspendFunctionKind = getSuspendFunctionKind(irFunction, functionReference)) {
|
||||
is SuspendFunctionKind.NO_SUSPEND_CALLS -> {
|
||||
null // No suspend function calls - just an ordinary function.
|
||||
}
|
||||
@@ -156,15 +160,15 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
|
||||
is SuspendFunctionKind.NEEDS_STATE_MACHINE -> {
|
||||
val coroutine = buildCoroutine(irFunction, functionReference) // Coroutine implementation.
|
||||
if (irFunction in suspendLambdas) // Suspend lambdas are called through factory method <create>,
|
||||
if (irFunction.isLambda) // Suspend lambdas are called through factory method <create>,
|
||||
listOf(coroutine) // thus we can eliminate original body.
|
||||
else
|
||||
listOf<IrDeclaration>(coroutine, irFunction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSuspendFunctionKind(irFunction: IrSimpleFunction): SuspendFunctionKind {
|
||||
if (irFunction in suspendLambdas)
|
||||
private fun getSuspendFunctionKind(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?): SuspendFunctionKind {
|
||||
if (irFunction.isLambda || functionReference != null)
|
||||
return SuspendFunctionKind.NEEDS_STATE_MACHINE // Suspend lambdas always need coroutine implementation.
|
||||
|
||||
val body = irFunction.body ?: return SuspendFunctionKind.NO_SUSPEND_CALLS
|
||||
@@ -237,29 +241,33 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
private fun buildCoroutine(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?): IrClass {
|
||||
val coroutine = CoroutineBuilder(irFunction, functionReference).build()
|
||||
builtCoroutines[irFunction] = coroutine
|
||||
irFunction.coroutineConstructor = coroutine.coroutineConstructor
|
||||
|
||||
if (functionReference == null) {
|
||||
if (!irFunction.isLambda && functionReference == null) {
|
||||
// It is not a lambda - replace original function with a call to constructor of the built coroutine.
|
||||
val irBuilder = context.createIrBuilder(irFunction.symbol, irFunction.startOffset, irFunction.endOffset)
|
||||
irFunction.body = irBuilder.irBlockBody(irFunction) {
|
||||
val constructor = coroutine.coroutineConstructor
|
||||
generateCoroutineStart(coroutine.stateMachineFunction,
|
||||
irCallConstructor(constructor.symbol, irFunction.typeParameters.map {
|
||||
IrSimpleTypeImpl(it.symbol, true, emptyList(), emptyList())
|
||||
}).apply {
|
||||
val functionParameters = irFunction.explicitParameters
|
||||
functionParameters.forEachIndexed { index, argument ->
|
||||
putValueArgument(index, irGet(argument))
|
||||
}
|
||||
putValueArgument(
|
||||
functionParameters.size,
|
||||
irCall(
|
||||
getContinuationSymbol,
|
||||
getContinuationSymbol.owner.returnType,
|
||||
listOf(irFunction.returnType)
|
||||
(irFunction.body as IrBlockBody).statements.let {
|
||||
it.clear()
|
||||
it += irBuilder.irBlockBody(irFunction) {
|
||||
val constructor = coroutine.coroutineConstructor
|
||||
generateCoroutineStart(coroutine.stateMachineFunction,
|
||||
irCallConstructor(constructor.symbol, irFunction.typeParameters.map {
|
||||
IrSimpleTypeImpl(it.symbol, true, emptyList(), emptyList())
|
||||
}).apply {
|
||||
val functionParameters = irFunction.explicitParameters
|
||||
functionParameters.forEachIndexed { index, argument ->
|
||||
putValueArgument(index, irGet(argument))
|
||||
}
|
||||
putValueArgument(
|
||||
functionParameters.size,
|
||||
irCall(
|
||||
getContinuationSymbol,
|
||||
getContinuationSymbol.owner.returnType,
|
||||
listOf(irFunction.returnType)
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
}.statements
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,12 +412,14 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
coroutineConstructors += this
|
||||
|
||||
valueParameters = functionParameters.mapIndexed { index, parameter ->
|
||||
parameter.copyTo(this, DECLARATION_ORIGIN_COROUTINE_IMPL, index)
|
||||
parameter.copyTo(this, DECLARATION_ORIGIN_COROUTINE_IMPL, index, defaultValue = null)
|
||||
}
|
||||
val continuationParameter = coroutineBaseClassConstructor.valueParameters[0]
|
||||
valueParameters += continuationParameter.copyTo(
|
||||
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
|
||||
index = valueParameters.size, type = continuationType
|
||||
index = valueParameters.size,
|
||||
type = continuationType,
|
||||
defaultValue = null
|
||||
)
|
||||
|
||||
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
|
||||
@@ -695,3 +705,13 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val IrDeclaration.isLambda
|
||||
get() = origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
|
||||
|
||||
class RemoveSuspendLambdas() : DeclarationTransformer {
|
||||
|
||||
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
|
||||
return if (declaration.isLambda && declaration is IrFunction && declaration.isSuspend) emptyList() else null
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import org.jetbrains.kotlin.ir.backend.js.lower.inline.CopyInlineFunctionBodyLow
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.inline.RemoveInlineFunctionsWithReifiedTypeParametersLowering
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
||||
|
||||
private fun DeclarationContainerLoweringPass.runOnFilesPostfix(files: Iterable<IrFile>) = files.forEach { runOnFilePostfix(it) }
|
||||
|
||||
@@ -283,6 +282,12 @@ private val suspendFunctionsLoweringPhase = makeJsModulePhase(
|
||||
description = "Transform suspend functions into CoroutineImpl instance and build state machine"
|
||||
)
|
||||
|
||||
private val suspendLambdasRemovalLoweringPhase = makeJsModulePhase(
|
||||
{ RemoveSuspendLambdas() },
|
||||
name = "RemoveSuspendLambdas",
|
||||
description = "Remove suspend lambdas"
|
||||
)
|
||||
|
||||
private val privateMembersLoweringPhase = makeJsModulePhase(
|
||||
::PrivateMembersLowering,
|
||||
name = "PrivateMembersLowering",
|
||||
@@ -534,6 +539,7 @@ val jsPhases = namedIrModulePhase(
|
||||
enumUsageLoweringPhase then
|
||||
enumEntryRemovalLoweringPhase then
|
||||
suspendFunctionsLoweringPhase then
|
||||
suspendLambdasRemovalLoweringPhase then
|
||||
returnableBlockLoweringPhase then
|
||||
forLoopsLoweringPhase then
|
||||
primitiveCompanionLoweringPhase then
|
||||
|
||||
Vendored
+1
@@ -1,4 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_COROUTINES
|
||||
// WITH_RUNTIME
|
||||
|
||||
|
||||
Reference in New Issue
Block a user