Disable tail-call optimization for suspend functions with Unit return type

if it overrides functions with another return type.
Otherwise, we cannot determine on call site that the function returns Unit
and cannot { POP, PUSH Unit } in order to avoid the situation when callee's
continuation resumes with non-unit result. The observed behavior is that
suspend function, which should return Unit, suddenly returns other value.
 #KT-35262: Fixed
This commit is contained in:
Ilmir Usmanov
2019-12-04 16:51:59 +03:00
parent 09acdb655d
commit b6de3c2fcc
22 changed files with 768 additions and 70 deletions
@@ -0,0 +1,42 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun <T> tx(lambda: () -> T): T = suspendCoroutine { c = it; lambda() }
object Dummy
interface Base<T> {
suspend fun generic(): T
}
class Derived: Base<Unit> {
override suspend fun generic(): Unit {
tx { Dummy }
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
var res: Any? = null
builder {
val base: Base<*> = Derived()
res = base.generic()
}
(c as? Continuation<Dummy>)?.resume(Dummy)
return if (res != Unit) "$res" else "OK"
}
@@ -0,0 +1,44 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun <T> tx(lambda: () -> T): T = suspendCoroutine { c = it; lambda() }
object Dummy
interface Base<T> {
suspend fun generic(): T
}
abstract class Derived1: Base<Unit> {
}
class Derived2: Derived1() {
override suspend fun generic(): Unit {
tx { Dummy }
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
var res: Any? = null
builder {
val base: Base<*> = Derived2()
res = base.generic()
}
(c as? Continuation<Dummy>)?.resume(Dummy)
return if (res != Unit) "$res" else "OK"
}
@@ -0,0 +1,46 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun <T> tx(lambda: () -> T): T = suspendCoroutine { c = it; lambda() }
object Dummy
interface Foo {
suspend fun generic(): Any
}
interface Base {
suspend fun generic(): Unit
}
class Derived: Base, Foo {
override suspend fun generic(): Unit {
tx { Dummy }
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
var res: Any? = null
builder {
val foo: Foo = Derived()
res = foo.generic()
}
(c as? Continuation<Dummy>)?.resume(Dummy)
return if (res != Unit) "$res" else "OK"
}
@@ -0,0 +1,40 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
// CHECK_TAIL_CALL_OPTIMIZATION
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun suspendHere() = TailCallOptimizationChecker.saveStackTrace()
interface Base<T> {
suspend fun generic(): T
}
inline fun inlineMe(crossinline c: suspend () -> Unit) = object : Base<Unit> {
override suspend fun generic(): Unit {
c()
suspendHere()
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
builder {
inlineMe { }.generic()
}
// TODO: There should be no state-machine. Should fix in IR_BE
TailCallOptimizationChecker.checkStateMachineIn("generic")
return "OK"
}
@@ -0,0 +1,35 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
// CHECK_BYTECODE_LISTING
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
interface Base<T> {
suspend fun generic(): T
}
inline fun inlineMe(crossinline c: suspend () -> Unit) = object : Base<Unit> {
override suspend fun generic(): Unit {
c();
{}()
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
builder {
inlineMe { }.generic()
}
return "OK"
}
@@ -0,0 +1,70 @@
@kotlin.Metadata
public interface Base {
public abstract @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
}
@kotlin.Metadata
public final class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1 {
inner class Override5Kt$box$1$invokeSuspend$$inlined$inlineMe$1
inner class Override5Kt$inlineMe$1$generic$2
public method <init>(): void
public @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
}
@kotlin.coroutines.jvm.internal.DebugMetadata
@kotlin.Metadata
final class Override5Kt$box$1 {
field label: int
inner class Override5Kt$box$1
method <init>(p0: kotlin.coroutines.Continuation): void
public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): kotlin.coroutines.Continuation
public final method invoke(p0: java.lang.Object): java.lang.Object
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
@kotlin.coroutines.jvm.internal.DebugMetadata
public final class Override5Kt$inlineMe$1$generic$1 {
field L$0: java.lang.Object
field label: int
synthetic field result: java.lang.Object
synthetic final field this$0: Override5Kt$inlineMe$1
inner class Override5Kt$inlineMe$1
inner class Override5Kt$inlineMe$1$generic$1
public method <init>(p0: Override5Kt$inlineMe$1, p1: kotlin.coroutines.Continuation): void
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
public final class Override5Kt$inlineMe$1$generic$2 {
public final static field INSTANCE: Override5Kt$inlineMe$1$generic$2
inner class Override5Kt$inlineMe$1
inner class Override5Kt$inlineMe$1$generic$2
static method <clinit>(): void
public method <init>(): void
public synthetic method invoke(): java.lang.Object
public final method invoke(): void
}
@kotlin.Metadata
public final class Override5Kt$inlineMe$1 {
synthetic final field $c: kotlin.jvm.functions.Function1
inner class Override5Kt$inlineMe$1
inner class Override5Kt$inlineMe$1$generic$1
inner class Override5Kt$inlineMe$1$generic$2
public method <init>(p0: kotlin.jvm.functions.Function1): void
public @org.jetbrains.annotations.Nullable method generic$$forInline(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
public @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
}
@kotlin.Metadata
public final class Override5Kt {
private static @org.jetbrains.annotations.Nullable field c: kotlin.coroutines.Continuation
inner class Override5Kt$box$1
inner class Override5Kt$inlineMe$1
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
public final static @org.jetbrains.annotations.Nullable method getC(): kotlin.coroutines.Continuation
public final static @org.jetbrains.annotations.NotNull method inlineMe(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): Base
public final static method setC(@org.jetbrains.annotations.Nullable p0: kotlin.coroutines.Continuation): void
}
@@ -0,0 +1,38 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
// CHECK_TAIL_CALL_OPTIMIZATION
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun suspendHere() = TailCallOptimizationChecker.saveStackTrace()
interface Base<T> {
suspend fun generic(): T
}
inline fun inlineMe(crossinline c: suspend () -> Unit) = object : Base<Unit> {
override suspend fun generic(): Unit {
c()
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
builder {
inlineMe { suspendHere() }.generic()
}
TailCallOptimizationChecker.checkStateMachineIn("generic")
return "OK"
}
@@ -0,0 +1,53 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun <T> tx(lambda: () -> T): T = suspendCoroutine { c = it; lambda() }
object Dummy
interface Base<T> {
suspend fun callMe(a: IntArray): Unit
suspend fun callMe(s: String): Unit
suspend fun callMe(a: Array<String>): Unit
suspend fun callMe(a: Int): T
}
inline fun inlineMe(crossinline c: suspend () -> Unit): Base<*> {
return object: Base<Unit> {
override suspend fun callMe(a: IntArray) {}
override suspend fun callMe(a: Int): Unit {
c()
}
override suspend fun callMe(s: String) {}
override suspend fun callMe(a: Array<String>) {}
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
var res: Any? = null
builder {
res = inlineMe {
tx { Dummy }
}.callMe(1)
}
(c as? Continuation<Dummy>)?.resume(Dummy)
return if (res != Unit) "$res" else "OK"
}
@@ -0,0 +1,45 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.*
var c: Continuation<*>? = null
suspend fun <T> tx(lambda: () -> T): T = suspendCoroutine { c = it; lambda() }
object Dummy
interface Base<T> {
suspend fun generic(): T
}
open class Derived1: Base<Unit> {
override suspend fun generic() {}
}
class Derived2: Derived1() {
override suspend fun generic(): Unit {
tx { Dummy }
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
var res: Any? = null
builder {
val base: Base<*> = Derived2()
res = base.generic()
}
(c as? Continuation<Dummy>)?.resume(Dummy)
return if (res != Unit) "$res" else "OK"
}