在 Swift 的编译流水线中,这些操作跨越了从 AST(抽象语法树) 到 SIL(中间语言) 再到 IR(底层表示) 的多个阶段。它们并不是并列发生的,而是一个不断“降级”和“具体化”的过程。
以下是它们发生的顺序及逻辑关系:
1. 类型检查 (Type Checking)
-
发生阶段: 语义分析 (Semantic Analysis / AST 阶段)
-
核心逻辑: 这是编译器最早执行的关键任务。
- 编译器扫描 AST 节点,利用 约束求解器 (Constraint Solver) 推导出变量类型。
- 验证协议的一致性(即:类 A 是否真的实现了协议 P 要求的所有方法)。
-
结果: 产生一个类型化 AST (Typed AST) 。如果没有通过这一步,编译器会抛出大家熟悉的“Type mismatch”或“Does not conform to protocol”错误并直接终止。
2. 协议 Witness Table 的生成 (Protocol Witness Table Generation)
-
发生阶段: SIL 生成与 IR 降级阶段 (SIL / LLVM IR 阶段)
-
核心逻辑: * 一旦类型检查确认某个类符合协议,编译器就需要为这种关系创建一套“映射表”。
- Witness Table 本质上是一个虚函数表,它将协议定义的方法地址映射到具体实现类的函数地址上。
- 这通常在生成 SIL 过程中开始构建逻辑,并在 LLVM IR 阶段物理生成内存中的表结构。
3. 泛型特化 / 单例化 (Generic Specialization)
-
发生阶段: SIL 优化阶段 (SIL Optimization Pass)
-
核心逻辑: * Swift 默认使用“多态(Polymorphism)”来处理泛型(通过传递 Witness Table),但这有运行时开销。
- 为了提速,编译器会观察代码中如何使用泛型。例如,如果你调用了
Array<Int>,编译器会专门为Int版本生成一份去除了泛型包装的直接代码。 - 注意顺序: 它必须发生在类型检查之后(因为需要知道具体的类型),并且通常在 Witness Table 的逻辑确立之后进行优化。
- 为了提速,编译器会观察代码中如何使用泛型。例如,如果你调用了
总结:完整的执行时序
| 顺序 | 阶段 | 操作名称 | 产物/目的 |
|---|---|---|---|
| 1 | AST | 类型检查 | 确定所有符号的类型,确保代码逻辑合法。 |
| 2 | SIL | 建立 Witness 逻辑 | 建立协议与实现之间的动态调用链路。 |
| 3 | SIL Opt | 泛型特化 | (优化项) 将泛型代码展开为具体类型代码以提升性能。 |
| 4 | IR | 生成 Witness Table | 在二进制文件中生成真实的静态表数据。 |
关键点拨:
- 为什么类型检查最先? 因为它是所有后续工作的前提。不知道类型,就无法确定该找哪个 Witness Table,更无法进行泛型特化。
- 为什么特化在 SIL 阶段? 因为 SIL 保留了足够的 Swift 语义信息(比如知道这是个泛型函数),而到了 LLVM IR 阶段,一切都变成了底层的指针和字节,再做特化就太难了。