Document coroutines codegen: superclasses

This commit is contained in:
Ilmir Usmanov
2020-08-17 17:21:45 +02:00
committed by Ilmir Usmanov
parent bfd0291572
commit 0814c5cc93
@@ -1184,3 +1184,154 @@ or `yieldAll`. Furthermore, we do not want to intercept their continuations. We
If we look at `sequence`, the `SequenceScope` interface is annotated with the annotation.
Since we do not want to intercept the continuations, their `context`s cannot be other than `EmptyCoroutineContext`.
### Coroutine Superclasses
Below is the diagram of all continuation classes, defined in the standard library and used by the compiler:
```text
+------------+
|Continuation|
+------+-----+
^
|
+---------+----------+
|BaseContinuationImpl+<---------------+
+---------+----------+ |
^ |
| |
+-------+--------+ +-------------+------------+
|ContinuationImpl| |RestrictedContinuationImpl|
+-------+--------+ +-------------+------------+
^ ^
| |
+-----+-------+ +-----------+-----------+
|SuspendLambda| |RestrictedSuspendLambda|
+-------------+ +-----------------------+
```
The main superinterface of all coroutines is `Continuation`:
```kotlin
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
```
which is the only interface accessible by users. It is, essentially, the core of the coroutines' machinery. With the continuation-passing
style, every suspending function and lambda accept additional continuation parameter.
Every compiler generated continuation extends `BaseContinuationImpl`:
```kotlin
abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>)
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
protected open fun releaseIntercepted()
public open fun create(completion: Continuation<*>): Continuation<Unit>
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit>
public override fun toString(): String
public override val callerFrame: CoroutineStackFrame?
public override fun getStackTraceElement(): StackTraceElement?
}
```
Note, its `resumeWith` function is final, but it introduces the `invokeSuspend` function. The `resumeWith` function does
the following:
1. When the user calls its `resumeWith`, it calls `invokeSuspend` with the argument, resuming the suspended coroutine with passed result or
exception.
2. It calls `completion` continuation's `resumeWith` when the coroutine completes so that execution returns to the caller.
3. It catches exceptions and resumes the `completion` with it, wrapped in `Result`, thus propagating the exception to the caller.
Additionally, `resumeWith` calls `releaseIntercepted` upon coroutine's completion to clear the interceptor up.
The compiler generates `create` overrides for suspending lambdas with zero or one parameter, since `createCoroutineUnintercepted` calls
them.
The rest (`callerFrame` and `getStackTraceElement`) come from the `CoroutineStackFrame` interface, and the debugger and kotlinx.coroutines
library use the interface to generate async stack traces.
The next class is `ContinuationImpl`. Every continuation of a suspending function, generated by the compiler, extends this class. Note that
the compiler does not generate restricted suspend functions (yet).
```kotlin
abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
public override val context: CoroutineContext
protected override fun releaseIntercepted()
private var intercepted: Continuation<Any?>?
public fun intercepted(): Continuation<Any?>
}
```
It adds the `intercepted` field and `intercepted()` function covered in the corresponding section.
For restricted suspend function, there is`RestrictedContinuationImpl` class, and thus their context can only be `EmptyCoroutineContext`. It
allows us to save several bytes when one calls `startCoroutine` on a suspending functional type, which does not inherit
`BaseContinuationImpl`. For example, when
the receiver is a callable reference to suspend functions
the context of root continuation, passed to `startCoroutine` is `EmptyCoroutineContext`
```kotlin
abstract class RestrictedContinuationImpl(
completion: Continuation<Any?>?
) : BaseContinuationImpl(completion) {
public override val context: CoroutineContext
}
```
All non-restricted generated suspend lambdas extend `SuspendLambda`:
```kotlin
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction
```
since all suspend lambdas are functional types, they implement the `FunctionBase` interface. `SuspendFunction` is a marker interface used in
type checks and type conversions (see the next subsection).
All restricted generated suspend lambdas extend `RestrictedSuspendLambda`:
```kotlin
abstract class RestrictedSuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : RestrictedContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction
```
the only difference from `SuspendLambda` is superclass. `SuspendLambda` inherits `ContinuationImpl`, while
`RestrictedSuspendLambda` inherits `RestrictedSuspendLambda`.
#### SuspendFunction{N}
Every suspending lambda has a special suspend functional type: `SuspendFunction{N}`, where `{N}` is the number of lambda parameters. They
only exist during compile-time and are changed to `Function{N+1}` and `SuspendFunction`. Because `SuspendFunction{N}` is not present at
runtime, there would be no way to distinguish ordinary functional type
from suspend functional type if we would not use `SuspendFunction` marker interface. To be more specific, it is used in
`is SuspendFunction{N}` and `as SuspendFunction{N}` expressions. For example, if we have code like
```kotlin
fun main() {
val lambda: suspend () -> Unit = {}
val a: Any = lambda
print(a is (suspend () -> Unit))
}
```
for `is` expression we generate something like
```kotlin
a is SuspendFunction and TypeIntrinsics.isFunctionOfArity(a, 1)
```
That, by the way, is the reason why `SuspendLambda`'s constructor accepts arity.
Of course, all generated suspend lambdas implement `SuspendFunction` through `SuspendLambda` and `RestrcitedSuspendLambda`. Callable
references to suspend functions implement the interface directly.
#### 1.2: CoroutineImpl
In experimental coroutines, the diagram is much simpler.
```text
+------------+
|Continuation|
+------+-----+
^
|
+------+------+
|CoroutineImpl|
+-------------+
```
Every suspending function's continuation or suspend lambda extends `CoroutineImpl`.