Inline
将suspend函数内联化是一件棘手的事情。
在我们深入讨论之前,让我们解释一下,携带参数的内联函数存在64种可能的组合:
- 函数可以是suspend函数或普通函数
- 参数可以是 noinline、inline 或 crossinline lambda,也可以没有 lambda
- 参数可以是我们内联的块,也可以是我们无法内联的变量
- 参数可以是可挂起的或普通的
- 我们可以内联函数,也可以通过反射调用它
当然,其中一些是不正确的,因为在 Kotlin 中没有suspend的非函数参数。suspend参数只能是函数类型。此外,当我们通过反射调用时,无法内联变量。
让我们绘制一个表格,列出所有可能的组合,并逐行填写:
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
请注意,并非所有带有普通参数的普通函数都不在本文档的范围之外。
让我们解释一下 argument kind
中的 block
和 variable
是什么意思。我们可以通过提供一个块或传递 lambda 类型的变量来调用带有 lambda 参数的内联函数:
kotlin
inline fun inlineMe(c: () -> Unit) { c() }
fun main() {
// block
inlineMe { println("block") }
// variable
val variable = { println("block") }
inlineMe(variable)
}
弄清楚这一点后,现在让我们考虑一下没有 lambda 参数的suspend内联函数。
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函数应该有一个状态机,除非它们是尾调用,并且我们不能将一个状态机内联到另一个状态机中,因为这会重置标签字段。
我们生成它们就像普通函数一样,但已经生成了所有的标记。换句话说,如果我们有一个内联suspend函数,像这样:
kotlin
suspend fun returnsUnit() = suspendCoroutine<Unit> { it.resume(Unit) }
suspend inline fun inlineMe() {
returnsUnit()
}
编译器生成的字节码如下:
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
如果仔细观察,这恰好是如果我们没有声明函数为内联时传递给 CoroutineTransformerMethodVisitor
的字节码,但没有堆栈溢出标记,因为内联器会为我们溢出堆栈。
现在,让我们再创建另一个内联函数,调用之前的函数。
kotlin
suspend inline fun inlineMe2() {
inlineMe()
inlineMe()
}
生成的字节码如下所示:
text
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
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
正如您所见,它内联了内联函数。但是,它不会生成状态机,因为该函数稍后将被内联。请注意,编译器不会在内联的字节码周围生成挂起标记,因为状态机应该是平的,所以不允许嵌套的挂起点。
当 lambda 参数为普通参数时,在参数调用周围没有标记。
现在我们可以填写表格:
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(Java 互操作和反射)
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|suspend |no lambda |variable |ordinary |call | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |variable |ordinary |call | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
然而,这并不简单。协程内联以其复杂性而著称,这主要是因为它需要支持的各种边界情况。与Java的后向互操作性(从Java调用Kotlin代码)就是其中之一。应该可以从Java调用内联函数。由于javac不会内联它们,生成的代码直接调用它们。是的,甚至是内联挂起函数。支持后向兼容性的原因很简单:反射的工作方式与从Java调用相同。它不会内联函数,而是直接调用它们。对于内联挂起函数来说,这意味着当我们使用反射或Java调用该函数时,它应该具有状态机。然而,另一方面,我们不应该这样做,因为我们无法将一个状态机内联到另一个状态机中。
因此,我们不能仅仅生成一个函数就足够了。相反,我们生成内联函数的两个版本:一个用于内联器,另一个用于直接调用和反射调用。用于直接调用的版本名称保持不变,并具有状态机和continuation。用于内联器的版本没有状态机,但具有标记的挂起调用,并带有$$forInline
后缀。
例如:
kotlin
suspend fun returnsUnit() = suspendCoroutine<Unit> { it.resume(Unit) }
suspend inline fun inlineMe() {
returnsUnit()
}
suspend inline fun inlineMe2() {
inlineMe()
inlineMe()
}
编译器生成以下方法:
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;
注意,$$forInline
版本具有私有可见性,因此像 ProGuard 这样的工具可以轻松删除它们。这种函数复制是为了保持代码的语义,无论是内联函数还是通过反射调用。
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
对于规则的一个例外是仅内联的函数。例如,如果它们带有 @kotlin.internal.InlineOnly
注解或具有具体化的类型参数。由于无法通过反射或从Java调用它们,因此我们不需要对它们进行复制。因此,只有一个版本 - 供内联器使用。但是,由于没有反射版本,我们不需要对这些函数进行处理。换句话说,我们保留函数的名称。例如,如果我们有一个仅内联的函数:
kotlin
suspend fun blackhole() {}
suspend inline fun <reified T> inlineMe(t: T): T {
blackhole()
return t
}
生成的字节码将如下所示:
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
Ordinary Inline Functions(普通内联函数)
由于普通内联函数没有状态机,它们既可以直接调用,也可以内联。因此,对于普通函数来说,"call" 和 "inline" 的调用种类之间没有区别。这进一步减少了不同组合的数量。
然而,有一个值得注意的例外。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
假设我们有以下代码:
kotlin
suspend fun isFoo(): Boolean = TODO()
suspend fun main() {
println(listOf(1, 2, 3).filter { isFoo() })
}
正如你所看到的,我们在普通内联函数的普通内联 lambda 块参数中使用了一个挂起函数。内联器将 lambda 内联到函数中,然后将函数内联到调用站点,调用站点是一个挂起上下文。因此,我们应该支持这种情况。这样,我们就可以从上下文中检索到 continuation,并将其传递给调用。请注意,如果函数或参数未被内联,上下文中将没有 continuation。因此,我们禁止这些情况。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
由于 lambda 被内联,上下文中存在 continuation。因此,我们可以调用挂起函数。而且,这是一个挂起内联函数。因此我们对其进行了复制。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
如果将参数内联到函数中,则 lambda 的调用将使用函数的 continuation。因此,将其定义为挂起是没有必要的。因此,我们对具有内联挂起 lambda 参数的挂起内联函数生成警告,要求程序员删除 suspend
关键字。程序员可以在其他情况下忽略此警告。由于最常见的情况是内联,因此警告是有意义的。我们决定在发现存在不内联的情况时保留此警告。
挂起点不能嵌套,因此我们生成不同的挂起标记:内联挂起标记。如果内联器将 lambda 内联,则它们将被移除;如果没有 lambda 内联,则会被真实的挂起标记替换。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
因为挂起 lambda 参数的主体应该内联到普通内联函数的主体中,而后者又可以内联到非挂起上下文中,所以我们无法检索 continuation 并将其传递给 lambda 可能具有的挂起调用。换句话说,我们禁止这种组合。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
在我们讨论内联挂起 lambda 或交叉内联挂起 lambda 之前,让我们看一下内联函数的非内联挂起 lambda 参数是如何工作的。例如,如果我们甚至有一个可能的最简单的函数:
kotlin
suspend inline fun inlineMe(noinline c: suspend () -> Unit) { c() }
我们需要用挂起标记包装参数的调用;否则,调用将不会在状态机中具有状态。因此,编译器生成的代码如下所示:
text
ALOAD 0 // lambda 参数
ALOAD 1 // continuation
ICONST_0 // 挂起调用前的标记
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
INVOKEINTERFACE Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object;
ICONST_2 // 返回 Unit 的标记
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
ICONST_1 // 挂起调用后的标记
INVOKESTATIC kotlin/jvm/internal/InlineMarker.mark
POP
GETSTATIC kotlin/Unit.INSTANCE
ARETURN
当然,对于 call
,我们也生成状态机。
请注意,我们可以在内部 lambda 或对象内调用 noinline
参数,就像下面这样:
kotlin
suspend inline fun inlineMe(noinline c: suspend () -> Unit) = suspend { c() }
在这种情况下,我们只为 lambda 生成状态机,并且当内联函数内联时,内联器会复制它。
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 | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
为了调用挂起的非内联参数,我们需要一个挂起上下文。最简单的方法是将调用包装在一个挂起 lambda 中:
kotlin
inline fun inlineMe(noinline c: suspend () -> Unit) = suspend { c() }
结果与前一节相同:调用位于一个单独的状态内,但这次它不在函数本身内,而是在 lambda 内部。由于这是一个普通的内联函数,它没有状态机,而 lambda 有。当内联函数内联时,内联器会复制它的类,并保留状态机。这样,无论是内联函数还是调用函数,语义都是相同的。
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 |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
Crossroutines
最后,协程代码生成中最棘手的部分:在挂起函数或 lambda 中捕获的跨内联 lambda,或称为"交叉协程"。
让我们从一个简单的例子开始:
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |block |suspend |inline | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
kotlin
interface SuspendRunnable {
suspend fun run()
}
suspend fun dummy() {}
inline fun crossinlineMe(crossinline c: suspend () -> Unit): SuspendRunnable =
object : SuspendRunnable {
override suspend fun run() {
dummy()
c()
}
}
The compiler generated code for the inline function will look like
kotlin
public final class crossilineMe$1($captured_local_variable$0: Function1<Continuation<Unit>, Unit>) {
final /*package-local*/ $c : Function1<Continuation<Unit>, Unit>
init {
$c = $captured_local_variable$0
}
public override fun run($completion: Continuation) {
beforeFakeContinuationConstructionCall()
run$1()
afterFakeContinuationConstructionCall()
dummy()
this.$c.invoke($completion)
}
public final class run$1 {
// Partially built continuation.
}
}
public final fun crossinlineMe(c: Function1<Continuation<Unit>, Unit>): SuspendRunnable {
return crossinlineMe$1(c)
}
当内联器内联 crossinlineMe
函数时,它首先将其 lambda 内联到对象中(这是称为匿名对象转换的过程),替换对象的使用,最后再内联函数。
注意 run$1
类。它是由代码生成器为内联器生成的"假continuation",也是一个continuation类。如果没有它,内联器将需要生成continuation类。但是,有了假continuation,它只需要转换假continuation,就像转换任何其他内部对象一样。假continuation就是一个没有溢出变量的continuation类。
For example, if we use the function, like:
kotlin
suspend fun main() {
crossinlineMe {
pritln("OK")
}.run()
}
Firstly, we transform the object:
kotlin
public final class main$1 {
public override fun run($completion: Continuation) {
val $continuation = if ($completion is run$1 && $completion.label.sign_bit.is_set) {
$completion.label.sign_bit.unset
$completion
} else {
run$1($completion)
}
when ($continuation.label) {
0 -> {
dummy()
// error handling is omitted
}
1 -> {
println("OK")
return
}
else -> {
error(...)
}
}
}
public final run$1: Continuation {
// Continuation's content
}
}
正如你所看到的,我们在转换过程中生成了状态机,并且内联器转换了假continuation。状态机构建器移除了假continuation的构造函数调用。为此,它查找假continuation构造函数调用前后的标记,它们的值分别为 4
和 5
。
然后,我们替换对象的使用:
kotlin
public final fun tmp(): SuspendRunnable {
return main$1()
}
最后,我们内联该函数:
kotlin
public final fun main$$$($completion: Continuation<Unit>) {
main$1().run()
}
既然我们将 lambda 内联到 run
函数中,我们无法在代码生成期间为其生成状态机,只能在转换期间生成。
由于参数声明为 suspend
,而函数本身则不是,我们无法在函数内部调用参数;我们需要有一些挂起上下文,即内部 lambda 或内部具有挂起函数的对象。
请注意,如果 lambda 不包含挂起调用,我们不应该将内联的 lambda 放入单独的状态中;换句话说,我们应该移除围绕内联代码的挂起标记。为此,我们引入了新的挂起标记:内联挂起调用前后的标记。它们的值分别为 6
和 7
。如果 lambda 被内联,那么在 lambda 被内联后,在构建状态机之前,内联器会移除这些标记。否则,如果参数未被内联,则它会将内联挂起标记替换为真实的挂起标记。
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |block |suspend |inline |inline suspend markers around invoke |
| | | | | |fake continuation for inliner to transform |
| | | | | |state-machine is built after inlining |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
现在假设我们不内联参数:
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |variable |suspend |inline | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |suspend |call | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
我们无法调用该函数,因为它在对象的挂起函数内部没有状态机。需要状态机和不需要状态机之间的这种困境与内联挂起函数相同。因此,解决方案也相同:我们复制挂起函数,在这种情况下是 run
:
kotlin
public final class crossilineMe$1($captured_local_variable$0: Function1<Continuation<Unit>, Unit>) {
final /*package-local*/ $c : Function1<Continuation<Unit>, Unit>
init {
$c = $captured_local_variable$0
}
public override fun run$$forInline($completion: Continuation) {
beforeFakeContinuationConstructionCall()
run$1()
afterFakeContinuationConstructionCall()
dummy()
this.$c.invoke($completion)
}
public override fun run($completion: Continuation) {
val $continuation = if ($completion is run$1 && $completion.label.sign_bit.is_set) {
$completion.label.sign_bit.unset
$completion
} else {
run$1($completion)
}
when ($continuation.label) {
0 -> {
dummy()
// error handling is omitted
}
1 -> {
println("OK")
return
}
else -> {
error(...)
}
}
}
public final run$1: Continuation {
// Continuation's content
}
}
public final fun crossinlineMe(c: Function1<Continuation<Unit>, Unit>): SuspendRunnable {
return crossinlineMe$1(c)
}
FIXME: 我们不再需要假continuation,除非函数是尾调用或仅内联。内联器会转换 run 的continuation,它可以从 run 的函数头中获取continuation。
正如你所见,我们有两个版本:一个带有状态机,当内联器转换对象时会被丢弃,另一个用于内联,作为生成"真正"状态机的模板。
在挂起的 lambda 的情况下,我们会复制其 invokeSuspend 方法。
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |variable |suspend |inline |suspend function duplication |
| | | | | |inline suspend markers replaced with real ones |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |suspend |call |suspend function duplication |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
当我们将 crossinlineMe
函数内联到另一个内联函数中时会发生什么呢:
kotlin
inline fun crossinlineMe2(crossinline c: suspend () -> Unit): SuspendRunnable =
object : SuspendRunnable {
override suspend fun run() {
crossinlineMe { c() }
}
}
在这种情况下,我们不能简单地丢弃状态机模板(即传递给状态机构建器的函数版本)。因此,如果内联器将函数内联到另一个内联函数中,它会保留模板(当然,在内联之后)。
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |block |suspend |inline |inline suspend markers around invoke |
| | | | | |if inline-site is inline, keep the template |
| | | | | |fake continuation for inliner to transform |
| | | | | |state-machine is built after inlining |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
Let us now make the function itself suspend:
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|suspend |crossinine |block |suspend |inline | |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
kotlin
suspend inline fun crossinlineMe(crossinline c: suspend () -> Unit): SuspendRunnable =
object : SuspendRunnable {
override suspend fun run() {
c()
}
}
代码生成器当然会将其复制,但它不会复制对象。因此,它仅仅是内联挂起和交叉内联挂起的结合。
有一个区别。你看,就像noinline
参数一样,crossinline
参数可以在函数本身中被调用:
kotlin
suspend inline fun inlineMe(crossinline c: suspend () -> Unit) {
c()
}
在这个例子中,尽管inlineMe
的参数被声明为crossinline
,但它是内联的。顺便说一句,这就是suspendCoroutineUninterceptedOrReturn
和suspendCoroutine
的参数的行为。
由于参数是内联的,行为与没有crossinline
关键字时相同,但没有多余的挂起修饰符警告。
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|suspend |crossinine |block |suspend |inline |inline suspend markers around invoke |
| | | | | |if inline-site is inline, keep the template |
| | | | | |fake continuation for inliner to transform |
| | | | | |state-machine is built after inlining |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
为了简化挂起函数转换的流程,因为它已经够复杂的了,我们不支持以不同方式处理普通的crossinline
参数。如果它的调用点是挂起的,我们会对其进行复制,然后再转换对象。
text
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|function|parameter kind|argument kind|parameter type|call kind|notes |
+========+==============+=============+==============+=========+===============================================+
|ordinary|crossinine |variable |ordinary |inline |if lambda call-site is suspend, duplicate it |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |block |ordinary |inline |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |ordinary |inline |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |ordinary |call |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
Summary
以下是所有可能组合的完整列表,不包括无效的组合。现在可以看到为什么协程内联是一个棘手的问题。
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 |state-machine with parameter invoke in a state |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|noinline |variable |ordinary |inline |no suspend call allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|noinline |variable |ordinary |call |no suspend call allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|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 |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |block |ordinary |inline |suspend calls in block allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |block |suspend |inline |FORBIDDEN |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |variable |ordinary |inline |no suspend calls allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |variable |ordinary |call |no suspend calls allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |variable |suspend |inline |FORBIDDEN |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|inline |variable |suspend |call |FORBIDDEN |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |block |ordinary |inline |no suspend call allowed |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |block |suspend |inline |inline suspend markers around invoke |
| | | | | |if inline-site is inline, keep the template |
| | | | | |fake continuation for inliner to transform |
| | | | | |state-machine is built after inlining |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |ordinary |inline |if lambda call-site is suspend, duplicate it |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |ordinary |call |if lambda call-site is suspend, duplicate it |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |suspend |inline |suspend function duplication |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|ordinary|crossinine |variable |suspend |call |suspend function duplication |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|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 |no state-machine with markers |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |block |suspend |inline |if called in a function, markers around invoke |
| | | | | |if called in inner function, state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |variable |ordinary |inline |no state-machine with markers |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |variable |ordinary |call |state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |variable |suspend |inline |if called in a function, markers around invoke |
| | | | | |if called in inner function, state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |noinline |variable |suspend |call |state-machine with parameter's invoke in state |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |inline |block |ordinary |inline |suspend calls in block allowed, |
| | | | | |markers without state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |inline |block |suspend |inline |WARNING to remove `suspend` keyword |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|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 |variable |suspend |inline |WARNING should be ignored, |
| | | | | |no state-machine with markers |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |inline |variable |suspend |call |WARNING should be ignored, state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |block |ordinary |inline |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |block |suspend |inline |inline suspend markers around invoke |
| | | | | |if inline-site is inline, keep the template |
| | | | | |fake continuation for inliner to transform |
| | | | | |state-machine is built after inlining |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |ordinary |inline |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |ordinary |call |if lambda call-site is suspend, duplicate it |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |suspend |inline |suspend function duplication |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |crossinine |variable |suspend |call |suspend function duplication |
| | | | | |function is duplicated |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |no lambda |variable |ordinary |inline |no state-machine with markers |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
|suspend |no lambda |variable |ordinary |call |state-machine |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+