JVM: Make coroutines spilling tests runtime
instead of bytecode text ones. Check the content of continuation object instead of bytecode, since this is less prone to changes during changes in coroutines codegen. #KT-48678
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
// IGNORE_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test() {
|
||||
for (i in 0..1) {
|
||||
// Should be cleanup, since there is a back edge from the rest of the loop
|
||||
saveSpilledVariables()
|
||||
val a = "a"
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
}
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test()
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "I$0" to "0", "L$0" to "null")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "I$0" to "0", "L$0" to "a")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "1", "I$0" to "1", "L$0" to "null")) return "FAIL 3: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "I$0" to "1", "L$0" to "a")) return "FAIL 4: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "I$0" to "1", "L$0" to "a")) return "FAIL 5: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test(check: Boolean) {
|
||||
if (check) {
|
||||
val a = "a1"
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
} else {
|
||||
val a = "a2"
|
||||
val b = "b2"
|
||||
saveSpilledVariables()
|
||||
blackhole(a, b)
|
||||
}
|
||||
// Cleanup both a and b, since the compiler does not know, which branch is going to executed
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test(true)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "L$0" to "a1", "L$1" to "null")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 3: $spilledVariables"
|
||||
|
||||
builder {
|
||||
test(false)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "a2", "L$1" to "b2")) return "FAIL 4: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 5: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 6: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test() {
|
||||
var a: String? = "a"
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
a = null
|
||||
// a is null, known at compile time, do not spill, but cleanup
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test()
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "L$0" to "a")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "null")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "null")) return "FAIL 3: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test() {
|
||||
val a = null
|
||||
// a is null, known at compile time, do not spill
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test()
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2")) return "FAIL 3: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test() {
|
||||
val a = "a"
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
// a is dead, cleanup
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test()
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "L$0" to "a")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "null")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "null")) return "FAIL 3: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println(box())
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test(a: String, b: String) {
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
saveSpilledVariables()
|
||||
blackhole(b)
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test("a", "b")
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "L$0" to "a", "L$1" to "b")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "2", "L$0" to "b", "L$1" to "null")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 3: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "3", "L$0" to "null", "L$1" to "null")) return "FAIL 4: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println(box())
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
val test: suspend (Int) -> Unit = { unused ->
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test(1)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "1")) return "FAIL 2: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: JVM
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun blackhole(vararg a: Any?) {}
|
||||
|
||||
val spilledVariables = mutableSetOf<Pair<String, String>>()
|
||||
|
||||
var c: Continuation<Unit>? = null
|
||||
|
||||
suspend fun saveSpilledVariables() = suspendCoroutineUninterceptedOrReturn<Unit> { continuation ->
|
||||
spilledVariables.clear()
|
||||
for (field in continuation.javaClass.declaredFields) {
|
||||
if (field.name != "label" && (field.name.length != 3 || field.name[1] != '$')) continue
|
||||
field.isAccessible = true
|
||||
spilledVariables += field.name to "${field.get(continuation)}"
|
||||
}
|
||||
c = continuation
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
suspend fun test(check: Int) {
|
||||
when (check) {
|
||||
0 -> {
|
||||
val a = "a0"
|
||||
saveSpilledVariables()
|
||||
blackhole(a)
|
||||
}
|
||||
1 -> {
|
||||
val a = "a1"
|
||||
val b = "b1"
|
||||
saveSpilledVariables()
|
||||
blackhole(a, b)
|
||||
}
|
||||
else -> {
|
||||
val a = "a2"
|
||||
val b = "b2"
|
||||
val c = 1
|
||||
saveSpilledVariables()
|
||||
blackhole(a, b, c)
|
||||
}
|
||||
}
|
||||
// Cleanup both a and b, but c is primitive, so no need to clean it up
|
||||
saveSpilledVariables()
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(Continuation(EmptyCoroutineContext) {
|
||||
it.getOrThrow()
|
||||
})
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
builder {
|
||||
test(0)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "1", "I$0" to "0", "L$0" to "a0", "L$1" to "null")) return "FAIL 1: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "0", "L$0" to "null", "L$1" to "null")) return "FAIL 2: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "0", "L$0" to "null", "L$1" to "null")) return "FAIL 3: $spilledVariables"
|
||||
|
||||
builder {
|
||||
test(1)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "2", "I$0" to "0", "L$0" to "a1", "L$1" to "b1")) return "FAIL 4: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "0", "L$0" to "null", "L$1" to "null")) return "FAIL 5: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "0", "L$0" to "null", "L$1" to "null")) return "FAIL 6: $spilledVariables"
|
||||
|
||||
builder {
|
||||
test(2)
|
||||
}
|
||||
if (spilledVariables != setOf("label" to "3", "I$0" to "1", "L$0" to "a2", "L$1" to "b2")) return "FAIL 7: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "1", "L$0" to "null", "L$1" to "null")) return "FAIL 8: $spilledVariables"
|
||||
c?.resume(Unit)
|
||||
if (spilledVariables != setOf("label" to "4", "I$0" to "1", "L$0" to "null", "L$1" to "null")) return "FAIL 9: $spilledVariables"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user