[JS IR BE] Fix runtime for Release coroutines

This commit is contained in:
Roman Artemev
2018-09-14 19:30:34 +03:00
committed by romanart
parent c4ddf2e9ae
commit c0ef1311ba
11 changed files with 350 additions and 65 deletions
@@ -15,16 +15,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.test.TargetBackend
import java.io.File
private val runtimeSources = listOfKtFilesFrom(
// TODO: share more coroutine code between JS BEs
// TODO: don't generate code for external declarations, until that:
// it's important now than *H.kt files at the start since we generate code for them
// and actual versions just will override them later.
"libraries/stdlib/coroutines-experimental/src/kotlin/coroutines/experimental/CoroutinesExperimentalH.kt",
"libraries/stdlib/coroutines-experimental/src/kotlin/coroutines/experimental/CoroutinesIntrinsicsExperimentalH.kt",
"libraries/stdlib/coroutines-experimental/src/kotlin/coroutines/experimental/SequenceBuilder.kt",
"libraries/stdlib/coroutines-experimental/src/kotlin/coroutines/experimental/Coroutines.kt",
private val runtimeSourcesCommon = listOfKtFilesFrom(
"core/builtins/src/kotlin",
"libraries/stdlib/common/src",
"libraries/stdlib/src/kotlin/",
@@ -80,9 +71,27 @@ private val runtimeSources = listOfKtFilesFrom(
"libraries/stdlib/js/irRuntime/rangeExtensions.kt"
)
private val coroutine12Files = listOfKtFilesFrom(
"libraries/stdlib/js/irRuntime/coroutines_12"
)
private val coroutine13Files = listOfKtFilesFrom(
"libraries/stdlib/coroutines/common",
"libraries/stdlib/coroutines/js/src/kotlin/coroutines/SafeContinuationJs.kt",
"libraries/stdlib/coroutines/src",
// TODO: merge coroutines_13 with JS BE coroutines
"libraries/stdlib/js/irRuntime/coroutines_13"
)
private var runtimeResult: Result? = null
private val runtimeFile = File("js/js.translator/testData/out/irBox/testRuntime.js")
private val runtimeSources_12 = (runtimeSourcesCommon - coroutine13Files + coroutine12Files).distinct()
private val runtimeSources_13 = (runtimeSourcesCommon - coroutine12Files + coroutine13Files).distinct()
private val runtimeSources = runtimeSources_13
abstract class BasicIrBoxTest(
pathToTestDir: String,
testGroupOutputDirPrefix: String,
@@ -128,11 +137,11 @@ abstract class BasicIrBoxTest(
LanguageFeature.MultiPlatformProjects to LanguageFeature.State.ENABLED
),
analysisFlags = mapOf(
AnalysisFlag.useExperimental to listOf("kotlin.contracts.ExperimentalContracts", "kotlin.Experimental")
AnalysisFlag.useExperimental to listOf("kotlin.contracts.ExperimentalContracts", "kotlin.Experimental"),
AnalysisFlag.allowResultReturnType to true
)
)
if (runtimeResult == null) {
runtimeResult = compile(config.project, runtimeSources.map(::createPsiFile), runtimeConfiguration)
runtimeFile.write(runtimeResult!!.generatedCode)
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.js
import kotlin.js.coroutineAliases.*
@PublishedApi
internal fun <T> getContinuation(): ContinuationAlias<T> { throw Exception("Implemented as intrinsic") }
// Do we really need this intrinsic in JS?
@PublishedApi
internal suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T {
throw Exception("Implemented as intrinsic")
}
@PublishedApi
internal fun <T> interceptContinuationIfNeeded(
context: CoroutineContextAlias,
continuation: ContinuationAlias<T>
) = context[ContinuationInterceptorAlias]?.interceptContinuation(continuation) ?: continuation
@SinceKotlin("1.2")
@PublishedApi
internal suspend fun getCoroutineContext(): CoroutineContextAlias = getContinuation<Any?>().context
// TODO: remove `JS` suffix oncec `NameGenerator` is implemented
@PublishedApi
internal suspend fun <T> suspendCoroutineUninterceptedOrReturnJS(block: (ContinuationAlias<T>) -> Any?): T =
returnIfSuspended<T>(block(getContinuation<T>()))
@@ -0,0 +1,9 @@
package kotlin.js.coroutineAliases
import kotlin.coroutines.experimental.*
typealias ContinuationAlias<T> = Continuation<T>
typealias CoroutineContextAlias = CoroutineContext
typealias ContinuationInterceptorAlias = ContinuationInterceptor
@@ -7,11 +7,9 @@ package kotlin.coroutines.experimental.intrinsics
import kotlin.coroutines.experimental.*
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
@Suppress("UNUSED_PARAMETER")
public suspend inline fun <T> suspendCoroutineOrReturn(crossinline block: (Continuation<T>) -> Any?): T =
suspendCoroutineUninterceptedOrReturn { cont -> block(cont.intercepted()) }
@PublishedApi
internal fun <T> normalizeContinuation(continuation: Continuation<T>): Continuation<T> =
(continuation as? CoroutineImpl)?.facade ?: continuation
/**
* Obtains the current continuation instance inside suspend functions and either suspends
@@ -19,9 +17,16 @@ public suspend inline fun <T> suspendCoroutineOrReturn(crossinline block: (Conti
*
* Unlike [suspendCoroutineOrReturn] it does not intercept continuation.
*/
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
@Suppress("UNUSED_PARAMETER")
public suspend inline fun <T> suspendCoroutineOrReturn(crossinline block: (Continuation<T>) -> Any?): T =
suspendCoroutineUninterceptedOrReturn { cont -> block(cont.intercepted()) }
@SinceKotlin("1.2")
public suspend fun <T> suspendCoroutineUninterceptedOrReturn(block: (Continuation<T>) -> Any?): T =
returnIfSuspended<T>(block(getContinuation<T>()))
throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic")
/**
* Intercept continuation with [ContinuationInterceptor].
@@ -30,14 +35,9 @@ public suspend fun <T> suspendCoroutineUninterceptedOrReturn(block: (Continuatio
@kotlin.internal.InlineOnly
public inline fun <T> Continuation<T>.intercepted() = normalizeContinuation<T>(this)
/**
* This value is used as a return value of [suspendCoroutineOrReturn] `block` argument to state that
* the execution was suspended and will not return any result immediately.
*/
@SinceKotlin("1.1")
public val COROUTINE_SUSPENDED: Any = Any()
@SinceKotlin("1.1")
@Suppress("UNCHECKED_CAST")
@kotlin.internal.InlineOnly
@@ -291,6 +291,14 @@ private val RESUMED: Any? = Any()
private class Fail(val exception: Throwable)
@SinceKotlin("1.2")
@Suppress("WRONG_MODIFIER_TARGET")
@kotlin.internal.InlineOnly
public suspend inline val coroutineContext: CoroutineContext
get() {
throw NotImplementedError("Implemented as intrinsic")
}
@kotlin.internal.InlineOnly
internal inline fun processBareContinuationResume(completion: Continuation<*>, block: () -> Any?) {
try {
@@ -3,36 +3,11 @@
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.js
import kotlin.coroutines.experimental.*
internal fun <T> getContinuation(): Continuation<T> { throw Exception("Implemented as intrinsic") }
// Do we really need this intrinsic in JS?
internal suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T {
throw Exception("Implemented as intrinsic")
}
fun <T> normalizeContinuation(continuation: Continuation<T>): Continuation<T> =
(continuation as? CoroutineImpl)?.facade ?: continuation
internal fun <T> interceptContinuationIfNeeded(
context: CoroutineContext,
continuation: Continuation<T>
) = context[ContinuationInterceptor]?.interceptContinuation(continuation) ?: continuation
@SinceKotlin("1.2")
@Suppress("WRONG_MODIFIER_TARGET")
public suspend val coroutineContext: CoroutineContext
get() {
throw Exception("Implemented as intrinsic")
}
package kotlin.coroutines.experimental
internal abstract class CoroutineImpl(private val completion: Continuation<Any?>) : Continuation<Any?> {
protected var exceptionState = 0
protected var label: Int = 0
protected var state: Int = 0
protected var exception: dynamic = null
protected var result: dynamic = null
@@ -51,7 +26,7 @@ internal abstract class CoroutineImpl(private val completion: Continuation<Any?>
override fun resumeWithException(exception: Throwable) {
// TODO: once we have arrays working refact it with exception table
this.label = exceptionState
this.state = exceptionState
this.exception = exception
doResumeWrapper()
}
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.coroutines
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
@SinceKotlin("1.3")
@JsName("CoroutineImpl")
internal abstract class CoroutineImpl(private val resultContinuation: Continuation<Any?>?) : Continuation<Any?> {
protected var state = 0
protected var exceptionState = 0
protected var result: dynamic = null
protected var exception: dynamic = null
protected var finallyPath: Array<Int>? = null
public override val context: CoroutineContext get() = resultContinuation!!.context
private var intercepted_: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted_
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted_ = it }
override fun resumeWith(result: Result<Any?>) {
var current = this
var currentResult: Any? = result.getOrNull()
var currentException: Throwable? = result.exceptionOrNull()
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
while (true) {
with(current) {
// Set result and exception fields in the current continuation
if (currentException == null) {
this.result = currentResult
} else {
state = exceptionState
exception = currentException
}
try {
val outcome = doResume()
if (outcome === COROUTINE_SUSPENDED) return
currentResult = outcome
currentException = null
} catch (exception: dynamic) { // Catch all exceptions
currentResult = null
currentException = exception.unsafeCast<Throwable>()
}
releaseIntercepted() // this state machine instance is terminating
val completion = resultContinuation!!
if (completion is CoroutineImpl) {
// unrolling recursion via loop
current = completion
} else {
// top-level completion reached -- invoke and return
if (currentException != null) {
completion.resumeWithException(currentException!!)
} else {
completion.resume(currentResult)
}
return
}
}
}
}
private fun releaseIntercepted() {
val intercepted = intercepted_
if (intercepted != null && intercepted !== this) {
context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
}
this.intercepted_ = CompletedContinuation // just in case
}
protected abstract fun doResume(): Any?
public open fun create(completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Continuation) has not been overridden")
}
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
}
}
internal object CompletedContinuation : Continuation<Any?> {
override val context: CoroutineContext
get() = error("This continuation is already complete")
override fun resumeWith(result: Result<Any?>) {
error("This continuation is already complete")
}
override fun toString(): String = "This continuation is already complete"
}
@@ -0,0 +1,148 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "UNCHECKED_CAST")
package kotlin.coroutines.intrinsics
import kotlin.coroutines.*
import kotlin.internal.InlineOnly
/**
* Starts unintercepted coroutine without receiver and with result type [T] and executes it until its first suspension.
* Returns the result of the coroutine or throws its exception if it does not suspend or [COROUTINE_SUSPENDED] if it suspends.
* In the later case, the [completion] continuation is invoked when coroutine completes with result or exception.
*
* The coroutine is started directly in the invoker's thread without going through the [ContinuationInterceptor] that might
* be present in the completion's [CoroutineContext]. It is invoker's responsibility to ensure that the proper invocation
* context is established.
*
* This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of suspended
* coroutine using a reference to the suspending function.
*/
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun <T> (suspend () -> T).startCoroutineUninterceptedOrReturn(
completion: Continuation<T>
): Any? {
val a = this.asDynamic()
return if (jsTypeOf(a) == "function") a(completion, false)
else a.invoke(completion)
}
/**
* Starts unintercepted coroutine with receiver type [R] and result type [T] and executes it until its first suspension.
* Returns the result of the coroutine or throws its exception if it does not suspend or [COROUTINE_SUSPENDED] if it suspends.
* In the later case, the [completion] continuation is invoked when coroutine completes with result or exception.
*
* The coroutine is started directly in the invoker's thread without going through the [ContinuationInterceptor] that might
* be present in the completion's [CoroutineContext]. It is invoker's responsibility to ensure that the proper invocation
* context is established.
*
* This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of suspended
* coroutine using a reference to the suspending function.
*/
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun <R, T> (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
completion: Continuation<T>
): Any? {
val a = this.asDynamic()
return if(jsTypeOf(a) == "function") a(receiver, completion, false)
else a.invoke_P1(receiver, completion)
}
/**
* Creates unintercepted coroutine without receiver and with result type [T].
* This function creates a new, fresh instance of suspendable computation every time it is invoked.
*
* To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance.
* The [completion] continuation is invoked when coroutine completes with result or exception.
*
* This function returns unintercepted continuation.
* Invocation of `resume(Unit)` starts coroutine directly in the invoker's thread without going through the
* [ContinuationInterceptor] that might be present in the completion's [CoroutineContext].
* It is invoker's responsibility to ensure that the proper invocation context is established.
* [Continuation.intercepted] can be used to acquire the intercepted continuation.
*
* Repeated invocation of any resume function on the resulting continuation corrupts the
* state machine of the coroutine and may result in arbitrary behaviour or exception.
*/
@SinceKotlin("1.3")
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> =
// Kotlin/JS suspend lambdas have an extra parameter `suspended`
if (this.asDynamic().length == 2) {
// When `suspended` is true the continuation is created, but not executed
this.asDynamic()(completion, true)
} else {
createCoroutineFromSuspendFunction(completion) {
val a = this.asDynamic()
if (jsTypeOf(a) == "function") {
a(completion)
} else {
a.invoke(completion)
}
}
}
/**
* Creates unintercepted coroutine with receiver type [R] and result type [T].
* This function creates a new, fresh instance of suspendable computation every time it is invoked.
*
* To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance.
* The [completion] continuation is invoked when coroutine completes with result or exception.
*
* This function returns unintercepted continuation.
* Invocation of `resume(Unit)` starts coroutine directly in the invoker's thread without going through the
* [ContinuationInterceptor] that might be present in the completion's [CoroutineContext].
* It is invoker's responsibility to ensure that the proper invocation context is established.
* [Continuation.intercepted] can be used to acquire the intercepted continuation.
*
* Repeated invocation of any resume function on the resulting continuation corrupts the
* state machine of the coroutine and may result in arbitrary behaviour or exception.
*/
@SinceKotlin("1.3")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> =
// Kotlin/JS suspend lambdas have an extra parameter `suspended`
if (this.asDynamic().length == 3) {
// When `suspended` is true the continuation is created, but not executed
this.asDynamic()(receiver, completion, true)
} else {
createCoroutineFromSuspendFunction(completion) {
val a = this.asDynamic()
if (jsTypeOf(a) == "function") {
a(receiver, completion)
} else {
a.invoke_P1(receiver, completion)
}
}
}
/**
* Intercepts this continuation with [ContinuationInterceptor].
*/
@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? CoroutineImpl)?.intercepted() ?: this
private inline fun <T> createCoroutineFromSuspendFunction(
completion: Continuation<T>,
crossinline block: () -> Any?
): Continuation<Unit> {
return object : CoroutineImpl(completion as Continuation<Any?>) {
override fun doResume(): Any? {
if (exception != null) throw exception
return block()
}
}
}
@@ -0,0 +1,9 @@
package kotlin.js.coroutineAliases
import kotlin.coroutines.*
typealias ContinuationAlias<T> = Continuation<T>
typealias CoroutineContextAlias = CoroutineContext
typealias ContinuationInterceptorAlias = ContinuationInterceptor
@@ -97,3 +97,5 @@ fun THROW_CCE() {
fun THROW_NPE() {
throw NullPointerException()
}
fun error(s: String): Nothing = throw IllegalStateException(s, null)
-14
View File
@@ -5,8 +5,6 @@
package kotlin
import kotlin.coroutines.experimental.SequenceBuilder
// TODO: Ignore FunctionN interfaces
public interface Function0<out R> : Function<R> {
@@ -26,15 +24,3 @@ public interface Function3<in P1, in P2, in P3, out R> : Function<R> {
}
public inline fun <reified T> arrayOfNulls(size: Int): Array<T?> = js("[]") // FIXME: Implement
internal inline fun <T> buildSequence(noinline builderAction: suspend SequenceBuilder<T>.() -> Unit): Sequence<T> =
kotlin.coroutines.experimental.buildSequence(builderAction)
internal inline fun <T> buildIterator(noinline builderAction: suspend SequenceBuilder<T>.() -> Unit): Iterator<T> =
kotlin.coroutines.experimental.buildIterator(builderAction)
internal inline fun <T> sequence(noinline builderAction: suspend SequenceBuilder<T>.() -> Unit): Sequence<T> =
kotlin.coroutines.experimental.buildSequence(builderAction)
internal inline fun <T> iterator(noinline builderAction: suspend SequenceBuilder<T>.() -> Unit): Iterator<T> =
kotlin.coroutines.experimental.buildIterator(builderAction)