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 7a1d1251b61..cf1ea3f9a8f 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 @@ -14,6 +14,7 @@ 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_IMPL_NAME_SUFFIX import org.jetbrains.kotlin.codegen.coroutines.reportSuspensionPointInsideMonitor import org.jetbrains.kotlin.codegen.inline.addFakeContinuationConstructorCallMarker import org.jetbrains.kotlin.config.isReleaseCoroutines @@ -28,6 +29,7 @@ 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.file +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 @@ -118,6 +120,11 @@ internal fun IrFunction.isInvokeOfSuspendCallableReference(): Boolean = isSuspen private fun IrFunction.isInvokeOfSuspendMainWrapper(): Boolean = !isSuspend && name.asString() == "invoke" && parentAsClass.origin == JvmLoweredDeclarationOrigin.LAMBDA_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, @@ -130,7 +137,7 @@ internal fun IrFunction.isKnownToBeTailCall(): Boolean = IrDeclarationOrigin.BRIDGE, IrDeclarationOrigin.BRIDGE_SPECIAL, IrDeclarationOrigin.DELEGATED_MEMBER -> true - else -> isInvokeOfSuspendMainWrapper() || isInvokeOfSuspendCallableReference() + else -> isInvokeOfSuspendMainWrapper() || isInvokeOfSuspendCallableReference() || isBridgeToSuspendImplMethod() } internal fun IrFunction.shouldNotContainSuspendMarkers(): Boolean = 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 e6d7122e2eb..8e636468dcd 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 @@ -436,13 +436,6 @@ class ExpressionCodegen( } if (expression.isSuspensionPoint()) { - // 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 (expression.symbol.owner.suspendFunctionOriginal().returnType.isUnit()) { - addReturnsUnitMarker(mv) - } - addSuspendMarker(mv, isStartNotEnd = false) addInlineMarker(mv, isStartNotEnd = false) } @@ -455,8 +448,12 @@ class ExpressionCodegen( } expression is IrConstructorCall -> MaterialValue(this, asmType, expression.type) - expression.type.isUnit() -> { + !irFunction.shouldNotContainSuspendMarkers() && expression.type.isUnit() -> { // 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 + // is methods that do not pass through the state machine generating MethodVisitor, since getting + // COROUTINE_SUSPENDED here is still possible; luckily, all those methods are bridges. if (callable.asmMethod.returnType != Type.VOID_TYPE) MaterialValue(this, callable.asmMethod.returnType, callee.returnType).discard() // don't generate redundant UNIT/pop instructions 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 81f4a8be259..8cc5e60cbcf 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 @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.common.lower.BOUND_VALUE_PARAMETER import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.codegen.AsmUtil -import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_IMPL_NAME_SUFFIX import org.jetbrains.kotlin.codegen.mangleNameIfNeeded import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.visitAnnotableParameterCount @@ -142,12 +141,7 @@ open class FunctionCodegen( 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 && - // Continuations are generated for suspendImpls - parentAsClass.functions.none { - it.name.asString() == name.asString() + SUSPEND_IMPL_NAME_SUFFIX && - it.attributeOwnerId == (this as? IrAttributeContainer)?.attributeOwnerId - } + origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE private fun continuationClass() = irFunction.body!!.statements.firstIsInstance() 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 c947aee83a4..f81aa078595 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 @@ -75,18 +75,6 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : return super.visitFunction(declaration).also { functionStack.pop() } } - override fun visitMemberAccess(expression: IrMemberAccessExpression): IrExpression { - val receiverType = expression.dispatchReceiver?.type - val newExpression = super.visitMemberAccess(expression) as IrMemberAccessExpression - if (receiverType != null && receiverType != newExpression.dispatchReceiver?.type) { - newExpression.dispatchReceiver = IrTypeOperatorCallImpl( - expression.startOffset, expression.endOffset, receiverType, - IrTypeOperator.IMPLICIT_CAST, receiverType, newExpression.dispatchReceiver!! - ) - } - return newExpression - } - 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) @@ -790,7 +778,9 @@ private fun IrCall.createSuspendFunctionCallViewIfNeeded(context: JvmBackendCont 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 { + // 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 { it.copyTypeArgumentsFrom(this) it.dispatchReceiver = dispatchReceiver it.extensionReceiver = extensionReceiver diff --git a/compiler/testData/codegen/box/coroutines/suspendImplBridge.kt b/compiler/testData/codegen/box/coroutines/suspendImplBridge.kt new file mode 100644 index 00000000000..89b9f19737a --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/suspendImplBridge.kt @@ -0,0 +1,45 @@ +// IGNORE_BACKEND_FIR: JVM_IR +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// WITH_COROUTINES + +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +var result: String = "" +var proceed: (() -> Unit)? = null + +fun box(): String { + async { + O().foo(1) + O().foo(2) + } + while (proceed != null) { + result += "--;" + proceed!!() + } + if (result != "begin(1);--;end(1);begin(2);--;end(2);done;") return result; + return "OK" +} + +open class O { + open suspend fun foo(x: Int) { + result += "begin($x);" + sleep() + result += "end($x);" + } +} + +suspend fun sleep(): Unit = suspendCoroutine { c -> + proceed = { c.resume(Unit) } +} + +fun async(f: suspend () -> Unit) { + f.startCoroutine(object : Continuation { + override fun resumeWith(x: Result) { + result += "done;" + proceed = null + } + override val context = EmptyCoroutineContext + }) +} diff --git a/compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt b/compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt new file mode 100644 index 00000000000..b58820c139b --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt @@ -0,0 +1,31 @@ +// IGNORE_BACKEND_FIR: JVM_IR +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// WITH_COROUTINES + +// FILE: I.kt + +open class A { + suspend fun id(x: T): T = x +} + +class B { + fun ok() = "OK" +} + +// FILE: JavaClass.java + +public class JavaClass extends A {} + +// FILE: main.kt +import helpers.* +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +fun box(): String { + var result = "fail" + suspend { + result = JavaClass().id(B()).ok() + }.startCoroutine(EmptyContinuation) + return result +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt new file mode 100644 index 00000000000..5bb625b768c --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt @@ -0,0 +1,24 @@ +// !LANGUAGE: +NewInference +// IGNORE_BACKEND_FIR: JVM_IR +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// WITH_COROUTINES + +import helpers.* +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +object Dummy + +suspend fun suspect(): Dummy = suspendCoroutineUninterceptedOrReturn { + x -> x.resume(Dummy) + COROUTINE_SUSPENDED +} + +fun box(): String { + var res: Any? = null + suspend { + res = (::suspect as suspend () -> Unit)() + }.startCoroutine(EmptyContinuation) + return if (res != Unit) "$res" else "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 961e2a1f24c..9fb0a04f9bc 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -6785,6 +6785,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendFunImportedFromObject.kt", "kotlin.coroutines"); } + @TestMetadata("suspendImplBridge.kt") + public void testSuspendImplBridge() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendImplBridge.kt"); + } + @TestMetadata("suspendInCycle.kt") public void testSuspendInCycle_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendInCycle.kt", "kotlin.coroutines.experimental"); @@ -6830,6 +6835,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/coroutines/suspendJavaOverrides.kt"); } + @TestMetadata("suspendReturningPlatformType.kt") + public void testSuspendReturningPlatformType() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt"); + } + @TestMetadata("suspensionInsideSafeCallWithElvis.kt") public void testSuspensionInsideSafeCallWithElvis_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt", "kotlin.coroutines.experimental"); @@ -8715,6 +8725,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } + @TestMetadata("functionReference.kt") + public void testFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt"); + } + @TestMetadata("override.kt") public void testOverride() throws Exception { runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index e631ab5d368..b9559d553f6 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -6785,6 +6785,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendFunImportedFromObject.kt", "kotlin.coroutines"); } + @TestMetadata("suspendImplBridge.kt") + public void testSuspendImplBridge() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendImplBridge.kt"); + } + @TestMetadata("suspendInCycle.kt") public void testSuspendInCycle_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendInCycle.kt", "kotlin.coroutines.experimental"); @@ -6830,6 +6835,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/coroutines/suspendJavaOverrides.kt"); } + @TestMetadata("suspendReturningPlatformType.kt") + public void testSuspendReturningPlatformType() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt"); + } + @TestMetadata("suspensionInsideSafeCallWithElvis.kt") public void testSuspensionInsideSafeCallWithElvis_1_2() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt", "kotlin.coroutines.experimental"); @@ -8715,6 +8725,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } + @TestMetadata("functionReference.kt") + public void testFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt"); + } + @TestMetadata("override.kt") public void testOverride() throws Exception { runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java index 4bce8fb609e..3882f26b590 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java @@ -6395,6 +6395,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendFunImportedFromObject.kt", "kotlin.coroutines"); } + @TestMetadata("suspendImplBridge.kt") + public void testSuspendImplBridge() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendImplBridge.kt"); + } + @TestMetadata("suspendInCycle.kt") public void testSuspendInCycle_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendInCycle.kt", "kotlin.coroutines"); @@ -6420,6 +6425,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/coroutines/suspendJavaOverrides.kt"); } + @TestMetadata("suspendReturningPlatformType.kt") + public void testSuspendReturningPlatformType() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt"); + } + @TestMetadata("suspensionInsideSafeCallWithElvis.kt") public void testSuspensionInsideSafeCallWithElvis_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt", "kotlin.coroutines"); @@ -7640,6 +7650,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @TestMetadata("functionReference.kt") + public void testFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt"); + } + @TestMetadata("override.kt") public void testOverride() throws Exception { runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 4d1390c5745..6cad78fd412 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -6395,6 +6395,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendFunImportedFromObject.kt", "kotlin.coroutines"); } + @TestMetadata("suspendImplBridge.kt") + public void testSuspendImplBridge() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendImplBridge.kt"); + } + @TestMetadata("suspendInCycle.kt") public void testSuspendInCycle_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspendInCycle.kt", "kotlin.coroutines"); @@ -6420,6 +6425,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/coroutines/suspendJavaOverrides.kt"); } + @TestMetadata("suspendReturningPlatformType.kt") + public void testSuspendReturningPlatformType() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/suspendReturningPlatformType.kt"); + } + @TestMetadata("suspensionInsideSafeCallWithElvis.kt") public void testSuspensionInsideSafeCallWithElvis_1_3() throws Exception { runTestWithPackageReplacement("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCallWithElvis.kt", "kotlin.coroutines"); @@ -7640,6 +7650,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @TestMetadata("functionReference.kt") + public void testFunctionReference() throws Exception { + runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/functionReference.kt"); + } + @TestMetadata("override.kt") public void testOverride() throws Exception { runTest("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override.kt");