Document coroutines codegen: inliner part 1
This commit is contained in:
committed by
Ilmir Usmanov
parent
e2c5f9d245
commit
771b0c006f
+307
-1
@@ -1919,4 +1919,310 @@ way, we do the same in `callSuspend` and `callSuspendBy` functions.
|
||||
|
||||
However, in this example, we cannot be sure that `generic` returns `Unit`. In this case, the compiler disables tail-call optimization. More
|
||||
generally, the compiler disables tail-call optimization for functions returning `Unit` if the function overrides a function, returning
|
||||
non-`Unit` type.
|
||||
non-`Unit` type.
|
||||
|
||||
## Inline
|
||||
|
||||
Inlining a suspend function is a tricky business.
|
||||
|
||||
Before we get further, let us explain that there are 64 possible combinations of inline functions with parameter:
|
||||
1. The function can be either suspend or ordinary
|
||||
2. A parameter can be noinline, inline or crossinline lambda; or no lambda at all
|
||||
3. An argument can be a block, which we inline, or a variable, which we cannot inline
|
||||
4. The parameter can be suspendable or ordinary
|
||||
5. We can inline the function or call it via reflection
|
||||
|
||||
Some of them are ill-formed, of course, because there are no suspend non-functional parameters in Kotlin. Suspend parameters can have only
|
||||
functional types. Also, we cannot inline variable when we call it via reflection.
|
||||
|
||||
Let us draw a table with all possible combinations, which are valid and fill it one row at a time:
|
||||
```text
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||
+========+==============+=============+==============+=========+===============================================+
|
||||
|ordinary|noinline |block |ordinary |inline |no suspend call allowed |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|noinline |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|noinline |variable |ordinary |inline |no suspend call allowed |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|noinline |variable |ordinary |call |no suspend call allowed |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|noinline |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|noinline |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|inline |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|crossinine |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|no lambda |variable |ordinary |inline |no suspend call allowed |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|ordinary|no lambda |variable |ordinary |call |no suspend call allowed |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |inline |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |block |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |variable |suspend |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |crossinine |variable |suspend |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |no lambda |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |no lambda |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
```
|
||||
|
||||
Note that not all ordinary functions with ordinary parameters are out of the scope of the document.
|
||||
|
||||
Let us explain what `block` and `variable` of `argument kind` mean. We can call an inline function with lambda parameter in a couple of
|
||||
ways: by providing a block or passing a variable of lambda type:
|
||||
```kotlin
|
||||
inline fun inlineMe(c: () -> Unit) { c() }
|
||||
|
||||
fun main() {
|
||||
// block
|
||||
inlineMe { println("block") }
|
||||
|
||||
// variable
|
||||
val variable = { println("block") }
|
||||
inlineMe(variable)
|
||||
}
|
||||
```
|
||||
|
||||
With this out of the way, let us now consider suspend inline functions with no lambda parameter.
|
||||
|
||||
### Inline Suspend Functions with Ordinary Parameters
|
||||
|
||||
```text
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||
+========+==============+=============+==============+=========+===============================================+
|
||||
|suspend |no lambda |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |block |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |inline | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
```
|
||||
|
||||
Suspend functions should have a state-machine unless they are tail-call, and we cannot inline a state-machine into another state-machine,
|
||||
since it will reset the label field.
|
||||
|
||||
We generate them as if they are ordinary functions, but they have all the markers already generated. In other words, if we have an inline
|
||||
suspend function like:
|
||||
```kotlin
|
||||
suspend fun returnsUnit() = suspendCoroutine<Unit> { it.resume(Unit) }
|
||||
|
||||
suspend inline fun inlineMe() {
|
||||
returnsUnit()
|
||||
}
|
||||
```
|
||||
|
||||
The compiler generates the following bytecode:
|
||||
```text
|
||||
ALOAD 1 // continuation
|
||||
ICONST 0 // before suspending marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
INVOKESTATIC returnsUnit()
|
||||
ICONST_2 // returns unit marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
ICONST 1 // after suspending marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
POP
|
||||
GETSTATIC kotlin/Unit.INSTANCE
|
||||
ARETURN
|
||||
```
|
||||
if one looks closely, this is exactly the bytecode that would come to `CoroutineTransformerMethodVisitor` if we would not have declared the
|
||||
function inline, but without stack spilling markers, since the inliner spills the stack for us.
|
||||
|
||||
Now, let us have another inline function, which calls this one.
|
||||
```kotlin
|
||||
suspend inline fun inlineMe2() {
|
||||
inlineMe()
|
||||
inlineMe()
|
||||
}
|
||||
```
|
||||
the generated bytecode would look like:
|
||||
```text
|
||||
ALOAD 1 // continuation
|
||||
ICONST 0 // before suspending marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
INVOKESTATIC returnsUnit()
|
||||
ICONST_2 // returns unit marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
ICONST 1 // after suspending marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
POP
|
||||
|
||||
ALOAD 1 // continuation
|
||||
ICONST 0 // before suspend marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
INVOKESTATIC returnsUnit()
|
||||
ICONST_2 // returns unit marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
ICONST 1 // after suspend marker
|
||||
INVOKESTATIC InlineMarker.mark
|
||||
POP
|
||||
|
||||
GETSTATIC kotlin/Unit.INSTANCE
|
||||
ARETURN
|
||||
```
|
||||
as one sees, it inlines the inline functions. However, it does not generate a state-machine since the function will be inlined later. Note
|
||||
that the compiler does not generate suspending markers around inlined bytecode, since the state-machine should be flat, so nested suspension
|
||||
points are not allowed.
|
||||
|
||||
When lambda parameters are ordinary, there are no markers around the parameter call.
|
||||
|
||||
Now we can fill the rows:
|
||||
```text
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||
+========+==============+=============+==============+=========+===============================================+
|
||||
|suspend |no lambda |variable |ordinary |inline |no state-machine with markers |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |block |ordinary |inline |no state-machine with markers |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |inline |no state-machine with markers |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
```
|
||||
|
||||
### Java Interop and Reflection
|
||||
|
||||
```text
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||
+========+==============+=============+==============+=========+===============================================+
|
||||
|suspend |no lambda |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |call | |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
```
|
||||
|
||||
However, it, of course, is not that simple. Coroutine inlining is notorious for its complexity, simply because of the number of corner cases
|
||||
it needs to support. Backward interop with Java (calling Kotlin code from Java) is one of them. It should be possible to call inline
|
||||
functions from Java. Since javac does not inline them, generated code calls them directly. Yes, even inline suspend function. The reason
|
||||
for backward compatibility support is simple: reflection works the same as calling from Java. It does not inline the functions but calls
|
||||
them. For inline suspend functions, it means that when we call the function using reflection or Java, it should have a state-machine.
|
||||
However, on the other hand, we should not have it, since we cannot inline a state-machine into state-machine.
|
||||
|
||||
Thus, we cannot suffice by generating only one function. Instead, we generate two versions of the inline function: one of them is for
|
||||
inliner, the other is to call directly and via reflection. The name of the version for the direct call is left unchanged and has a
|
||||
state-machine and a continuation. The version for the inliner has no state-machine but has the suspend
|
||||
calls marked and name with `$$forInline` suffix.
|
||||
|
||||
For example, for code like
|
||||
```kotlin
|
||||
suspend fun returnsUnit() = suspendCoroutine<Unit> { it.resume(Unit) }
|
||||
|
||||
suspend inline fun inlineMe() {
|
||||
returnsUnit()
|
||||
}
|
||||
|
||||
suspend inline fun inlineMe2() {
|
||||
inlineMe()
|
||||
inlineMe()
|
||||
}
|
||||
```
|
||||
the compiler generates the following methods:
|
||||
```text
|
||||
// Can be called directly, have state-machine, unless tail-call
|
||||
public returnsUnit(L/kotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public inlineMe(L/kotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public inlineMe2(L/kotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
// For inliner use only, no state-machine, suspend calls are marked
|
||||
private inlineMe$$forInline(L/kotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
private inlineMe2$$forInline(L/kotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
```
|
||||
Note that `$$forInline` versions have private visibility, so tools like proguard can easily remove them. This whole function duplication is
|
||||
to preserve the semantics of the code, no matter whether we inline the function or call it via reflection.
|
||||
|
||||
```text
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||
+========+==============+=============+==============+=========+===============================================+
|
||||
|suspend |no lambda |variable |ordinary |call |state-machine |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
|suspend |noinline |variable |ordinary |call |state-machine |
|
||||
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||
```
|
||||
|
||||
### Inline-Only Functions
|
||||
|
||||
One exception to the rule of two functions is inline-only functions. For example, if they are annotated with `@kotlin.internal.InlineOnly`
|
||||
annotation or have reified type parameters. Since they cannot be called via reflection or from Java, we do not need to duplicate them. Thus,
|
||||
there is only one version - for inliner. However, since there is no version for
|
||||
reflection, we do not need to mangle these functions. In other words, we keep the name of the function. For example, if we have an
|
||||
inline-only function:
|
||||
```kotlin
|
||||
suspend fun blackhole() {}
|
||||
|
||||
suspend inline fun <reified T> inlineMe(t: T): T {
|
||||
blackhole()
|
||||
return t
|
||||
}
|
||||
```
|
||||
generated bytecode will look like:
|
||||
```text
|
||||
synthtic public inlineMe(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
ALOAC 0 // parameter
|
||||
ALOAD 1 // continuation
|
||||
ICONST_0 // before suspend call marker
|
||||
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||
INVOKESTATIC blackhole (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
ICONST_2 // returns Unit marker
|
||||
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||
ICONST_1 // after suspend call marker
|
||||
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||
POP
|
||||
ARETURN
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user