From 159d292ea7912471d7725d3e4b66b9c71bad4e40 Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 26 Feb 2020 10:37:46 +0100 Subject: [PATCH] JVM_IR: make continuation detection more consistent * unify various checks for whether a suspend function needs a continuation; * mark the continuation parameter with an origin (this also allows correctly computing the offset of the local in codegen -- see the new test); * when wrapping `suspend fun main`, use a suspend reference instead of a synthetic non-suspend lambda (required to correctly implement the previous point, as previously the continuation parameter was passed to main() itself correctly only because of very lenient checks in AddContinuationLowering). --- .../kotlin/backend/common/ir/IrUtils.kt | 18 +--- .../backend/jvm/codegen/CoroutineCodegen.kt | 83 ++++++------------ .../backend/jvm/codegen/ExpressionCodegen.kt | 22 ++++- .../backend/jvm/codegen/FunctionCodegen.kt | 10 --- .../jvm/lower/AddContinuationLowering.kt | 85 ++++++------------- .../jvm/lower/MainMethodGenerationLowering.kt | 81 +++++------------- .../jvm/lower/TailCallOptimizationLowering.kt | 10 +-- .../kotlin/ir/descriptors/IrBuiltIns.kt | 2 - .../suspend/nestedMethodWith2XParameter.kt | 22 +++++ .../BlackBoxInlineCodegenTestGenerated.java | 5 ++ ...otlinAgainstInlineKotlinTestGenerated.java | 5 ++ .../IrBlackBoxInlineCodegenTestGenerated.java | 5 ++ ...otlinAgainstInlineKotlinTestGenerated.java | 5 ++ 13 files changed, 138 insertions(+), 215 deletions(-) create mode 100644 compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt index 73979c5b70d..9b8870aa384 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/IrUtils.kt @@ -185,7 +185,7 @@ fun IrTypeParameter.copyToWithoutSuperTypes( } } -private fun IrFunction.copyReceiverParametersFrom(from: IrFunction) { +fun IrFunction.copyReceiverParametersFrom(from: IrFunction) { dispatchReceiverParameter = from.dispatchReceiverParameter?.let { IrValueParameterImpl(it.startOffset, it.endOffset, it.origin, it.descriptor, it.type, it.varargElementType).also { it.parent = this @@ -200,22 +200,6 @@ fun IrFunction.copyValueParametersFrom(from: IrFunction) { valueParameters += from.valueParameters.map { it.copyTo(this, index = it.index + shift) } } -fun IrFunction.copyValueParametersInsertingContinuationFrom(from: IrFunction, insertContinuation: () -> Unit) { - copyReceiverParametersFrom(from) - val shift = valueParameters.size - var additionalShift = 0 - from.valueParameters.forEach { - // The continuation parameter goes before the default argument mask and handler. - if (it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION && additionalShift == 0) { - insertContinuation() - additionalShift = 1 - } - valueParameters += it.copyTo(this, index = it.index + shift + additionalShift) - } - // If there was no default argument mask and handler, the continuation goes last. - if (additionalShift == 0) insertContinuation() -} - fun IrFunction.copyParameterDeclarationsFrom(from: IrFunction) { assert(typeParameters.isEmpty()) copyTypeParametersFrom(from) 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 8fd0b3c057e..2311eb528c5 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 @@ -16,7 +16,6 @@ import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_IMPL_NAME_SUFFIX 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.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.declarations.* @@ -30,13 +29,9 @@ import org.jetbrains.kotlin.ir.util.functions 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 import org.jetbrains.kotlin.types.Variance -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.org.objectweb.asm.MethodVisitor -import org.jetbrains.org.objectweb.asm.Type -import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter internal fun generateStateMachineForNamedFunction( irFunction: IrFunction, @@ -101,45 +96,49 @@ internal fun generateStateMachineForLambda( ) } +internal fun IrFunction.continuationParameter(): IrValueParameter? = when { + isInvokeSuspendOfLambda() || isInvokeSuspendForInlineOfLambda() -> dispatchReceiverParameter + else -> valueParameters.singleOrNull { it.origin == JvmLoweredDeclarationOrigin.CONTINUATION_CLASS } +} + internal fun IrFunction.isInvokeSuspendOfLambda(): Boolean = name.asString() == INVOKE_SUSPEND_METHOD_NAME && parentAsClass.origin == JvmLoweredDeclarationOrigin.SUSPEND_LAMBDA -internal fun IrFunction.isInvokeSuspendForInlineOfLambda(): Boolean = +private fun IrFunction.isInvokeSuspendForInlineOfLambda(): Boolean = origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE && parentAsClass.origin == JvmLoweredDeclarationOrigin.SUSPEND_LAMBDA internal fun IrFunction.isInvokeSuspendOfContinuation(): Boolean = name.asString() == INVOKE_SUSPEND_METHOD_NAME && parentAsClass.origin == JvmLoweredDeclarationOrigin.CONTINUATION_CLASS -internal fun IrFunction.isInvokeOfSuspendCallableReference(): Boolean = isSuspend && name.asString() == "invoke" && - parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL - -// Wrapper of suspend main is always tail-call and it is not generated as suspend function. -private fun IrFunction.isInvokeOfSuspendMainWrapper(): Boolean = !isSuspend && name.asString() == "invoke" && - parentAsClass.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL +private fun IrFunction.isInvokeOfSuspendCallableReference(): Boolean = + isSuspend && name.asString() == "invoke" && parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL private fun IrFunction.isBridgeToSuspendImplMethod(): Boolean = isSuspend && this is IrSimpleFunction && parentAsClass.functions.any { it.name.asString() == name.asString() + SUSPEND_IMPL_NAME_SUFFIX && it.attributeOwnerId == attributeOwnerId } -internal fun IrFunction.isKnownToBeTailCall(): Boolean = - when (origin) { - IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER, - JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER, - JvmLoweredDeclarationOrigin.MULTIFILE_BRIDGE, - JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR, - JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE, - JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC, - JvmLoweredDeclarationOrigin.GENERATED_MEMBER_IN_CALLABLE_REFERENCE, - IrDeclarationOrigin.BRIDGE, - IrDeclarationOrigin.BRIDGE_SPECIAL, - IrDeclarationOrigin.DELEGATED_MEMBER -> true - else -> isInvokeOfSuspendMainWrapper() || isInvokeOfSuspendCallableReference() || isBridgeToSuspendImplMethod() - } +internal fun IrFunction.shouldContainSuspendMarkers(): Boolean = !isInvokeSuspendOfContinuation() && + // These are tail-call bridges and do not require any bytecode modifications. + origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER && + origin != JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER && + origin != JvmLoweredDeclarationOrigin.MULTIFILE_BRIDGE && + origin != JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR && + origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE && + origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE_TO_SYNTHETIC && + origin != JvmLoweredDeclarationOrigin.GENERATED_MEMBER_IN_CALLABLE_REFERENCE && + origin != IrDeclarationOrigin.BRIDGE && + origin != IrDeclarationOrigin.BRIDGE_SPECIAL && + origin != IrDeclarationOrigin.DELEGATED_MEMBER && + !isInvokeOfSuspendCallableReference() && + !isBridgeToSuspendImplMethod() -internal fun IrFunction.shouldNotContainSuspendMarkers(): Boolean = - isInvokeSuspendOfContinuation() || isKnownToBeTailCall() +internal fun IrFunction.hasContinuation(): Boolean = isSuspend && shouldContainSuspendMarkers() && + // These are templates for the inliner; the continuation will be generated after it runs. + origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA && + origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE && + origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE internal fun IrExpression?.isReadOfCrossinline(): Boolean = when (this) { is IrGetValue -> (symbol.owner as? IrValueParameter)?.isCrossinline == true @@ -156,31 +155,3 @@ internal fun createFakeContinuation(context: JvmBackendContext): IrExpression = context.ir.symbols.continuationClass.createType(true, listOf(makeTypeProjection(context.irBuiltIns.anyNType, Variance.INVARIANT))), "FAKE_CONTINUATION" ) - -internal fun generateFakeContinuationConstructorCall( - v: InstructionAdapter, - classCodegen: ClassCodegen, - continuationClass: IrClass, - irFunction: IrFunction -) { - val continuationType = classCodegen.typeMapper.mapClass(continuationClass) - // TODO: This is different in case of DefaultImpls - val thisNameType = Type.getObjectType(classCodegen.visitor.thisName.replace(".", "/")) - val continuationIndex = listOfNotNull(irFunction.dispatchReceiverParameter, irFunction.extensionReceiverParameter).size + - irFunction.valueParameters.size - 1 - with(v) { - addFakeContinuationConstructorCallMarker(this, true) - anew(continuationType) - dup() - if (irFunction.dispatchReceiverParameter != null) { - load(0, AsmTypes.OBJECT_TYPE) - load(continuationIndex, Type.getObjectType("kotlin/coroutines/Continuation")) - invokespecial(continuationType.internalName, "", "(${thisNameType}Lkotlin/coroutines/Continuation;)V", false) - } else { - load(continuationIndex, Type.getObjectType("kotlin/coroutines/Continuation")) - invokespecial(continuationType.internalName, "", "(Lkotlin/coroutines/Continuation;)V", false) - } - addFakeContinuationConstructorCallMarker(this, false) - pop() - } -} 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 2cb5c476336..02417632d07 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 @@ -226,7 +226,23 @@ class ExpressionCodegen( 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, continuationClass, irFunction) + val continuationType = typeMapper.mapClass(continuationClass) + val continuationIndex = frameMap.getIndex(irFunction.continuationParameter()!!.symbol) + with(mv) { + addFakeContinuationConstructorCallMarker(this, true) + anew(continuationType) + dup() + if (irFunction.dispatchReceiverParameter != null) { + load(0, OBJECT_TYPE) + load(continuationIndex, Type.getObjectType("kotlin/coroutines/Continuation")) + invokespecial(continuationType.internalName, "", "(${classCodegen.type}Lkotlin/coroutines/Continuation;)V", false) + } else { + load(continuationIndex, Type.getObjectType("kotlin/coroutines/Continuation")) + invokespecial(continuationType.internalName, "", "(Lkotlin/coroutines/Continuation;)V", false) + } + addFakeContinuationConstructorCallMarker(this, false) + pop() + } } private fun generateNonNullAssertions() { @@ -444,7 +460,7 @@ class ExpressionCodegen( } expression is IrConstructorCall -> MaterialValue(this, asmType, expression.type) - !irFunction.shouldNotContainSuspendMarkers() && expression.type.isUnit() -> { + expression.type.isUnit() && irFunction.shouldContainSuspendMarkers() -> { // NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail. // Also, if the callee is a suspend function with a suspending tail call, the next `resumeWith` // will continue from here, but the value passed to it might not have been `Unit`. An exception @@ -469,7 +485,7 @@ class ExpressionCodegen( } private fun IrFunctionAccessExpression.isSuspensionPoint(): Boolean = when { - !symbol.owner.isSuspend || irFunction.shouldNotContainSuspendMarkers() -> false + !symbol.owner.isSuspend || !irFunction.shouldContainSuspendMarkers() -> false // Copy-pasted bytecode blocks are not suspension points. symbol.owner.isInline -> symbol.owner.fqNameForIrSerialization == FqName("kotlin.coroutines.intrinsics.IntrinsicsKt.suspendCoroutineUninterceptedOrReturn") 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 bbae942531d..ba339c68049 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 @@ -143,16 +143,6 @@ open class FunctionCodegen( context.suspendLambdaToOriginalFunctionMap[irFunction.parentAsClass.attributeOwnerId]!!.symbol.descriptor.psiElement) as KtElement - private fun IrFunction.hasContinuation(): Boolean = isSuspend && - // We do not generate continuation and state-machine for synthetic accessors, bridges, and delegated members, - // in a sense, they are tail-call - !isKnownToBeTailCall() && - // This is suspend lambda parameter of inline function - origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA && - // This is just a template for inliner - origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE && - origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE - private fun IrFunction.continuationClass(): IrClass = (body as IrBlockBody).statements.first { it is IrClass && it.origin == JvmLoweredDeclarationOrigin.CONTINUATION_CLASS } as IrClass 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 8f8efb5bbfa..bbe1a91cb36 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 @@ -12,9 +12,9 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin -import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendForInlineOfLambda +import org.jetbrains.kotlin.backend.jvm.codegen.continuationParameter +import org.jetbrains.kotlin.backend.jvm.codegen.hasContinuation import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfContinuation -import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfLambda import org.jetbrains.kotlin.backend.jvm.codegen.isReadOfCrossinline import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator import org.jetbrains.kotlin.backend.jvm.ir.defaultValue @@ -33,7 +33,6 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl @@ -616,11 +615,11 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : functionsStack.push(declaration) val function = super.visitFunction(declaration) as IrFunction functionsStack.pop() - if (skip(function)) return function + if (!function.isSuspend || function.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) return function val view = function.getOrCreateSuspendFunctionViewIfNeeded(context) as IrSimpleFunction - if (withoutContinuationClass(function)) return view + if (function.body == null || !function.hasContinuation()) return view if (function in suspendFunctionsCapturingCrossinline || function.isInline) { val newFunction = buildFunWithDescriptorForInlining(view.descriptor) { @@ -661,19 +660,6 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : return newFunction } - // 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.origin == JvmLoweredDeclarationOrigin.GENERATED_MEMBER_IN_CALLABLE_REFERENCE || - function.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE || - function.origin == JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER - - private fun skip(function: IrFunction) = - !function.isSuspend || - function.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA - private fun registerNewFunction(function: IrFunction) { functionsToAdd.peek()!!.add(function) } @@ -731,19 +717,22 @@ private fun IrFunction.suspendFunctionStub(context: JvmBackendContext): IrFuncti function.copyAttributes(this) function.copyTypeParametersFrom(this) + function.copyReceiverParametersFrom(this) if (origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE) { function.overriddenSymbols += 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. + // 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)) - } + val index = valueParameters.firstOrNull { it.origin == IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION }?.index + ?: valueParameters.size + function.valueParameters += valueParameters.take(index).map { it.copyTo(function, index = it.index) } + function.addValueParameter( + SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME, continuationType(context), JvmLoweredDeclarationOrigin.CONTINUATION_CLASS + ) + function.valueParameters += valueParameters.drop(index).map { it.copyTo(function, index = it.index + 1) } context.recordSuspendFunctionViewStub(this, function) } @@ -792,44 +781,20 @@ private fun IrCall.createSuspendFunctionCallViewIfNeeded(context: JvmBackendCont // While the new callee technically returns ` | COROUTINE_SUSPENDED`, the latter case is handled // by a method visitor so at an IR overview we don't need to consider it. - return IrCallImpl(startOffset, endOffset, type, view.symbol, superQualifierSymbol = superQualifierSymbol).also { + return IrCallImpl(startOffset, endOffset, type, view.symbol, origin, superQualifierSymbol).also { it.copyTypeArgumentsFrom(this) it.dispatchReceiver = dispatchReceiver it.extensionReceiver = extensionReceiver - val valueArguments = computeSuspendFunctionCallViewValueArguments(caller, context, view) - valueArguments.forEachIndexed { index, argument -> it.putValueArgument(index, argument) } + val continuationIndex = view.continuationParameter()!!.index + for (i in 0 until valueArgumentsCount) { + it.putValueArgument(i + if (i >= continuationIndex) 1 else 0, getValueArgument(i)!!) + } + // At this point the only LOCAL_FUNCTION_FOR_LAMBDAs are inline and crossinline lambdas + val continuation = if (caller.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) + context.fakeContinuation + else + IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, caller.continuationParameter()?.symbol + ?: throw AssertionError("${caller.render()} has no continuation; can't call ${symbol.owner.render()}")) + it.putValueArgument(continuationIndex, continuation) } } - -private fun IrCall.computeSuspendFunctionCallViewValueArguments( - caller: IrFunction, - context: JvmBackendContext, - view: IrFunction -): List { - // 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.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) - val valueArguments = mutableListOf() - for (i in 0 until continuationIndex) { - valueArguments.add(getValueArgument(i)!!) - } - valueArguments.add(continuationParameter) - if (numberOfMasks != 0) { - for (i in 0 until numberOfMasks + 1) { - valueArguments.add(getValueArgument(continuationIndex + i)!!) - } - } - return valueArguments -} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt index 23ae813f38c..6988f48c476 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin -import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.languageVersionSettings @@ -21,17 +20,11 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addFunction import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter -import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrValueParameter -import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.isFileClass import org.jetbrains.kotlin.name.Name @@ -81,27 +74,19 @@ private class MainMethodGenerationLowering(private val context: JvmBackendContex irClass.functions.find { it.isMainMethod() }?.let { mainMethod -> if (mainMethod.isSuspend) { - generateMainMethod(irClass) { args -> - body = buildRunSuspendWrapper { - +IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.nothingType, it, irCall(mainMethod).apply { - putValueArgument(0, irGet(args)) - }) - } + irClass.generateMainMethod { args -> + +irRunSuspend(mainMethod, args) } } return } irClass.functions.find { it.isParameterlessMainMethod() }?.let { parameterlessMainMethod -> - generateMainMethod(irClass) { - body = if (parameterlessMainMethod.isSuspend) { - buildRunSuspendWrapper { - +IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.nothingType, it, irCall(parameterlessMainMethod)) - } + irClass.generateMainMethod { + if (parameterlessMainMethod.isSuspend) { + +irRunSuspend(parameterlessMainMethod, null) } else { - context.createIrBuilder(symbol).irBlockBody { - +irCall(parameterlessMainMethod) - } + +irCall(parameterlessMainMethod) } } } @@ -130,11 +115,8 @@ private class MainMethodGenerationLowering(private val context: JvmBackendContex } } - private fun generateMainMethod( - irClass: IrClass, - makeBody: IrFunctionImpl.(IrValueParameter) -> Unit - ) { - irClass.addFunction { + private fun IrClass.generateMainMethod(makeBody: IrBlockBodyBuilder.(IrValueParameter) -> Unit) = + addFunction { name = Name.identifier("main") visibility = Visibilities.PUBLIC returnType = context.irBuiltIns.unitType @@ -145,41 +127,22 @@ private class MainMethodGenerationLowering(private val context: JvmBackendContex name = Name.identifier("args") type = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType) } - makeBody(args) + body = context.createIrBuilder(symbol).irBlockBody { makeBody(args) } } - } - private fun IrFunctionImpl.buildRunSuspendWrapper(buildMainInvocation: IrBlockBodyBuilder.(IrSimpleFunctionSymbol) -> Unit) = - context.createJvmIrBuilder(symbol).irBlockBody { - val lambda = irBlock(UNDEFINED_OFFSET, UNDEFINED_OFFSET, IrStatementOrigin.LAMBDA) { - val function = buildFun { - origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA - name = Name.identifier("wrapper_for_suspend_main") - visibility = Visibilities.LOCAL - returnType = context.irBuiltIns.anyNType - }.apply { - addValueParameter("k", context.irBuiltIns.anyNType) - parent = this - body = irBlockBody { - buildMainInvocation(symbol) - } - } - +function - +IrFunctionReferenceImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - context.irBuiltIns.function1.typeWith( - context.irBuiltIns.anyNType, - context.irBuiltIns.anyNType - ), - function.symbol, - typeArgumentsCount = 0, - origin = IrStatementOrigin.LAMBDA, - reflectionTarget = null - ) - } - +irCall(this@MainMethodGenerationLowering.context.ir.symbols.runSuspendFunction).apply { - putValueArgument(0, lambda) + private fun IrBuilderWithScope.irRunSuspend(target: IrSimpleFunction, args: IrValueParameter?) = + irCall(this@MainMethodGenerationLowering.context.ir.symbols.runSuspendFunction).apply { + val reference = IrFunctionReferenceImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + this@MainMethodGenerationLowering.context.ir.symbols.suspendFunctionN(0).typeWith(context.irBuiltIns.anyNType), + target.symbol, + typeArgumentsCount = 0, + reflectionTarget = null + ) + if (args != null) { + reference.putValueArgument(0, irGet(args)) } + putValueArgument(0, reference) } } \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TailCallOptimizationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TailCallOptimizationLowering.kt index 1ebac975746..45eda15f1d2 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TailCallOptimizationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TailCallOptimizationLowering.kt @@ -9,10 +9,8 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.ir.isSuspend import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext -import org.jetbrains.kotlin.backend.jvm.codegen.anyOfOverriddenFunctionsReturnsNonUnit -import org.jetbrains.kotlin.backend.jvm.codegen.isKnownToBeTailCall +import org.jetbrains.kotlin.backend.jvm.codegen.hasContinuation import org.jetbrains.kotlin.ir.IrStatement -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.expressions.* @@ -20,7 +18,6 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl import org.jetbrains.kotlin.ir.types.isUnit import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable -import org.jetbrains.kotlin.ir.util.isSuspend import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.visitors.IrElementTransformer import org.jetbrains.kotlin.name.FqName @@ -77,10 +74,7 @@ private class TailCallOptimizationData(val function: IrFunction) { } init { - if (function.isSuspend && function.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA && !function.isKnownToBeTailCall() && - // See `disableTailCallOptimizationForFunctionReturningUnit` in `generateStateMachineForNamedFunction`: - !(returnsUnit && function.anyOfOverriddenFunctionsReturnsNonUnit()) - ) { + if (function.hasContinuation()) { when (val body = function.body) { is IrBlockBody -> body.statements.findTailCall(returnsUnit)?.let(::findCallsOnTailPositionWithoutImmediateReturn) is IrExpressionBody -> findCallsOnTailPositionWithoutImmediateReturn(body.expression) diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt index 5dcb4cc061e..26080f8aa02 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt @@ -318,6 +318,4 @@ class IrBuiltIns( const val ANDAND = "ANDAND" const val OROR = "OROR" } - - val function1: IrClassSymbol = builtIns.getFunction(1).toIrSymbol() } diff --git a/compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt b/compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt new file mode 100644 index 00000000000..79d3376ce03 --- /dev/null +++ b/compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// WITH_COROUTINES +// TARGET_BACKEND: JVM +// FILE: a.kt + +inline suspend fun f(crossinline lambda: suspend (Double) -> Double): Double { + val obj = object { + suspend fun g(x: Double): Double = lambda(x) + } + return obj.g(1.0) +} + +// FILE: b.kt +import helpers.* +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +fun box(): String { + var result: Double = 0.0 + suspend { result = f { it } }.startCoroutine(EmptyContinuation) + return if (result == 1.0) "OK" else "fail: $result" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index 3e15a340734..d8c70903a59 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -3813,6 +3813,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/multipleSuspensionPoints.kt", "kotlin.coroutines"); } + @TestMetadata("nestedMethodWith2XParameter.kt") + public void testNestedMethodWith2XParameter() throws Exception { + runTest("compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt"); + } + @TestMetadata("nonSuspendCrossinline.kt") public void testNonSuspendCrossinline_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/nonSuspendCrossinline.kt", "kotlin.coroutines.experimental"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 5f816892147..ff180f01845 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -3813,6 +3813,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/multipleSuspensionPoints.kt", "kotlin.coroutines"); } + @TestMetadata("nestedMethodWith2XParameter.kt") + public void testNestedMethodWith2XParameter() throws Exception { + runTest("compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt"); + } + @TestMetadata("nonSuspendCrossinline.kt") public void testNonSuspendCrossinline_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/nonSuspendCrossinline.kt", "kotlin.coroutines.experimental"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java index 959cefc93a6..e4c98505524 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java @@ -3728,6 +3728,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/multipleSuspensionPoints.kt", "kotlin.coroutines"); } + @TestMetadata("nestedMethodWith2XParameter.kt") + public void testNestedMethodWith2XParameter() throws Exception { + runTest("compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt"); + } + @TestMetadata("nonSuspendCrossinline.kt") public void testNonSuspendCrossinline_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/nonSuspendCrossinline.kt", "kotlin.coroutines"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java index 74e1d5f456d..aeb7c4e4b33 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java @@ -3728,6 +3728,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/multipleSuspensionPoints.kt", "kotlin.coroutines"); } + @TestMetadata("nestedMethodWith2XParameter.kt") + public void testNestedMethodWith2XParameter() throws Exception { + runTest("compiler/testData/codegen/boxInline/suspend/nestedMethodWith2XParameter.kt"); + } + @TestMetadata("nonSuspendCrossinline.kt") public void testNonSuspendCrossinline_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/boxInline/suspend/nonSuspendCrossinline.kt", "kotlin.coroutines");