From fc70455877aa4b21696d84b41635c3eee2f55ddc Mon Sep 17 00:00:00 2001 From: Ilmir Usmanov Date: Fri, 31 Jan 2020 12:35:11 +0100 Subject: [PATCH] JVM_IR: Move suspend function views creation to lowering Now AddContinuationLowering is responsible for both adding continuation classes to suspend functions and adding continuation parameters to them. Because we cannot create a view if inline suspend function is defined in another file, we generate a stub without body when we encounter call to it. And then, when we lower the file containing the function we add the body. This way we have no unlowered views after the lowering. Thus, after the lowering there should be no suspend function, which are not views, therefore, remove VIEW origins. Because transformations of suspend functions can copy them into another object, use attribute as a key inside function to view map. --- .../kotlin/backend/jvm/DeclarationOrigins.kt | 4 +- .../kotlin/backend/jvm/JvmBackendContext.kt | 12 +- .../backend/jvm/codegen/CoroutineCodegen.kt | 131 +--------- .../backend/jvm/codegen/ExpressionCodegen.kt | 44 ++-- .../backend/jvm/codegen/FunctionCodegen.kt | 25 +- .../backend/jvm/codegen/IrInlineCodegen.kt | 14 +- .../jvm/codegen/IrSourceCompilerForInline.kt | 5 +- .../jvm/codegen/MethodSignatureMapper.kt | 12 +- .../jvm/lower/AddContinuationLowering.kt | 239 +++++++++++++++--- .../backend/jvm/lower/BridgeLowering.kt | 11 +- .../lower/FunctionNVarargBridgeLowering.kt | 4 +- .../ir/util/DeepCopyIrTreeWithSymbols.kt | 16 +- .../box/coroutines/suspendDefaultImpl.kt | 1 + .../superCallInterface.kt | 1 + .../tailCallOptimizations/crossinline_ir.txt | 4 +- .../defaultValueCrossinline.kt | 2 - .../defaultParameter/defaultValueInline.kt | 2 - .../defaultValueInlineFromMultiFileFacade.kt | 2 - 18 files changed, 291 insertions(+), 238 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt index 57337b55a53..ec98f18b31f 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -33,7 +33,6 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin { 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_MAIN_FOR_PARAMETERLESS_MAIN_METHOD : IrDeclarationOriginImpl("GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD", isSynthetic = true) - object SUSPEND_FUNCTION_VIEW : IrDeclarationOriginImpl("SUSPEND_FUNCTION_VIEW") object SUSPEND_IMPL_STATIC_FUNCTION : IrDeclarationOriginImpl("SUSPEND_IMPL_STATIC_FUNCTION", isSynthetic = true) object INTERFACE_COMPANION_PRIVATE_INSTANCE : IrDeclarationOriginImpl("INTERFACE_COMPANION_PRIVATE_INSTANCE", isSynthetic = true) object POLYMORPHIC_SIGNATURE_INSTANTIATION : IrDeclarationOriginImpl("POLYMORPHIC_SIGNATURE_INSTANTIATION", isSynthetic = true) @@ -43,6 +42,5 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin { object SUSPEND_LAMBDA : IrDeclarationOriginImpl("SUSPEND_LAMBDA") object FOR_INLINE_STATE_MACHINE_TEMPLATE : IrDeclarationOriginImpl("FOR_INLINE_TEMPLATE") object FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE : IrDeclarationOriginImpl("FOR_INLINE_TEMPLATE_CROSSINLINE") - object FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW : IrDeclarationOriginImpl("FOR_INLINE_TEMPLATE_CROSSINLINE_VIEW") object COMPANION_PROPERTY_BACKING_FIELD : IrDeclarationOriginImpl("COMPANION_MOVED_PROPERTY_BACKING_FIELD") } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index 594ff1bf6dc..ed12a4c1949 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.jvm.descriptors.JvmSharedVariablesManager import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.MemoizedInlineClassReplacements +import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.inline.NameGenerator import org.jetbrains.kotlin.codegen.state.GenerationState @@ -108,7 +109,7 @@ class JvmBackendContext( val suspendLambdaToOriginalFunctionMap = mutableMapOf() val continuationClassBuilders = mutableMapOf() val suspendFunctionOriginalToView = mutableMapOf() - val suspendFunctionViewToOriginal = mutableMapOf() + val suspendFunctionOriginalToStub = mutableMapOf() val suspendTailCallsWithUnitReplacement = mutableSetOf() val fakeContinuation: IrExpression = createFakeContinuation(this) @@ -117,8 +118,13 @@ class JvmBackendContext( val inlineClassReplacements = MemoizedInlineClassReplacements() internal fun recordSuspendFunctionView(function: IrFunction, view: IrFunction) { - suspendFunctionOriginalToView[function] = view - suspendFunctionViewToOriginal[view] = function + val attribute = function.suspendFunctionOriginal() + suspendFunctionOriginalToStub.remove(attribute) + suspendFunctionOriginalToView[attribute] = view + } + + internal fun recordSuspendFunctionViewStub(function: IrFunction, stub: IrFunction) { + suspendFunctionOriginalToStub[function.suspendFunctionOriginal()] = stub } internal fun referenceClass(descriptor: ClassDescriptor): IrClassSymbol = diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt index 46a84bb927d..3c738c73c3e 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt @@ -6,36 +6,29 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.common.CodegenUtil -import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom -import org.jetbrains.kotlin.backend.common.ir.copyValueParametersInsertingContinuationFrom -import org.jetbrains.kotlin.backend.common.ir.isSuspend -import org.jetbrains.kotlin.backend.common.lower.VariableRemapper import org.jetbrains.kotlin.backend.common.lower.allOverridden import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME -import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME import org.jetbrains.kotlin.codegen.coroutines.reportSuspensionPointInsideMonitor import org.jetbrains.kotlin.codegen.inline.addFakeContinuationConstructorCallMarker import org.jetbrains.kotlin.config.isReleaseCoroutines -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET -import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter -import org.jetbrains.kotlin.ir.builders.declarations.buildFunWithDescriptorForInlining -import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.name import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom -import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl import org.jetbrains.kotlin.ir.types.createType import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection import org.jetbrains.kotlin.ir.types.isUnit -import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.ir.util.file +import org.jetbrains.kotlin.ir.util.isSuspend +import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature @@ -71,8 +64,9 @@ internal fun generateStateMachineForNamedFunction( || irFunction.origin == JvmLoweredDeclarationOrigin.SUSPEND_IMPL_STATIC_FUNCTION, internalNameForDispatchReceiver = classCodegen.visitor.thisName, putContinuationParameterToLvt = false, - disableTailCallOptimizationForFunctionReturningUnit = irFunction.returnType.isUnit() && - irFunction.anyOfOverriddenFunctionsReturnsNonUnit() + disableTailCallOptimizationForFunctionReturningUnit = irFunction.suspendFunctionOriginal().let { + it.returnType.isUnit() && it.anyOfOverriddenFunctionsReturnsNonUnit() + } ) } @@ -134,109 +128,6 @@ internal fun IrFunction.isKnownToBeTailCall(): Boolean = internal fun IrFunction.shouldNotContainSuspendMarkers(): Boolean = isInvokeSuspendOfContinuation() || isKnownToBeTailCall() -// Transform `suspend fun foo(params): RetType` into `fun foo(params, $completion: Continuation): Any?` -// the result is called 'view', just to be consistent with old backend. -internal fun IrFunction.getOrCreateSuspendFunctionViewIfNeeded(context: JvmBackendContext): IrFunction { - if (!isSuspend || origin == JvmLoweredDeclarationOrigin.SUSPEND_FUNCTION_VIEW || - origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW - ) return this - return context.suspendFunctionOriginalToView[this] ?: suspendFunctionView(context, true) -} - -fun IrFunction.suspendFunctionView(context: JvmBackendContext, generateBody: Boolean): IrFunction { - require(this.isSuspend && this is IrSimpleFunction) - // For SuspendFunction{N}.invoke we need to generate INVOKEINTERFACE Function{N+1}.invoke(...Ljava/lang/Object;)... - // instead of INVOKEINTERFACE Function{N+1}.invoke(...Lkotlin/coroutines/Continuation;)... - val isInvokeOfNumberedSuspendFunction = (parent as? IrClass)?.defaultType?.isSuspendFunction() == true - // And we need to generate this function for callable references - val isBridgeInvokeOfCallableReference = origin == IrDeclarationOrigin.BRIDGE && - (parent as? IrClass)?.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL - val continuationType = if (isInvokeOfNumberedSuspendFunction || isBridgeInvokeOfCallableReference) - context.irBuiltIns.anyNType - else - context.ir.symbols.continuationClass.createType(false, listOf(makeTypeProjection(returnType, Variance.INVARIANT))) - return buildFunWithDescriptorForInlining(descriptor) { - updateFrom(this@suspendFunctionView) - name = this@suspendFunctionView.name - origin = if (origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE) - JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW - else - JvmLoweredDeclarationOrigin.SUSPEND_FUNCTION_VIEW - returnType = context.irBuiltIns.anyNType - }.also { - it.parent = parent - - it.annotations.addAll(annotations) - - it.copyAttributes(this) - it.copyTypeParametersFrom(this) - - // Copy the value parameters and insert the continuation parameter. The continuation parameter - // goes before the default argument mask(s) and handler for default argument stubs. - // TODO: It would be nice if AddContinuationLowering could insert the continuation argument before default stub generation. - // That would avoid the reshuffling both here and in createSuspendFunctionClassViewIfNeeded. - var continuationValueParam: IrValueParameter? = null - it.copyValueParametersInsertingContinuationFrom(this) { - continuationValueParam = it.addValueParameter(SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME, continuationType) - } - - if (generateBody) { - // Add the suspend function view to the map before transforming the body to make sure - // that recursive suspend functions do not lead to unbounded recursion at compile time. - context.recordSuspendFunctionView(this, it) - - val valueParametersMapping = explicitParameters.zip(it.explicitParameters.filter { it != continuationValueParam }).toMap() - it.body = body?.deepCopyWithSymbols(this) - it.body?.transformChildrenVoid(object : VariableRemapper(valueParametersMapping) { - // Do not cross class boundaries inside functions. Otherwise, callable references will try to access wrong $completion. - override fun visitClass(declaration: IrClass): IrStatement = declaration - - override fun visitCall(expression: IrCall): IrExpression = - super.visitCall(expression.createSuspendFunctionCallViewIfNeeded(context, it, callerIsInlineLambda = false)) - }) - } - } -} - -internal fun IrCall.createSuspendFunctionCallViewIfNeeded( - context: JvmBackendContext, - caller: IrFunction, - callerIsInlineLambda: Boolean -): IrCall { - if (!isSuspend) return this - val view = (symbol.owner as IrSimpleFunction).getOrCreateSuspendFunctionViewIfNeeded(context) - if (view == symbol.owner) return this - return IrCallImpl(startOffset, endOffset, view.returnType, view.symbol, superQualifierSymbol = superQualifierSymbol).also { - it.copyTypeArgumentsFrom(this) - it.dispatchReceiver = dispatchReceiver - it.extensionReceiver = extensionReceiver - // Locate the caller continuation parameter. The continuation parameter is before default argument mask(s) and handler params. - val callerNumberOfMasks = caller.valueParameters.count { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION } - val callerContinuationIndex = caller.valueParameters.size - 1 - (if (callerNumberOfMasks != 0) callerNumberOfMasks + 1 else 0) - val continuationParameter = - when { - caller.isInvokeSuspendOfLambda() || caller.isInvokeSuspendOfContinuation() || - caller.isInvokeSuspendForInlineOfLambda() -> - IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, caller.dispatchReceiverParameter!!.symbol) - callerIsInlineLambda -> context.fakeContinuation - else -> IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, caller.valueParameters[callerContinuationIndex].symbol) - } - // If the suspend function view that we are calling has default parameters, we need to make sure to pass the - // continuation before the default parameter mask(s) and handler. - val numberOfMasks = view.valueParameters.count { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION } - val continuationIndex = valueArgumentsCount - (if (numberOfMasks != 0) numberOfMasks + 1 else 0) - for (i in 0 until continuationIndex) { - it.putValueArgument(i, getValueArgument(i)) - } - it.putValueArgument(continuationIndex, continuationParameter) - if (numberOfMasks != 0) { - for (i in 0 until numberOfMasks + 1) { - it.putValueArgument(valueArgumentsCount + i - 1, getValueArgument(continuationIndex + i)) - } - } - } -} - internal fun createFakeContinuation(context: JvmBackendContext): IrExpression = IrErrorExpressionImpl( UNDEFINED_OFFSET, UNDEFINED_OFFSET, diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index d2cfddf86fc..cd5161d5bfb 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin.* import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.backend.jvm.intrinsics.JavaClassProperty import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry @@ -52,6 +53,7 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext import org.jetbrains.kotlin.types.model.TypeParameterMarker +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.keysToMap @@ -166,7 +168,7 @@ class ExpressionCodegen( // TODO remove fun gen(expression: IrExpression, type: Type, irType: IrType, data: BlockInfo): StackValue { - if (expression === context.fakeContinuation) { + if (expression.attributeOwnerId === context.fakeContinuation) { addFakeContinuationMarker(mv) } else { expression.accept(this, data).coerce(type, irType).materialize() @@ -219,10 +221,11 @@ class ExpressionCodegen( } private fun generateFakeContinuationConstructorIfNeeded() { - if (irFunction.origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW) return + if (irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE) return val continuationClass = classCodegen.irClass.functions.find { - it.name.asString() == irFunction.name.asString().removeSuffix(FOR_INLINE_SUFFIX) - }?.body?.statements?.get(0) ?: error("could not find continuation for ${irFunction.render()}") + it.attributeOwnerId == (irFunction as? IrSimpleFunction)?.attributeOwnerId && + it.name.asString() == irFunction.name.asString().removeSuffix(FOR_INLINE_SUFFIX) + }?.body?.statements?.firstIsInstance() ?: error("could not find continuation for ${irFunction.render()}") generateFakeContinuationConstructorCall( mv, classCodegen.visitor, @@ -254,12 +257,10 @@ class ExpressionCodegen( if (notCallableFromJava) return - // Do not generate non-null checks for suspend function views. When resumed the arguments + // Do not generate non-null checks for suspend functions. When resumed the arguments // will be null and the actual values are taken from the continuation. - val isSuspendFunctionView = irFunction.origin == JvmLoweredDeclarationOrigin.SUSPEND_FUNCTION_VIEW || - irFunction.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW - if (isSuspendFunctionView) + if (irFunction.isSuspend) return irFunction.extensionReceiverParameter?.let { generateNonNullAssertion(it) } @@ -361,10 +362,6 @@ class ExpressionCodegen( override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) = visitStatementContainer(expression, data).coerce(expression.type) - override fun visitCall(expression: IrCall, data: BlockInfo): PromisedValue { - return visitFunctionAccess(expression.createSuspendFunctionCallViewIfNeeded(context, irFunction, inlinedInto != null), data) - } - override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue { classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol) ?.invoke(expression, this, data)?.let { return it } @@ -445,7 +442,7 @@ class ExpressionCodegen( // Check return type of non-lowered suspend call, in order to replace the result of the call with Unit, // otherwise, it would seem like the call returns non-unit upon resume. // See box/coroutines/tailCallOptimization/unit tests. - if (context.suspendFunctionViewToOriginal[expression.symbol.owner]?.returnType?.isUnit() == true) { + if (((expression.symbol.owner as? IrSimpleFunction)?.attributeOwnerId as? IrSimpleFunction)?.returnType?.isUnit() == true) { addReturnsUnitMarker(mv) } @@ -486,11 +483,11 @@ class ExpressionCodegen( // Inside $$forInline functions crossinline calls are (usually) not suspension points, otherwise, flow will be pessimized (dispatchReceiver as? IrGetField)?.symbol?.owner?.origin == LocalDeclarationsLowering.DECLARATION_ORIGIN_FIELD_FOR_CROSSINLINE_CAPTURED_VALUE -> - irFunction.origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE + irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE // The same goes for inline lambdas (dispatchReceiver as? IrGetValue)?.let { it.origin == IrStatementOrigin.VARIABLE_AS_FUNCTION && (it.symbol.owner as? IrValueParameter)?.isNoinline != true - } == true -> irFunction.origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE + } == true -> irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE // Otherwise, this is normal inline call, ergo not a suspension point else -> false } @@ -636,10 +633,21 @@ class ExpressionCodegen( override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue { val returnTarget = expression.returnTargetSymbol.owner - val owner = - (returnTarget as? IrFunction + var owner = + returnTarget as? IrFunction ?: (returnTarget as? IrReturnableBlock)?.inlineFunctionSymbol?.owner - ?: error("Unsupported IrReturnTarget: $returnTarget")).getOrCreateSuspendFunctionViewIfNeeded(context) + ?: error("Unsupported IrReturnTarget: $returnTarget") + // $$forInline and $suspendImpl functions share the same attributes as originals, but the name is different + // fix the return target. TODO: attributes seem to be overloaded. We rely on their uniqueness across transformations to find views + // TODO: but in this case, they are not unique enough. + if ((irFunction.origin == FOR_INLINE_STATE_MACHINE_TEMPLATE || + irFunction.origin == SUSPEND_IMPL_STATIC_FUNCTION || + irFunction.origin == FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE) && + owner.isSuspend && irFunction is IrSimpleFunction && owner is IrSimpleFunction && + irFunction.attributeOwnerId == owner.attributeOwnerId + ) { + owner = irFunction + } //TODO: should be owner != irFunction val isNonLocalReturn = methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt index 213d5117cbb..73321bd23ee 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -53,10 +53,9 @@ open class FunctionCodegen( } private fun doGenerate(): JvmMethodGenericSignature { - val functionView = irFunction.getOrCreateSuspendFunctionViewIfNeeded(context) - val signature = classCodegen.methodSignatureMapper.mapSignatureWithGeneric(functionView) + val signature = classCodegen.methodSignatureMapper.mapSignatureWithGeneric(irFunction) - val flags = calculateMethodFlags(functionView.isStatic) + val flags = calculateMethodFlags(irFunction.isStatic) var methodVisitor = createMethod(flags, signature) if (state.generateParametersMetadata && flags.and(Opcodes.ACC_SYNTHETIC) == 0) { @@ -75,14 +74,14 @@ open class FunctionCodegen( ) } }.genAnnotations( - functionView, + irFunction, signature.asmMethod.returnType, irFunction.returnType ) // Not generating parameter annotations for default stubs fixes KT-7892, though // this certainly looks like a workaround for a javac bug. if (irFunction !is IrConstructor || !irFunction.parentAsClass.shouldNotGenerateConstructorParameterAnnotations()) { - generateParameterAnnotations(functionView, methodVisitor, signature, classCodegen, context) + generateParameterAnnotations(irFunction, methodVisitor, signature, classCodegen, context) } } @@ -107,7 +106,7 @@ open class FunctionCodegen( ) else -> methodVisitor } - ExpressionCodegen(functionView, signature, frameMap, InstructionAdapter(methodVisitor), classCodegen, inlinedInto).generate() + ExpressionCodegen(irFunction, signature, frameMap, InstructionAdapter(methodVisitor), classCodegen, inlinedInto).generate() methodVisitor.visitMaxs(-1, -1) if (irFunction.hasContinuation()) { context.continuationClassBuilders[continuationClass().attributeOwnerId].sure { @@ -151,7 +150,8 @@ open class FunctionCodegen( // $$forInline functions never have a continuation origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE - private fun continuationClass() = irFunction.body!!.statements.firstIsInstance() + private fun continuationClass() = + irFunction.body!!.statements.firstIsInstance() private fun IrFunction.getVisibilityForDefaultArgumentStub(): Int = when (visibility) { @@ -260,17 +260,16 @@ open class FunctionCodegen( private fun createFrameMapWithReceivers(): IrFrameMap { val frameMap = IrFrameMap() - val functionView = irFunction.getOrCreateSuspendFunctionViewIfNeeded(context) if (irFunction is IrConstructor) { frameMap.enterDispatchReceiver(irFunction.constructedClass.thisReceiver!!) - } else if (functionView.dispatchReceiverParameter != null) { - frameMap.enterDispatchReceiver(functionView.dispatchReceiverParameter!!) + } else if (irFunction.dispatchReceiverParameter != null) { + frameMap.enterDispatchReceiver(irFunction.dispatchReceiverParameter!!) } - functionView.extensionReceiverParameter?.let { + irFunction.extensionReceiverParameter?.let { frameMap.enter(it, classCodegen.typeMapper.mapType(it)) } - for (parameter in functionView.valueParameters) { + for (parameter in irFunction.valueParameters) { frameMap.enter(parameter, classCodegen.typeMapper.mapType(parameter.type)) } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index d3f9bc31b20..c2cf9be8fc0 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -1,24 +1,22 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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.backend.jvm.codegen import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionViewOrStub import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter import org.jetbrains.kotlin.backend.jvm.ir.isLambda -import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.inline.* import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrValueParameter -import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.types.IrType @@ -251,7 +249,7 @@ class IrExpressionLambdaImpl( capturedParamDesc(param.name.asString(), typeMapper.mapType(param.type)) } - private val loweredMethod = methodSignatureMapper.mapAsmMethod(function.getOrCreateSuspendFunctionViewIfNeeded(context)) + private val loweredMethod = methodSignatureMapper.mapAsmMethod(function.suspendFunctionViewOrStub(context)) val capturedParamsInDesc: List = if (isBoundCallableReference) { loweredMethod.argumentTypes.take(1) @@ -274,7 +272,7 @@ class IrExpressionLambdaImpl( override val hasDispatchReceiver: Boolean = false override fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor { - return function.getOrCreateSuspendFunctionViewIfNeeded(context).descriptor + return function.suspendFunctionViewOrStub(context).descriptor } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt index 77c1257cfe5..0d11e944822 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrSourceCompilerForInline.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.kotlin.backend.common.CodegenUtil import org.jetbrains.kotlin.backend.common.ir.ir2string +import org.jetbrains.kotlin.backend.jvm.lower.getOrCreateSuspendFunctionViewIfNeeded import org.jetbrains.kotlin.codegen.BaseExpressionCodegen import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.OwnerKind @@ -86,7 +87,7 @@ class IrSourceCompilerForInline( function.parentAsClass.functions.find { it.name.asString() == function.name.asString() + FOR_INLINE_SUFFIX && it.attributeOwnerId == (function as? IrAttributeContainer)?.attributeOwnerId - } ?: function + } ?: function.getOrCreateSuspendFunctionViewIfNeeded(classCodegen.context) else function val functionCodegen = object : FunctionCodegen(forInlineFunction, classCodegen, codegen.takeIf { isLambda }) { override fun createMethod(flags: Int, signature: JvmMethodGenericSignature): MethodVisitor { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt index 437036ce233..4d8376f0904 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor +import org.jetbrains.kotlin.backend.jvm.lower.* import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.JvmCodegenUtil @@ -208,13 +209,6 @@ class MethodSignatureMapper(private val context: JvmBackendContext) { } } - if (function.isSuspend && function.origin != JvmLoweredDeclarationOrigin.SUSPEND_FUNCTION_VIEW && - function.origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE_VIEW && - (function.parent as? IrClass)?.origin != JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL - ) { - return mapSignature(function.suspendFunctionView(context, false), skipGenericSignature) - } - val sw = if (skipGenericSignature) JvmSignatureWriter() else BothSignatureWriter(BothSignatureWriter.Mode.METHOD) typeMapper.writeFormalTypeParameters(function.typeParameters, sw) @@ -300,7 +294,7 @@ class MethodSignatureMapper(private val context: JvmBackendContext) { } fun mapToCallableMethod(caller: IrFunction, expression: IrFunctionAccessExpression): IrCallableMethod { - val callee = expression.symbol.owner.getOrCreateSuspendFunctionViewIfNeeded(context) + val callee = expression.symbol.owner val calleeParent = callee.parentAsClass val owner = typeMapper.mapClass(calleeParent) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt index 8ea93319c87..594c6d956dd 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt @@ -14,7 +14,9 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator import org.jetbrains.kotlin.backend.jvm.ir.defaultValue -import org.jetbrains.kotlin.backend.jvm.codegen.isInlineIrBlock +import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendForInlineOfLambda +import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfContinuation +import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfLambda import org.jetbrains.kotlin.backend.jvm.localDeclarationsPhase import org.jetbrains.kotlin.codegen.coroutines.* import org.jetbrains.kotlin.codegen.inline.coroutines.FOR_INLINE_SUFFIX @@ -28,9 +30,7 @@ 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.expressions.impl.IrGetFieldImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl +import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl @@ -47,19 +47,45 @@ import org.jetbrains.kotlin.util.OperatorNameConventions internal val addContinuationPhase = makeIrFilePhase( ::AddContinuationLowering, "AddContinuation", - "Add continuation classes to suspend functions and transform suspend lambdas into continuations", + "Add continuation classes and parameters to suspend functions and transform suspend lambdas into continuations", prerequisite = setOf(localDeclarationsPhase, tailCallOptimizationPhase) ) private class AddContinuationLowering(private val context: JvmBackendContext) : FileLoweringPass { - override fun lower(irFile: IrFile) { - val (suspendLambdas, inlineLambdas) = markSuspendLambdas(irFile) - transformSuspendFunctions(irFile, (suspendLambdas.map { it.function } + inlineLambdas).toSet()) - transformReferencesToSuspendLambdas(irFile, suspendLambdas) + val suspendLambdas = findSuspendAndInlineLambdas(irFile) + addContinuationObjectAndContinuationParameterToSuspendFunctions(irFile) + transformSuspendLambdasIntoContinuations(irFile, suspendLambdas) + addContinuationParameterToSuspendCallsAndUpdateNonLocalReturns(irFile) } - private fun transformReferencesToSuspendLambdas(irFile: IrFile, suspendLambdas: List) { + private fun addContinuationParameterToSuspendCallsAndUpdateNonLocalReturns(irFile: IrFile) { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + val functionStack = mutableListOf() + + override fun visitFunction(declaration: IrFunction): IrStatement { + functionStack.push(declaration) + return super.visitFunction(declaration).also { functionStack.pop() } + } + + override fun visitCall(expression: IrCall): IrExpression { + // This is a property, no need to add continuation parameter, since this cannot be suspend call + if (functionStack.isEmpty()) return super.visitCall(expression) + val caller = functionStack.peek()!! + return (super.visitCall(expression) as IrCall) + .createSuspendFunctionCallViewIfNeeded(context, caller) + } + + override fun visitReturn(expression: IrReturn): IrExpression { + val ret = super.visitReturn(expression) as IrReturn + val irFunction = expression.returnTargetSymbol.owner as? IrFunction ?: return ret + val targetViewOrStub = irFunction.suspendFunctionViewOrStub(context) + return IrReturnImpl(ret.startOffset, ret.endOffset, ret.type, targetViewOrStub.symbol, ret.value) + } + }) + } + + private fun transformSuspendLambdasIntoContinuations(irFile: IrFile, suspendLambdas: List) { for (lambda in suspendLambdas) { (lambda.function.parent as IrDeclarationContainer).declarations.remove(lambda.function) } @@ -508,9 +534,13 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : UNDEFINED_OFFSET, UNDEFINED_OFFSET, backendContext ) } - for ((i, parameter) in irFunction.valueParameters.withIndex()) { + for ((i, parameter) in irFunction.valueParameters.dropLast(1).withIndex()) { it.putValueArgument(i, parameter.type.defaultValue(UNDEFINED_OFFSET, UNDEFINED_OFFSET, backendContext)) } + it.putValueArgument( + irFunction.valueParameters.size - 1, + IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, function.dispatchReceiverParameter!!.symbol) + ) if (isStaticSuspendImpl) { it.putValueArgument(0, capturedThisValue) } @@ -529,6 +559,7 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : copyMetadata = false ) static.body = irFunction.moveBodyTo(static) + static.copyAttributes(irFunction) // 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 { @@ -547,11 +578,10 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : } }) } - static.copyAttributes(irFunction) return static } - private fun transformSuspendFunctions(irFile: IrFile, suspendAndInlineLambdas: Set) { + private fun addContinuationObjectAndContinuationParameterToSuspendFunctions(irFile: IrFile) { irFile.transformChildrenVoid(object : IrElementTransformerVoid() { private val functionsStack = arrayListOf() private val suspendFunctionsCapturingCrossinline = mutableSetOf() @@ -573,39 +603,45 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : functionsStack.pop() if (skip(function)) return function - function as IrSimpleFunction + // TODO: This does not work for DEFAULT_IMPLS + val view = + (if (function.body == null) function.suspendFunctionStub(context, false) + else function.getOrCreateSuspendFunctionViewIfNeeded(context)) as IrSimpleFunction + + if (withoutContinuationClass(function)) return view + if (function in suspendFunctionsCapturingCrossinline || function.isInline) { - val newFunction = buildFunWithDescriptorForInlining(function.descriptor) { - name = Name.identifier(function.name.asString() + FOR_INLINE_SUFFIX) - returnType = function.returnType - modality = function.modality - isSuspend = function.isSuspend - isInline = function.isInline + val newFunction = buildFunWithDescriptorForInlining(view.descriptor) { + name = Name.identifier(view.name.asString() + FOR_INLINE_SUFFIX) + returnType = view.returnType + modality = view.modality + isSuspend = view.isSuspend + isInline = view.isInline origin = - if (function.isInline) JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE + if (view.isInline) JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE else JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE }.apply { - copyTypeParameters(function.typeParameters) - dispatchReceiverParameter = function.dispatchReceiverParameter?.copyTo(this) - extensionReceiverParameter = function.extensionReceiverParameter?.copyTo(this) - function.valueParameters.mapTo(valueParameters) { it.copyTo(this) } - body = function.copyBodyTo(this) - copyAttributes(function) + copyTypeParameters(view.typeParameters) + dispatchReceiverParameter = view.dispatchReceiverParameter?.copyTo(this) + extensionReceiverParameter = view.extensionReceiverParameter?.copyTo(this) + view.valueParameters.mapTo(valueParameters) { it.copyTo(this) } + body = view.copyBodyTo(this) + copyAttributes(view) } registerNewFunction(newFunction) } - val newFunction = if (function.isOverridable) { + val newFunction = if ((function as IrSimpleFunction).isOverridable) { // Create static method for the suspend state machine method so that reentering the method // does not lead to virtual dispatch to the wrong method. - registerNewFunction(function) - createStaticSuspendImpl(function) - } else function + registerNewFunction(view) + createStaticSuspendImpl(view) + } else view newFunction.body = context.createIrBuilder(newFunction.symbol).irBlockBody { +generateContinuationClassForNamedFunction( newFunction, - function.dispatchReceiverParameter, + view.dispatchReceiverParameter, declaration as IrAttributeContainer ) for (statement in newFunction.body!!.statements) { @@ -615,11 +651,16 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : return newFunction } - private fun skip(function: IrFunction) = - !function.isSuspend || function in suspendAndInlineLambdas || function.body == null || - function.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE || + // TODO: Merge with `knownToBeTailCall` + private fun withoutContinuationClass(function: IrFunction): Boolean = + function.body == null || + function.parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL || function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || - function.parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL + function.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE + + private fun skip(function: IrFunction) = + !function.isSuspend || + function.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA private fun registerNewFunction(function: IrFunction) { functionsToAdd.peek()!!.add(function) @@ -638,7 +679,7 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : }) } - private fun markSuspendLambdas(irElement: IrElement): Pair, List> { + private fun findSuspendAndInlineLambdas(irElement: IrElement): List { val suspendLambdas = arrayListOf() val capturesCrossinline = mutableSetOf() val inlineReferences = mutableSetOf() @@ -678,7 +719,7 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : } } }) - return suspendLambdas to inlineReferences.map { it.symbol.owner as IrFunction } + return suspendLambdas } private class SuspendLambdaInfo( @@ -689,4 +730,126 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : ) { lateinit var constructor: IrConstructor } +} + +// Transform `suspend fun foo(params): RetType` into `fun foo(params, $completion: Continuation): Any?` +// the result is called 'view', just to be consistent with old backend. +internal fun IrFunction.getOrCreateSuspendFunctionViewIfNeeded(context: JvmBackendContext): IrFunction { + if (!isSuspend) return this + return context.suspendFunctionOriginalToView[suspendFunctionOriginal()] ?: suspendFunctionView(context) +} + +private fun IrFunction.getOrCreateSuspendFunctionStub(context: JvmBackendContext): IrFunction { + if (!isSuspend) return this + return context.suspendFunctionOriginalToStub[suspendFunctionOriginal()] ?: suspendFunctionStub(context) +} + +internal fun IrFunction.suspendFunctionOriginal(): IrFunction { + require(isSuspend && this is IrSimpleFunction) + return attributeOwnerId as IrFunction +} + +private fun IrFunction.suspendFunctionStub(context: JvmBackendContext): IrFunction { + require(this.isSuspend && this is IrSimpleFunction) + return buildFunWithDescriptorForInlining(descriptor) { + updateFrom(this@suspendFunctionStub) + name = this@suspendFunctionStub.name + origin = this@suspendFunctionStub.origin + returnType = context.irBuiltIns.anyNType + }.also { function -> + function.parent = parent + + function.annotations.addAll(annotations) + function.metadata = metadata + + function.copyAttributes(this) + function.copyTypeParametersFrom(this) + + if (origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE) { + function.overriddenSymbols + .addAll(overriddenSymbols.map { it.owner.suspendFunctionViewOrStub(context).symbol as IrSimpleFunctionSymbol }) + } + + // Copy the value parameters and insert the continuation parameter. The continuation parameter + // goes before the default argument mask(s) and handler for default argument stubs. + // TODO: It would be nice if AddContinuationLowering could insert the continuation argument before default stub generation. + // That would avoid the reshuffling both here and in createSuspendFunctionCallViewIfNeeded. + function.copyValueParametersInsertingContinuationFrom(this) { + function.addValueParameter(SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME, continuationType(context)) + } + + context.recordSuspendFunctionViewStub(this, function) + } +} + +private fun IrFunction.continuationType(context: JvmBackendContext): IrType { + // For SuspendFunction{N}.invoke we need to generate INVOKEINTERFACE Function{N+1}.invoke(...Ljava/lang/Object;)... + // instead of INVOKEINTERFACE Function{N+1}.invoke(...Lkotlin/coroutines/Continuation;)... + val isInvokeOfNumberedSuspendFunction = (parent as? IrClass)?.defaultType?.isSuspendFunction() == true + val isInvokeOfNumberedFunction = (parent as? IrClass)?.fqNameWhenAvailable?.asString()?.let { + it.startsWith("kotlin.jvm.functions.Function") && it.removePrefix("kotlin.jvm.functions.Function").all { c -> c.isDigit() } + } == true + return if (isInvokeOfNumberedSuspendFunction || isInvokeOfNumberedFunction) + context.irBuiltIns.anyNType + else + context.ir.symbols.continuationClass.typeWith(returnType) +} + +private fun IrFunction.suspendFunctionView(context: JvmBackendContext): IrFunction { + require(isSuspend && this is IrSimpleFunction) + return getOrCreateSuspendFunctionStub(context).also { function -> + context.recordSuspendFunctionView(this, function) + + val continuationParameter = + function.valueParameters.find { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION } + ?.let { function.valueParameters[it.index - 1] } ?: function.valueParameters.last() + + function.body = + moveBodyTo(function, explicitParameters.zip(function.explicitParameters.filter { it != continuationParameter }).toMap()) + } +} + +fun IrFunction.suspendFunctionViewOrStub(context: JvmBackendContext): IrFunction { + if (!isSuspend) return this + return context.suspendFunctionOriginalToView[suspendFunctionOriginal()] ?: getOrCreateSuspendFunctionStub(context) +} + +private fun IrCall.createSuspendFunctionCallViewIfNeeded(context: JvmBackendContext, caller: IrFunction): IrCall { + // Calls inside continuation are already generated with continuation parameter as well as calls to suspendImpls + if (!isSuspend || caller.isInvokeSuspendOfContinuation() + || symbol.owner.origin == JvmLoweredDeclarationOrigin.SUSPEND_IMPL_STATIC_FUNCTION + || (symbol.owner.valueParameters.lastOrNull()?.name?.asString() == SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME) + ) return this + val view = (symbol.owner as IrSimpleFunction).suspendFunctionViewOrStub(context) + if (view == symbol.owner) return this + + return IrCallImpl(startOffset, endOffset, view.returnType, view.symbol, superQualifierSymbol = superQualifierSymbol).also { + it.copyTypeArgumentsFrom(this) + it.copyAttributes(this) + it.dispatchReceiver = dispatchReceiver + it.extensionReceiver = extensionReceiver + val callerNumberOfMasks = caller.valueParameters.count { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION } + val callerContinuationIndex = caller.valueParameters.size - 1 - (if (callerNumberOfMasks != 0) callerNumberOfMasks + 1 else 0) + val continuationParameter = + when { + caller.isInvokeSuspendOfLambda() || caller.isInvokeSuspendForInlineOfLambda() -> + IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, caller.dispatchReceiverParameter!!.symbol) + // At this point the only LOCAL_FUNCTION_FOR_LAMBDAs are inline and crossinline lambdas + caller.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA -> context.fakeContinuation + else -> IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, caller.valueParameters[callerContinuationIndex].symbol) + } + // If the suspend function view that we are calling has default parameters, we need to make sure to pass the + // continuation before the default parameter mask(s) and handler. + val numberOfMasks = view.valueParameters.count { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION } + val continuationIndex = valueArgumentsCount - (if (numberOfMasks != 0) numberOfMasks + 1 else 0) + for (i in 0 until continuationIndex) { + it.putValueArgument(i, getValueArgument(i)) + } + it.putValueArgument(continuationIndex, continuationParameter) + if (numberOfMasks != 0) { + for (i in 0 until numberOfMasks + 1) { + it.putValueArgument(valueArgumentsCount + i - 1, getValueArgument(continuationIndex + i)) + } + } + } } \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt index ba9d6893fdc..cab81294377 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -37,10 +37,13 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.org.objectweb.asm.Type @@ -424,7 +427,11 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass IrValueParameterSymbolImpl(descriptor), name, index, - type.eraseTypeParameters(), + // SuspendFunction{N} is Function{N+1} at runtime, thus, when we generate a bridge for suspend callable reference, + // we need to replace type of its continuation parameter with Any? + if (target.isSuspend && type.eraseTypeParameters().getClass() + ?.fqNameWhenAvailable == DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME_RELEASE + ) context.irBuiltIns.anyNType else type.eraseTypeParameters(), varargElementType?.eraseTypeParameters(), isCrossinline, isNoinline diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionNVarargBridgeLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionNVarargBridgeLowering.kt index 41d07c46fbb..557afbad13e 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionNVarargBridgeLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionNVarargBridgeLowering.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -88,7 +88,7 @@ private class FunctionNVarargBridgeLowering(val context: JvmBackendContext) : // Add vararg invoke bridge val invokeFunction = declaration.functions.single { - it.name.asString() == "invoke" && it.valueParameters.size == superType.arguments.size - 1 + it.name.asString() == "invoke" && it.valueParameters.size == superType.arguments.size - if (it.isSuspend) 0 else 1 } invokeFunction.overriddenSymbols.clear() declaration.addBridge(invokeFunction, functionNInvokeFun.owner) diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DeepCopyIrTreeWithSymbols.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DeepCopyIrTreeWithSymbols.kt index 717c7b8e087..31074016cc2 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DeepCopyIrTreeWithSymbols.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DeepCopyIrTreeWithSymbols.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2010-2020 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.util @@ -179,6 +168,7 @@ open class DeepCopyIrTreeWithSymbols( declaration.overriddenSymbols.mapTo(overriddenSymbols) { symbolRemapper.getReferencedFunction(it) as IrSimpleFunctionSymbol } + copyAttributes(declaration) transformFunctionChildren(declaration) } diff --git a/compiler/testData/codegen/box/coroutines/suspendDefaultImpl.kt b/compiler/testData/codegen/box/coroutines/suspendDefaultImpl.kt index 11c29f7ddd1..a5ca1c9b07d 100644 --- a/compiler/testData/codegen/box/coroutines/suspendDefaultImpl.kt +++ b/compiler/testData/codegen/box/coroutines/suspendDefaultImpl.kt @@ -1,4 +1,5 @@ // IGNORE_BACKEND_FIR: JVM_IR +// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // WITH_COROUTINES // COMMON_COROUTINES_TEST diff --git a/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt index 8608725b255..7e163b01b62 100644 --- a/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt +++ b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt @@ -1,4 +1,5 @@ // IGNORE_BACKEND_FIR: JVM_IR +// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // WITH_COROUTINES // COMMON_COROUTINES_TEST diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt index 116814ea318..6538475e7bb 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt @@ -6,6 +6,7 @@ final class CrossinlineKt$box$1$invokeSuspend$$inlined$filter$1$1 { field L$2: java.lang.Object field L$3: java.lang.Object field L$4: java.lang.Object + field L$5: java.lang.Object field label: int @org.jetbrains.annotations.NotNull field result: java.lang.Object synthetic final field this$0: CrossinlineKt$box$1$invokeSuspend$$inlined$filter$1 @@ -121,6 +122,7 @@ final class CrossinlineKt$filter$$inlined$source$1$1 { field L$2: java.lang.Object field L$3: java.lang.Object field L$4: java.lang.Object + field L$5: java.lang.Object field label: int @org.jetbrains.annotations.NotNull field result: java.lang.Object synthetic final field this$0: CrossinlineKt$filter$$inlined$source$1 @@ -146,6 +148,7 @@ final class CrossinlineKt$filter$$inlined$source$2$1 { field L$2: java.lang.Object field L$3: java.lang.Object field L$4: java.lang.Object + field L$5: java.lang.Object field label: int @org.jetbrains.annotations.NotNull field result: java.lang.Object synthetic final field this$0: CrossinlineKt$filter$$inlined$source$2 @@ -310,7 +313,6 @@ final class CrossinlineKt$range$$inlined$source$1$1 { field L$1: java.lang.Object field L$2: java.lang.Object field L$3: java.lang.Object - field L$4: java.lang.Object field label: int @org.jetbrains.annotations.NotNull field result: java.lang.Object synthetic final field this$0: CrossinlineKt$range$$inlined$source$1 diff --git a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueCrossinline.kt b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueCrossinline.kt index 320fba9186b..3f4bcfc9006 100644 --- a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueCrossinline.kt +++ b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueCrossinline.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // FILE: test.kt // COMMON_COROUTINES_TEST // WITH_RUNTIME diff --git a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInline.kt b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInline.kt index 9b0dfc04f8c..af2a989202a 100644 --- a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInline.kt +++ b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInline.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // FILE: test.kt // COMMON_COROUTINES_TEST // WITH_RUNTIME diff --git a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInlineFromMultiFileFacade.kt b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInlineFromMultiFileFacade.kt index a449c55b285..a4382510c57 100644 --- a/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInlineFromMultiFileFacade.kt +++ b/compiler/testData/codegen/boxInline/suspend/defaultParameter/defaultValueInlineFromMultiFileFacade.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR // FILE: test.kt // COMMON_COROUTINES_TEST // WITH_RUNTIME