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:
pyos
2020-02-21 12:49:07 +01:00
committed by Ilmir Usmanov
parent d982203d56
commit a3d85e108f
11 changed files with 177 additions and 29 deletions
@@ -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 =
@@ -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
@@ -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>()
@@ -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
}
@@ -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"
}
@@ -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");
@@ -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");
@@ -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");
@@ -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");