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 {
|
interface Mapping {
|
||||||
val defaultArgumentsDispatchFunction: Delegate<IrFunction, IrFunction>
|
val defaultArgumentsDispatchFunction: Delegate<IrFunction, IrFunction>
|
||||||
val defaultArgumentsOriginalFunction: Delegate<IrFunction, IrFunction>
|
val defaultArgumentsOriginalFunction: Delegate<IrFunction, IrFunction>
|
||||||
|
val suspendFunctionToCoroutineConstructor: Delegate<IrFunction, IrConstructor>
|
||||||
val lateInitFieldToNullableField: Delegate<IrField, IrField>
|
val lateInitFieldToNullableField: Delegate<IrField, IrField>
|
||||||
val inlineClassMemberToStatic: Delegate<IrFunction, IrSimpleFunction>
|
val inlineClassMemberToStatic: Delegate<IrFunction, IrSimpleFunction>
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ open class DefaultMapping : Mapping {
|
|||||||
|
|
||||||
override val defaultArgumentsDispatchFunction: Mapping.Delegate<IrFunction, IrFunction> = newMapping()
|
override val defaultArgumentsDispatchFunction: Mapping.Delegate<IrFunction, IrFunction> = newMapping()
|
||||||
override val defaultArgumentsOriginalFunction: 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 lateInitFieldToNullableField: Mapping.Delegate<IrField, IrField> = newMapping()
|
||||||
override val inlineClassMemberToStatic: Mapping.Delegate<IrFunction, IrSimpleFunction> = 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.ir.visitors.*
|
||||||
import org.jetbrains.kotlin.name.Name
|
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 STATEMENT_ORIGIN_COROUTINE_IMPL : IrStatementOriginImpl("COROUTINE_IMPL")
|
||||||
protected object DECLARATION_ORIGIN_COROUTINE_IMPL : IrDeclarationOriginImpl("COROUTINE_IMPL")
|
protected object DECLARATION_ORIGIN_COROUTINE_IMPL : IrDeclarationOriginImpl("COROUTINE_IMPL")
|
||||||
@@ -61,53 +63,38 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
delegatingCall
|
delegatingCall
|
||||||
|
|
||||||
private val builtCoroutines = mutableMapOf<IrFunction, BuiltCoroutine>()
|
private val builtCoroutines = mutableMapOf<IrFunction, BuiltCoroutine>()
|
||||||
private val suspendLambdas = mutableMapOf<IrFunction, IrFunctionReference>()
|
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||||
markSuspendLambdas(irFile)
|
transformCallableReferencesToSuspendLambdas(irBody)
|
||||||
buildCoroutines(irFile)
|
|
||||||
transformCallableReferencesToSuspendLambdas(irFile)
|
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 since it is used in native
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
protected fun IrCall.isReturnIfSuspendedCall() =
|
protected fun IrCall.isReturnIfSuspendedCall() =
|
||||||
symbol.owner.run { fqNameWhenAvailable == context.internalPackageFqn.child(Name.identifier("returnIfSuspended")) }
|
symbol.owner.run { fqNameWhenAvailable == context.internalPackageFqn.child(Name.identifier("returnIfSuspended")) }
|
||||||
|
|
||||||
private fun tryTransformSuspendFunction(element: IrElement) =
|
private fun tryTransformSuspendFunction(element: IrElement, functionReference: IrFunctionReference?) {
|
||||||
if (element is IrSimpleFunction && element.isSuspend && element.modality != Modality.ABSTRACT)
|
if (element is IrSimpleFunction && element.isSuspend && element.modality != Modality.ABSTRACT) {
|
||||||
transformSuspendFunction(element, suspendLambdas[element])
|
|
||||||
else null
|
|
||||||
|
|
||||||
private fun markSuspendLambdas(irElement: IrElement) {
|
transformCallableReferencesToSuspendLambdas(element)
|
||||||
irElement.acceptChildrenVoid(object : IrElementVisitorVoid {
|
|
||||||
override fun visitElement(element: IrElement) {
|
|
||||||
element.acceptChildrenVoid(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitFunctionReference(expression: IrFunctionReference) {
|
transformSuspendFunction(element, functionReference)?.let { result ->
|
||||||
expression.acceptChildrenVoid(this)
|
result.forEach { declaration ->
|
||||||
|
if (declaration !== element) {
|
||||||
|
// TODO Use proper means to emerge declarations
|
||||||
|
element.file.declarations += declaration
|
||||||
|
declaration.parent = element.file
|
||||||
|
|
||||||
if (expression.isSuspend) {
|
// TODO investigate IrJsCodegenBoxTestGenerated$Coroutines$ControlFlow.testBreakFinally_1_3
|
||||||
suspendLambdas[expression.symbol.owner] = expression
|
declaration.patchDeclarationParents(declaration.parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformCallableReferencesToSuspendLambdas(irElement: IrElement) {
|
private fun transformCallableReferencesToSuspendLambdas(irElement: IrElement) {
|
||||||
@@ -118,16 +105,33 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
|
|
||||||
if (!expression.isSuspend)
|
if (!expression.isSuspend)
|
||||||
return expression
|
return expression
|
||||||
val coroutine = builtCoroutines[expression.symbol.owner]
|
|
||||||
?: throw Error("Non-local callable reference to suspend lambda: $expression")
|
val coroutineConstructor = if (expression.symbol.owner.isLambda) {
|
||||||
val constructorParameters = coroutine.coroutineConstructor.valueParameters
|
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 }
|
val expressionArguments = expression.getArguments().map { it.second }
|
||||||
assert(constructorParameters.size == expressionArguments.size) {
|
assert(constructorParameters.size == expressionArguments.size) {
|
||||||
"Inconsistency between callable reference to suspend lambda and the corresponding coroutine"
|
"Inconsistency between callable reference to suspend lambda and the corresponding coroutine"
|
||||||
}
|
}
|
||||||
val irBuilder = context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset)
|
val irBuilder = context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset)
|
||||||
irBuilder.run {
|
irBuilder.run {
|
||||||
return irCall(coroutine.coroutineConstructor.symbol).apply {
|
return irCall(coroutineConstructor.symbol).apply {
|
||||||
expressionArguments.forEachIndexed { index, argument ->
|
expressionArguments.forEachIndexed { index, argument ->
|
||||||
putValueArgument(index, argument)
|
putValueArgument(index, argument)
|
||||||
}
|
}
|
||||||
@@ -144,7 +148,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun transformSuspendFunction(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?) =
|
private fun transformSuspendFunction(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?) =
|
||||||
when (val suspendFunctionKind = getSuspendFunctionKind(irFunction)) {
|
when (val suspendFunctionKind = getSuspendFunctionKind(irFunction, functionReference)) {
|
||||||
is SuspendFunctionKind.NO_SUSPEND_CALLS -> {
|
is SuspendFunctionKind.NO_SUSPEND_CALLS -> {
|
||||||
null // No suspend function calls - just an ordinary function.
|
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 -> {
|
is SuspendFunctionKind.NEEDS_STATE_MACHINE -> {
|
||||||
val coroutine = buildCoroutine(irFunction, functionReference) // Coroutine implementation.
|
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.
|
listOf(coroutine) // thus we can eliminate original body.
|
||||||
else
|
else
|
||||||
listOf<IrDeclaration>(coroutine, irFunction)
|
listOf<IrDeclaration>(coroutine, irFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSuspendFunctionKind(irFunction: IrSimpleFunction): SuspendFunctionKind {
|
private fun getSuspendFunctionKind(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?): SuspendFunctionKind {
|
||||||
if (irFunction in suspendLambdas)
|
if (irFunction.isLambda || functionReference != null)
|
||||||
return SuspendFunctionKind.NEEDS_STATE_MACHINE // Suspend lambdas always need coroutine implementation.
|
return SuspendFunctionKind.NEEDS_STATE_MACHINE // Suspend lambdas always need coroutine implementation.
|
||||||
|
|
||||||
val body = irFunction.body ?: return SuspendFunctionKind.NO_SUSPEND_CALLS
|
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 {
|
private fun buildCoroutine(irFunction: IrSimpleFunction, functionReference: IrFunctionReference?): IrClass {
|
||||||
val coroutine = CoroutineBuilder(irFunction, functionReference).build()
|
val coroutine = CoroutineBuilder(irFunction, functionReference).build()
|
||||||
builtCoroutines[irFunction] = coroutine
|
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.
|
// 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)
|
val irBuilder = context.createIrBuilder(irFunction.symbol, irFunction.startOffset, irFunction.endOffset)
|
||||||
irFunction.body = irBuilder.irBlockBody(irFunction) {
|
(irFunction.body as IrBlockBody).statements.let {
|
||||||
val constructor = coroutine.coroutineConstructor
|
it.clear()
|
||||||
generateCoroutineStart(coroutine.stateMachineFunction,
|
it += irBuilder.irBlockBody(irFunction) {
|
||||||
irCallConstructor(constructor.symbol, irFunction.typeParameters.map {
|
val constructor = coroutine.coroutineConstructor
|
||||||
IrSimpleTypeImpl(it.symbol, true, emptyList(), emptyList())
|
generateCoroutineStart(coroutine.stateMachineFunction,
|
||||||
}).apply {
|
irCallConstructor(constructor.symbol, irFunction.typeParameters.map {
|
||||||
val functionParameters = irFunction.explicitParameters
|
IrSimpleTypeImpl(it.symbol, true, emptyList(), emptyList())
|
||||||
functionParameters.forEachIndexed { index, argument ->
|
}).apply {
|
||||||
putValueArgument(index, irGet(argument))
|
val functionParameters = irFunction.explicitParameters
|
||||||
}
|
functionParameters.forEachIndexed { index, argument ->
|
||||||
putValueArgument(
|
putValueArgument(index, irGet(argument))
|
||||||
functionParameters.size,
|
}
|
||||||
irCall(
|
putValueArgument(
|
||||||
getContinuationSymbol,
|
functionParameters.size,
|
||||||
getContinuationSymbol.owner.returnType,
|
irCall(
|
||||||
listOf(irFunction.returnType)
|
getContinuationSymbol,
|
||||||
|
getContinuationSymbol.owner.returnType,
|
||||||
|
listOf(irFunction.returnType)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
})
|
}.statements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,12 +412,14 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
coroutineConstructors += this
|
coroutineConstructors += this
|
||||||
|
|
||||||
valueParameters = functionParameters.mapIndexed { index, parameter ->
|
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]
|
val continuationParameter = coroutineBaseClassConstructor.valueParameters[0]
|
||||||
valueParameters += continuationParameter.copyTo(
|
valueParameters += continuationParameter.copyTo(
|
||||||
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
|
this, DECLARATION_ORIGIN_COROUTINE_IMPL,
|
||||||
index = valueParameters.size, type = continuationType
|
index = valueParameters.size,
|
||||||
|
type = continuationType,
|
||||||
|
defaultValue = null
|
||||||
)
|
)
|
||||||
|
|
||||||
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
|
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.backend.js.lower.inline.RemoveInlineFunctionsWithReifiedTypeParametersLowering
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
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) }
|
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"
|
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(
|
private val privateMembersLoweringPhase = makeJsModulePhase(
|
||||||
::PrivateMembersLowering,
|
::PrivateMembersLowering,
|
||||||
name = "PrivateMembersLowering",
|
name = "PrivateMembersLowering",
|
||||||
@@ -534,6 +539,7 @@ val jsPhases = namedIrModulePhase(
|
|||||||
enumUsageLoweringPhase then
|
enumUsageLoweringPhase then
|
||||||
enumEntryRemovalLoweringPhase then
|
enumEntryRemovalLoweringPhase then
|
||||||
suspendFunctionsLoweringPhase then
|
suspendFunctionsLoweringPhase then
|
||||||
|
suspendLambdasRemovalLoweringPhase then
|
||||||
returnableBlockLoweringPhase then
|
returnableBlockLoweringPhase then
|
||||||
forLoopsLoweringPhase then
|
forLoopsLoweringPhase then
|
||||||
primitiveCompanionLoweringPhase then
|
primitiveCompanionLoweringPhase then
|
||||||
|
|||||||
Vendored
+1
@@ -1,4 +1,5 @@
|
|||||||
// IGNORE_BACKEND_FIR: JVM_IR
|
// IGNORE_BACKEND_FIR: JVM_IR
|
||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
|
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
// IGNORE_BACKEND_FIR: JVM_IR
|
// IGNORE_BACKEND_FIR: JVM_IR
|
||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user