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:
Anton Bannykh
2019-12-25 15:45:34 +03:00
committed by Anton Bannykh
parent 94d2a79d2e
commit 4c4b0ef013
5 changed files with 95 additions and 65 deletions
@@ -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()
@@ -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
@@ -1,4 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JS_IR
// WITH_RUNTIME
// WITH_COROUTINES
@@ -1,4 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JS_IR
// WITH_COROUTINES
// WITH_RUNTIME