2-1.【Concurrency】Swift 的 async/await 在编译器内部是如何转换为状态机的?

3 阅读2分钟

一句话总览

Swift 的 async 函数在编译期会被转换成一个「显式状态机」

  • 函数体被拆成多个 suspend point(await)之间的段
  • 局部变量被提升到 异步上下文(async context / frame)
  • 控制流通过一个 state + switch 来恢复执行
  • await 本质是:保存状态 → 挂起 → 未来某个时刻从状态机恢复

1️⃣ 从源码开始看

func foo() async -> Int {
    let x = await bar()
    let y = await baz(x)
    return y + 1
}

编译器看到的关键信息:

  • fooasync

  • 2 个 suspension point

    • await bar()
    • await baz(x)

👉 每一个 await 都是状态机的一个“切点”


2️⃣ 编译器的 lowering 阶段(AST → SIL)

Swift 的 async/await 主要在 SILGen(SIL Generation) 阶段被转换。

核心步骤是:

① 拆分函数为多个“continuation block”

逻辑上等价于:

state 0: 开始
state 1: await bar() 返回后
state 2: await baz(x) 返回后

② 局部变量提升(heap / context storage)

在同步函数中:

let x = ...

可以在栈上。

但在 await 之后还能用的变量:

let x = await bar()
let y = await baz(x)

⚠️ x 跨 suspension point 存活

➡️ 编译器会把它提升到 async context(也叫 frame) 中:

AsyncContext {
    state: Int
    x: Int
    y: Int
}

3️⃣ 状态机的“伪代码形态”

编译器最终生成的结构,概念上接近这样:

func foo_async(context: inout AsyncContext) {
    switch context.state {
    case 0:
        context.state = 1
        await bar { result in
            context.x = result
            resume foo_async(context)
        }

    case 1:
        context.state = 2
        await baz(context.x) { result in
            context.y = result
            resume foo_async(context)
        }

    case 2:
        return context.y + 1
    }
}

🔑 关键点:

  • state 决定从哪一段继续执行

  • 每个 await

    1. 保存当前 state
    2. 发起异步调用
    3. 注册 continuation
    4. 返回(函数“挂起”)
  • 恢复时再次进入同一个函数,但 state 已经改变


4️⃣ Swift 不用“真正的 switch”,而是用 continuation

Swift 实际上比上面更精巧:

真实机制是:

  • async 函数 = 隐式 continuation + resume function

  • await 会调用:

    • swift_task_suspend
    • 保存 resume point
  • 恢复时:

    • runtime 调用 resume function
    • 从正确的 SIL block 继续执行

你可以把它理解成:

状态机 + 函数指针 = continuation


5️⃣ 和 C++ / Kotlin / JS 的区别

Swift 的特点

语言状态机位置
Swift编译期(SIL)
Kotlin编译期(Continuation 参数)
C++20 coroutine编译期(promise / frame)
JavaScript编译期 + VM 支持

Swift 的 async:

  • 零反射
  • 无隐式线程切换
  • 完全静态分析
  • 性能接近手写状态机

6️⃣ 关键编译器组件(想看源码)

如果你要继续深挖源码,这些是入口:

  • SILGenFunction::emitAsyncFunction
  • AsyncLowering
  • SuspendContinuation
  • swift_task_switch
  • swift_task_suspend

路径大致在:

lib/SILGen/
lib/SIL/
lib/IRGen/
stdlib/public/Concurrency/

7️⃣ 一个容易忽略但很重要的点 ⚠️

async 并不等于线程切换

  • await ≠ yield thread

  • 状态机恢复在哪个 executor 上,由:

    • actor
    • @MainActor
    • task priority
    • runtime 决定

8️⃣ 总结一句话(拿去面试用)

Swift 的 async/await 在编译期被 lowering 成一个显式状态机:
函数被拆成多个 suspension block,跨 await 的局部变量被提升到 async context,
每个 await 保存当前状态并注册 continuation,恢复时通过 resume 函数从正确的状态继续执行。