JVM_IR: keep suspend fun return type in IrCalls
Otherwise:
* should the dispatch receiver of a call be another call to a `suspend
fun` wrapped in something that is optimized away later, the owner of
the method will be incorrect;
* references to functions returning non-Unit but casted to `() ->
Unit` (allowed by new inference) might in fact not return Unit after
tail call optimization.
This commit is contained in:
+8
-1
@@ -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 =
|
||||
|
||||
+5
-8
@@ -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
|
||||
|
||||
+1
-7
@@ -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<IrClass>()
|
||||
|
||||
+3
-13
@@ -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 `<original type> | 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
|
||||
|
||||
@@ -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<Unit> {
|
||||
override fun resumeWith(x: Result<Unit>) {
|
||||
result += "done;"
|
||||
proceed = null
|
||||
}
|
||||
override val context = EmptyCoroutineContext
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_RUNTIME
|
||||
// WITH_COROUTINES
|
||||
|
||||
// FILE: I.kt
|
||||
|
||||
open class A<T> {
|
||||
suspend fun id(x: T): T = x
|
||||
}
|
||||
|
||||
class B {
|
||||
fun ok() = "OK"
|
||||
}
|
||||
|
||||
// FILE: JavaClass.java
|
||||
|
||||
public class JavaClass extends A<B> {}
|
||||
|
||||
// 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
|
||||
}
|
||||
+24
@@ -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"
|
||||
}
|
||||
+15
@@ -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");
|
||||
|
||||
+15
@@ -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");
|
||||
|
||||
+15
@@ -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");
|
||||
|
||||
+15
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user