JVM: Check for multiple {POP, Unit} sequences in suspend function TCO

We already check for {POP, Unit} sequence before ARETURN, but if the
there are multiple sequence before ARETURN, the compiler assumes, that
TCO misses.

The fix is to check, that the instruction after the sequence is either
ARETURN or another {POP, Unit} sequence.

 #KT-50835
 #KT-54152 Fixed
This commit is contained in:
Ilmir Usmanov
2022-09-14 21:26:11 +02:00
parent 45b608e195
commit 3ee09f05ef
6 changed files with 96 additions and 1 deletions
@@ -0,0 +1,72 @@
// TARGET_BACKEND: JVM
// FULL_JDK
// WITH_STDLIB
// WITH_COROUTINES
// CHECK_TAIL_CALL_OPTIMIZATION
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
fun assert(value: () -> Boolean) {}
class ChannelSegment<E>(val id: Long)
suspend fun suspendHere() = suspendCoroutineUninterceptedOrReturn<Unit> { x ->
TailCallOptimizationChecker.saveStackTrace(x)
COROUTINE_SUSPENDED
}
private const val RESULT_SUSPEND_NO_WAITER = 3
open class BufferedChannel<E> {
private val sendSegment = ChannelSegment<E>(0)
suspend fun send(element: E): Unit =
sendImpl(
onClosed = {},
onNoWaiterSuspend = { sendOnNoWaiterSuspend() }
)
private suspend fun sendOnNoWaiterSuspend() {
suspendHere()
}
private fun findSegmentSend(): ChannelSegment<E>? = null
private fun updateCellSend(): Int = RESULT_SUSPEND_NO_WAITER
private inline fun <R> sendImpl(
onClosed: () -> R,
onNoWaiterSuspend: () -> R = { error("unexpected") }
): R {
var segment = sendSegment
while (true) {
val closed = false
val id = 0L
if (segment.id != id) {
assert { segment.id < id }
// Find the required segment.
segment = null ?:
if (closed) return onClosed() else continue
}
when(updateCellSend()) {
RESULT_SUSPEND_NO_WAITER -> {
return onNoWaiterSuspend()
}
}
}
}
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(EmptyContinuation)
}
fun box(): String {
builder {
BufferedChannel<Int>().send(0)
}
TailCallOptimizationChecker.checkNoStateMachineIn("send")
return "OK"
}