[JVM_IR] Support suspend functions that can be overriden.

The challenge for overridable suspend functions is that the calling
the state machine method to resume after suspension would be
virtually dispatced to the wrong method. To avoid that a static
suspend implementation method is generated which becomes the
state machine method used to resume.
This commit is contained in:
Mads Ager
2019-10-21 12:34:21 +02:00
committed by Ilmir Usmanov
parent eebb071ae9
commit 8b97819c04
9 changed files with 101 additions and 26 deletions
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrTypeParameterImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
@@ -36,7 +33,9 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
import java.io.StringWriter import java.io.StringWriter
@@ -509,7 +508,8 @@ fun createStaticFunctionWithReceivers(
oldFunction: IrFunction, oldFunction: IrFunction,
dispatchReceiverType: IrType? = oldFunction.dispatchReceiverParameter?.type, dispatchReceiverType: IrType? = oldFunction.dispatchReceiverParameter?.type,
origin: IrDeclarationOrigin = oldFunction.origin, origin: IrDeclarationOrigin = oldFunction.origin,
modality: Modality = Modality.FINAL modality: Modality = Modality.FINAL,
copyMetadata: Boolean = true
): IrSimpleFunction { ): IrSimpleFunction {
val descriptor = (oldFunction.descriptor as? DescriptorWithContainerSource)?.let { val descriptor = (oldFunction.descriptor as? DescriptorWithContainerSource)?.let {
WrappedFunctionDescriptorWithContainerSource(it.containerSource) WrappedFunctionDescriptorWithContainerSource(it.containerSource)
@@ -551,7 +551,7 @@ fun createStaticFunctionWithReceivers(
oldFunction.valueParameters.map { it.copyTo(this, index = it.index + offset) } oldFunction.valueParameters.map { it.copyTo(this, index = it.index + offset) }
) )
metadata = oldFunction.metadata if (copyMetadata) metadata = oldFunction.metadata
} }
} }
@@ -560,6 +560,28 @@ fun copyBodyToStatic(oldFunction: IrFunction, staticFunction: IrFunction) {
(listOfNotNull(oldFunction.dispatchReceiverParameter, oldFunction.extensionReceiverParameter) + oldFunction.valueParameters) (listOfNotNull(oldFunction.dispatchReceiverParameter, oldFunction.extensionReceiverParameter) + oldFunction.valueParameters)
.zip(staticFunction.valueParameters).toMap() .zip(staticFunction.valueParameters).toMap()
staticFunction.body = oldFunction.body staticFunction.body = oldFunction.body
?.transform(VariableRemapper(mapping), null) ?.transform(
?.patchDeclarationParents(staticFunction) object: IrElementTransformerVoid() {
// Remap return targets to the static method so they do not appear to be
// non-local returns.
override fun visitReturn(expression: IrReturn): IrExpression {
expression.transformChildrenVoid(this);
return if (expression.returnTargetSymbol == oldFunction.symbol) {
IrReturnImpl(
expression.startOffset,
expression.endOffset,
expression.type,
staticFunction.symbol,
expression.value)
} else expression
}
// Remap argument values.
override fun visitGetValue(expression: IrGetValue): IrExpression =
mapping[expression.symbol.owner]?.let {
IrGetValueImpl(expression.startOffset, expression.endOffset, it.type, it.symbol, expression.origin)
} ?: expression
}, null)
?.patchDeclarationParents(staticFunction)
} }
@@ -44,4 +44,5 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD") object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true) object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true)
object GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD : IrDeclarationOriginImpl("GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD", isSynthetic = true) object GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD : IrDeclarationOriginImpl("GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD", isSynthetic = true)
object SUSPEND_IMPL_STATIC_FUNCTION : IrDeclarationOriginImpl("SUSPEND_IMPL_STATIC_FUNCTION", isSynthetic = true)
} }
@@ -66,7 +66,8 @@ internal fun generateStateMachineForNamedFunction(
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization, shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
containingClassInternalName = classCodegen.visitor.thisName, containingClassInternalName = classCodegen.visitor.thisName,
isForNamedFunction = true, isForNamedFunction = true,
needDispatchReceiver = irFunction.dispatchReceiverParameter != null, needDispatchReceiver = irFunction.dispatchReceiverParameter != null
|| irFunction.origin == JvmLoweredDeclarationOrigin.SUSPEND_IMPL_STATIC_FUNCTION,
internalNameForDispatchReceiver = classCodegen.visitor.thisName, internalNameForDispatchReceiver = classCodegen.visitor.thisName,
putContinuationParameterToLvt = false putContinuationParameterToLvt = false
) )
@@ -186,7 +187,7 @@ internal fun IrCall.createSuspendFunctionCallViewIfNeeded(
if (!isSuspend) return this if (!isSuspend) return this
val view = (symbol.owner as IrSimpleFunction).getOrCreateSuspendFunctionViewIfNeeded(context) val view = (symbol.owner as IrSimpleFunction).getOrCreateSuspendFunctionViewIfNeeded(context)
if (view == symbol.owner) return this if (view == symbol.owner) return this
return IrCallImpl(startOffset, endOffset, view.returnType, view.symbol).also { return IrCallImpl(startOffset, endOffset, view.returnType, view.symbol, superQualifierSymbol = superQualifierSymbol).also {
it.copyTypeArgumentsFrom(this) it.copyTypeArgumentsFrom(this)
it.dispatchReceiver = dispatchReceiver it.dispatchReceiver = dispatchReceiver
it.extensionReceiver = extensionReceiver it.extensionReceiver = extensionReceiver
@@ -7,16 +7,14 @@ package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.common.ir.copyTo import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.ir.isSuspend
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.parents import org.jetbrains.kotlin.backend.common.lower.parents
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrBlock import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrBlock
import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeOfSuspendCallableReference import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeOfSuspendCallableReference
import org.jetbrains.kotlin.codegen.coroutines.* import org.jetbrains.kotlin.codegen.coroutines.*
@@ -364,9 +362,15 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
val capturedThisField = irFunction.dispatchReceiverParameter?.let { addField("this\$0", it.type) } val capturedThisField = irFunction.dispatchReceiverParameter?.let { addField("this\$0", it.type) }
val labelField = addField(COROUTINE_LABEL_FIELD_NAME, context.irBuiltIns.intType, JavaVisibilities.PACKAGE_VISIBILITY) val labelField = addField(COROUTINE_LABEL_FIELD_NAME, context.irBuiltIns.intType, JavaVisibilities.PACKAGE_VISIBILITY)
addConstructorForNamedFunction(capturedThisField) addConstructorForNamedFunction(capturedThisField)
addInvokeSuspendForNamedFunction(irFunction, resultField, labelField, capturedThisField) var function = irFunction
if (function is IrSimpleFunction && function.isOverridable && function.body != null) {
context.suspendFunctionContinuations[irFunction] = this // Create static method for the suspend state machine method so that reentering the method
// does not lead to virtual dispatch to the wrong method.
context.suspendFunctionContinuations[function] = this
function = createStaticSuspendImpl(function)
}
addInvokeSuspendForNamedFunction(function, resultField, labelField, capturedThisField, function != irFunction)
context.suspendFunctionContinuations[function] = this
} }
} }
@@ -388,11 +392,19 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
} }
} }
private fun Name.toSuspendImplementationName() = when {
isSpecial -> Name.special(asString() + SUSPEND_IMPL_NAME_SUFFIX)
else -> Name.identifier(asString() + SUSPEND_IMPL_NAME_SUFFIX)
}
private fun IrClass.addInvokeSuspendForNamedFunction( private fun IrClass.addInvokeSuspendForNamedFunction(
irFunction: IrFunction, irFunction: IrFunction,
resultField: IrField, resultField: IrField,
labelField: IrField, labelField: IrField,
capturedThisField: IrField? capturedThisField: IrField?,
isStaticSuspendImpl: Boolean
) { ) {
val invokeSuspend = continuationImpl.owner.functions.single { it.name == Name.identifier(INVOKE_SUSPEND_METHOD_NAME) } val invokeSuspend = continuationImpl.owner.functions.single { it.name == Name.identifier(INVOKE_SUSPEND_METHOD_NAME) }
addFunctionOverride(invokeSuspend).also { function -> addFunctionOverride(invokeSuspend).also { function ->
@@ -416,26 +428,69 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
irInt(signBit) irInt(signBit)
) )
) )
+irReturn(irCall(irFunction).also { +irReturn(irCall(irFunction).also {
for (i in irFunction.typeParameters.indices) { for (i in irFunction.typeParameters.indices) {
it.putTypeArgument(i, context.irBuiltIns.anyNType) it.putTypeArgument(i, context.irBuiltIns.anyNType)
} }
it.dispatchReceiver = capturedThisField?.let { irField -> val capturedThisValue = capturedThisField?.let { irField ->
irGetField(irGet(function.dispatchReceiverParameter!!), irField) irGetField(irGet(function.dispatchReceiverParameter!!), irField)
} }
if (irFunction.dispatchReceiverParameter != null) {
it.dispatchReceiver = capturedThisValue
}
if (irFunction.extensionReceiverParameter != null) { if (irFunction.extensionReceiverParameter != null) {
it.extensionReceiver = irNull() it.extensionReceiver = irNull()
} }
for ((i, parameter) in irFunction.valueParameters.withIndex()) { for ((i, parameter) in irFunction.valueParameters.withIndex()) {
val defaultValueForParameter = IrConstImpl.defaultValueForType( val defaultValueForParameter = IrConstImpl.defaultValueForType(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, parameter.type) UNDEFINED_OFFSET, UNDEFINED_OFFSET, parameter.type
)
it.putValueArgument(i, defaultValueForParameter) it.putValueArgument(i, defaultValueForParameter)
} }
if (isStaticSuspendImpl) {
it.putValueArgument(0, capturedThisValue)
}
}) })
} }
} }
} }
private fun createStaticSuspendImpl(irFunction: IrSimpleFunction): IrSimpleFunction {
// Create static suspend impl method.
val static = createStaticFunctionWithReceivers(
irFunction.parent,
irFunction.name.toSuspendImplementationName(),
irFunction,
origin = JvmLoweredDeclarationOrigin.SUSPEND_IMPL_STATIC_FUNCTION,
copyMetadata = false
)
copyBodyToStatic(irFunction, static)
(irFunction.parent as IrClass).declarations.add(static)
// Rewrite the body of the original suspend method to forward to the new static method.
irFunction.body = context.createIrBuilder(irFunction.symbol).irBlockBody {
+irReturn(irCall(static).also {
for (i in irFunction.typeParameters.indices) {
it.putTypeArgument(i, context.irBuiltIns.anyNType)
}
var i = 0
if (irFunction.dispatchReceiverParameter != null) {
it.putValueArgument(i++, irGet(irFunction.dispatchReceiverParameter!!))
}
if (irFunction.extensionReceiverParameter != null) {
it.putValueArgument(i++, irNull())
}
for (parameter in irFunction.valueParameters) {
val defaultValueForParameter = IrConstImpl.defaultValueForType(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, parameter.type
)
it.putValueArgument(i++, defaultValueForParameter)
}
})
}
return static
}
// TODO: Generate two copies of inline suspend functions // TODO: Generate two copies of inline suspend functions
private fun markSuspendFunctions(irFile: IrFile, suspendLambdas: Set<IrFunction>): Set<IrFunction> { private fun markSuspendFunctions(irFile: IrFile, suspendLambdas: Set<IrFunction>): Set<IrFunction> {
val result = hashSetOf<IrFunction>() val result = hashSetOf<IrFunction>()
@@ -79,8 +79,8 @@ class IrCallImpl(
descriptor.valueParameters.size, origin, superQualifierSymbol descriptor.valueParameters.size, origin, superQualifierSymbol
) )
constructor(startOffset: Int, endOffset: Int, type: IrType, symbol: IrFunctionSymbol) : constructor(startOffset: Int, endOffset: Int, type: IrType, symbol: IrFunctionSymbol, superQualifierSymbol : IrClassSymbol? = null) :
this(startOffset, endOffset, type, symbol, symbol.descriptor) this(startOffset, endOffset, type, symbol, symbol.descriptor, superQualifierSymbol = superQualifierSymbol)
override val superQualifier: ClassDescriptor? = superQualifierSymbol?.descriptor override val superQualifier: ClassDescriptor? = superQualifierSymbol?.descriptor
@@ -1,5 +1,4 @@
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
// FILE: main.kt // FILE: main.kt
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
// COMMON_COROUTINES_TEST // COMMON_COROUTINES_TEST
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
// COMMON_COROUTINES_TEST // COMMON_COROUTINES_TEST