协程代码生成-3.Inline(译)

Inline

将suspend函数内联化是一件棘手的事情。

在我们深入讨论之前,让我们解释一下,携带参数的内联函数存在64种可能的组合:

  1. 函数可以是suspend函数或普通函数
  2. 参数可以是 noinline、inline 或 crossinline lambda,也可以没有 lambda
  3. 参数可以是我们内联的块,也可以是我们无法内联的变量
  4. 参数可以是可挂起的或普通的
  5. 我们可以内联函数,也可以通过反射调用它

当然,其中一些是不正确的,因为在 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 中的 blockvariable 是什么意思。我们可以通过提供一个块或传递 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构造函数调用前后的标记,它们的值分别为 45

然后,我们替换对象的使用:

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 放入单独的状态中;换句话说,我们应该移除围绕内联代码的挂起标记。为此,我们引入了新的挂起标记:内联挂起调用前后的标记。它们的值分别为 67。如果 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,但它是内联的。顺便说一句,这就是suspendCoroutineUninterceptedOrReturnsuspendCoroutine的参数的行为。

由于参数是内联的,行为与没有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                                  |
+--------+--------------+-------------+--------------+---------+-----------------------------------------------+
相关推荐
一丝晨光1 小时前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
500了10 小时前
Kotlin基本知识
android·开发语言·kotlin
陈亦康1 天前
Armeria gPRC 高级特性 - 装饰器、无框架请求、阻塞处理器、Nacos集成、负载均衡、rpc异常处理、文档服务......
kotlin·grpc·armeria
奋斗的小鹰2 天前
kotlin 委托
android·开发语言·kotlin
Wency(王斯-CUEB)2 天前
【文献阅读】政府数字治理的改善是否促进了自然资源管理?基于智慧城市试点的准自然实验
android·kotlin·智慧城市
中游鱼2 天前
Visual Studio C# 编写加密火星坐标转换
kotlin·c#·visual studio
500了2 天前
kotlin协程
开发语言·python·kotlin
ChinaDragonDreamer4 天前
Kotlin:1.8.0 的新特性
android·开发语言·kotlin
IH_LZH4 天前
Kotlin:变量声明,null安全,条件语句,函数,类与对象
android·开发语言·kotlin
Forbesdytto5 天前
kotlin中的对象表达式与java中的匿名内部类
java·kotlin·移动端