Document coroutines codegen: inliner part 2
This commit is contained in:
committed by
Ilmir Usmanov
parent
771b0c006f
commit
ddaab2c2bb
@@ -2226,3 +2226,240 @@ INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
|||||||
POP
|
POP
|
||||||
ARETURN
|
ARETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ordinary Inline Functions
|
||||||
|
|
||||||
|
Since ordinary inline functions do not have a state-machine, they can be both called directly and inlined. Thus, there is no difference
|
||||||
|
between `call,` and `inline` `call kind`s for `ordinary` functions. That reduces the number of distinct combinations even further.
|
||||||
|
|
||||||
|
There is, however, one notable exception.
|
||||||
|
|
||||||
|
### Ordinary Inline Parameter Of Ordinary Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|inline |block |ordinary |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |ordinary |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |ordinary |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose, we have code like
|
||||||
|
```kotlin
|
||||||
|
suspend fun isFoo(): Boolean = TODO()
|
||||||
|
|
||||||
|
suspend fun main() {
|
||||||
|
println(listOf(1, 2, 3).filter { isFoo() })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
As one sees, we use a suspend function inside ordinary inline lambda block argument of an ordinary inline function. The inliner inlines the
|
||||||
|
lambda into the function, and then the function is inlined into call-site, which is a suspend context. Thus, we should support it. This way,
|
||||||
|
we can retrieve continuation from the context to pass it to the call. Note that if either the function or the argument is not inlined, there
|
||||||
|
would be no continuation in the context. Thus we forbid the cases.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|inline |block |ordinary |inline |suspend calls in block allowed |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |ordinary |inline |no suspend calls allowed |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |ordinary |call |no suspend calls allowed |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ordinary Inline Parameter of Suspend Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |inline |block |ordinary |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |ordinary |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |ordinary |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the lambda is inlined, there is a continuation in context. Thus, we can call suspend functions. Also, this is a suspend inline
|
||||||
|
function. Thus we duplicate it.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |inline |block |ordinary |inline |suspend calls in block allowed, |
|
||||||
|
| | | | | |markers without state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |ordinary |inline |no suspend call in argument allowed, |
|
||||||
|
| | | | | |markers without state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |ordinary |call |no suspend call in argument allowed, |
|
||||||
|
| | | | | |state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suspend Inline Parameter of Suspend Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |inline |block |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |suspend |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
If the parameter is inlined into the function, the lambda's calls use the function's continuation. Thus, it is not necessary to define it
|
||||||
|
as suspend. So, we generate a warning for a suspend inline functions with inline suspending lambda parameters, asking the programmer to
|
||||||
|
remove `suspend` keyword. The programmer is free to ignore the warning in other cases. Since the most common case is inlining, the warning
|
||||||
|
makes sense, and we decided to keep it when we discovered that there are cases of no inlining.
|
||||||
|
|
||||||
|
Suspension points cannot be nested, so, we generate different suspend markers: inline suspend markers. They are either removed by the
|
||||||
|
inliner, if it inlines the lambda, or replaced by real suspend markers when there is no lambda inlining.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |inline |block |suspend |inline |WARNING to remove `suspend` keyword |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |suspend |inline |WARNING should be ignored, |
|
||||||
|
| | | | | |no state-machine with markers |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |inline |variable |suspend |call |WARNING should be ignored, state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline Suspend Parameter of Ordinary Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|inline |block |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |suspend |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the suspend lambda parameter's body should be inlined into the body of an ordinary inline function, which, in turn, can be inlined
|
||||||
|
into a non-suspend context, there is no way we can retrieve continuation to pass to suspend calls, which the lambda could have. In other
|
||||||
|
words, we forbid this combination.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|inline |block |suspend |inline |FORBIDDEN |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |suspend |inline |FORBIDDEN |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|inline |variable |suspend |call |FORBIDDEN |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Noinline Suspend Parameter of Suspend Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |noinline |block |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |noinline |variable |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |noinline |variable |suspend |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Before we get to inline suspend lambdas, or crossinline suspend lambdas, let us see how noinline suspend lambda parameters of inline
|
||||||
|
functions work. For example, if we have even the simplest function possible:
|
||||||
|
```kotlin
|
||||||
|
suspend inline fun inlineMe(noinline c: suspend () -> Unit) { c() }
|
||||||
|
```
|
||||||
|
we need to wrap the call of the parameter with suspending markers; otherwise, the call will not have a state in the state-machine. Thus,
|
||||||
|
the compiler generates code like
|
||||||
|
```text
|
||||||
|
ALOAD 0 // lambda parameter
|
||||||
|
ALOAD 1 // continuation
|
||||||
|
ICONST_0 // before suspending call marker
|
||||||
|
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||||
|
INVOKEINTERFACE Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object;
|
||||||
|
ICONST_2 // returns Unit marker
|
||||||
|
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||||
|
ICONST_1 // after suspending call marker
|
||||||
|
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
|
||||||
|
POP
|
||||||
|
GETSTATIC kotlin/Unit.INSTANCE
|
||||||
|
ARETURN
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course, for `call`, we also generate a state-machine.
|
||||||
|
|
||||||
|
Note, that we can call `noinline` parameter inside inner lambda or object, like
|
||||||
|
```kotlin
|
||||||
|
suspend inline fun inlineMe(noinline c: suspend () -> Unit) = suspend { c() }
|
||||||
|
```
|
||||||
|
in this case, we just generate a state-machine for the lambda, and the inliner copies it when it inlines the function.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|suspend |noinline |block |suspend |inline |if called in function, markers around invoke |
|
||||||
|
| | | | | |if called in inner function, state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |noinline |variable |suspend |inline |if called in function, markers around invoke |
|
||||||
|
| | | | | |if called in inner function, state-machine |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|suspend |noinline |variable |suspend |call |state-machine with parameter invoke in a state |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Noinline Suspend Parameter of Ordinary Inline Function
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|noinline |block |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|noinline |variable |suspend |inline | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|noinline |variable |suspend |call | |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to call suspend noinline parameter, we need a suspend context. The easiest way to do this is to wrap the call in a suspend lambda:
|
||||||
|
```kotlin
|
||||||
|
inline fun inlineMe(noinline c: suspend () -> Unit) = suspend { c() }
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is the same as in the previous section: the call is inside a separate state, but this time it is not inside the function itself,
|
||||||
|
it is inside the lambda. Since this is an ordinary inline function, it has no state-machine, the lambda does. The inliner copies its class
|
||||||
|
when it inlines the function and leaves the state-machine. This way, either we both inline the function and call it, semantics are the same.
|
||||||
|
|
||||||
|
```text
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|function|parameter kind|argument kind|parameter type|call kind|notes |
|
||||||
|
+========+==============+=============+==============+=========+===============================================+
|
||||||
|
|ordinary|noinline |block |suspend |inline |state-machine with parameter invoke in a state |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|noinline |variable |suspend |inline |state-machine with parameter invoke in a state |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
|ordinary|noinline |variable |suspend |call |state-machine with parameter invoke in a state |
|
||||||
|
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user