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:
+42
@@ -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"
|
||||
}
|
||||
+44
@@ -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"
|
||||
}
|
||||
+46
@@ -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"
|
||||
}
|
||||
+40
@@ -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"
|
||||
}
|
||||
+35
@@ -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"
|
||||
}
|
||||
+70
@@ -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
|
||||
}
|
||||
+38
@@ -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"
|
||||
}
|
||||
+53
@@ -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"
|
||||
}
|
||||
+45
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user