Swift’s use of SIL
Swift 是一门静态语言,在 Swift 中声明的方法和属性静态编译期就确定了的,并且Swift具有更灵活的高级特性,协议,泛型,方法重载,值引用等,所以其与OC运行时动态消息派发不同,需要支持静态派发以及动态派发,目前的这些特性Clang并不能完全支持。
因此苹果另外实现了一套 Swift() 作为Swift的编译前端,由于其语言特性,反而有更多的优化空间。
其使用SIL作为前端中间表示层,与IR差异很大,大致体现在:
- 在编译期完全确定的调用关系会采用静态派发,即在可执行文件中直接指定方法实现的内存地址的指针,在运行时直接通过指针调用
- 在编译期间无法确定最终合适的操作时,则会执行动态派发。Swift又不完全同于C++的方式,其除了支持继承使用的虚函数表vtable,还加入了支持面向协议编程的witness table
- 通过dynamic关键字对方法标记,使用OC的运行时机制
- 由于支持函数重载,也就引出了name mangling(名字修饰)机制,其目的是给同名的重载函数不同的签名
- ...
1. Clang vs Swift():
Clang
- Wide abstraction gap between source and LLVM IR
- IR isn't suitable for source-level analysis
- CFG lacks fidelity
- CFG is off the hot path
- Duplicated effort in CFG and IR lowering
Swift
- Higher-level language
- Move more of the language into code
- Protocol-based generics
- Safe language
- Uninitialized vars, unreachable code should be compiler errors
- Bounds and overflow checks
2. V-Table
常见的编译型语言的动态派发方式,编译器层使用一个表格结构来存储类型声明中的每一个函数指针。C++中称之为虚函数表VTable,也是其支持多态的基础。
在Swift中,拥有继承关系的Class采用此种方式,即每一个类会维护一个函数表,该表会记录该类中所有的函数指针:
- 由父类继承而来的方法执行地址。
- 如果子类重写父类方法,表中会保存被重载之后的函数。
- 子类新增的函数会加入到表的末尾。
然后在程序运行期间查表执行具体实现。
3. Protocol Witness Table
因为协议不一定具有继承关系,所以其创建了Protocol Witness Table。大致实现为:Swift会为每一个实现了该协议的对象生成一个大小一致的结构体,这个结构体被称为Existenial Container
,它内部包含PWT,表中存储着一组函数的执行地址,而表中的每一个条目指向了符合该协议的类型信息。该结构体中还保留了三个字长的valueBuffer用来存储数据成员。
4. SIL
SIL是一种SSA形式的IR,具有高级语义信息,旨在实现Swift编程语言。 SIL适用于以下用例:
- 一组有保证的高级优化,可为运行时和诊断行为提供可预测的基准。
- 诊断数据流分析过程可满足Swift语言要求,例如变量和构造函数的确定性初始化,代码可访问性,开关覆盖率。
- 高级优化过程,包括保留/释放优化,动态方法去虚拟化,闭包内联,将堆分配提升为堆栈分配,将堆分配提升为SSA寄存器,将标量替换为标量(将标量分配拆分为多个较小的标量)和通用函数实例化。
- 一种稳定的分发格式,可用于分发带有Swift库模块的“易碎”的内联代码或通用代码,并将其优化为客户端二进制文件。
与LLVM IR相比,SIL是一种通常与目标无关的格式表示形式,可用于代码分发,但它也可以像LLVM一样表示特定于目标的概念。
- 完全代表程序语义
- 专为代码生成和分析而设计
- 位于编译器管道的热路径上
- 修复了源代码和LLVM之间的抽象鸿沟
在较高的层次上,Swift编译器遵循严格的管道架构:
- Parse模块从Swift源代码构造AST。
- Sema模块对AST进行类型检查,并使用类型信息对其进行注释。
- SILGen模块从AST生成raw SIL。
- 在原始SIL上运行一系列“保证的优化Pass”和“诊断Pass”,以执行优化并发出特定于语言的诊断信息。即使在-Onone上,它们也始终运行,并生成规范SIL。
- 常规SIL 优化Pass(可选)在规范的SIL上运行,以提高生成的可执行文件的性能。这些是由优化级别启用和控制的,而不是在-Onone上运行。
- IRGen将规范SIL降低为LLVM IR。
- LLVM后端(可选)使用LLVM优化,运行LLVM代码生成器并产生二进制代码。
5. SILGen
SILGen通过遍历经过类型检查的Swift AST来生成“原始SIL”。 SILGen发出的SIL形式具有以下特性:
- 变量通过加载和存储可变存储器位置来表示,而不是采用严格的SSA形式。 这类似于前端(如Clang)发出的初始“alloca”大量的LLVM IR。 但是,在最普通的情况下,Swift将变量表示为引用计数的“boxes”,可以将其保留,释放并捕获到闭包中。
- 尚未执行数据流要求,例如最终分配,函数返回,开关覆盖率(TBD)等。
-
transparent
功能优化尚未实现。
这些属性通过后续保证的优化和诊断遍历得到解决,这些遍历始终针对原始SIL运行。
6. General Optimization Passes
SIL捕获特定语言的类型信息,从而可以实现在LLVM IR上难以执行的高级优化。
- Generic Specialization 分析对泛型函数的专门调用,并生成函数的新的指定版本。然后,它将泛型的所有指定用法重写为对指定功能的直接调用。
- 给定类型的 Witness and VTable Devirtualization 从类的vtable或类型见证表中查找关联的方法,并将间接虚拟调用替换为对映射函数的调用。
- Performance Inlining
- Reference Counting Optimizations
- Memory Promotion/Optimizations
- High-level domain specific optimizations Swift编译器在基础的Swift容器(例如Array或String)上实现了高级优化。特定于域的优化需要在标准库和优化器之间定义接口。更多细节可以查看:HighLevelSILOptimizations
如下代码为swift实现代码在SIL下的展现形式:
sil_stage canonical
import Swift
//定义SIL函数使用的类型。
struct Point {
var x:Double
var y:Double
}
class Button {
func onClick()
func onMouseDown()
func onMouseUp()
}
//声明一个Swift函数。主体被SIL忽略。
func taxicabNorm(_ a:Point)-> Double {
return a.x + a.y
}
//定义SIL函数。
//名称@_T5norms11taxicabNormfT1aV5norms5Point_Sd是 taxicabNorm Swift函数的错误名称。
sil @ _T5norms11taxicabNormfT1aV5norms5Point_Sd:$(Point)-> Double {
bb0(%0:$ Point):
// func Swift。+(Double,Double)-> Double
%1 = function_ref @ _Tsoi1pfTSdSd_Sd
%2 = struct_extract%0:$ Point,#Point.x
%3 = struct_extract%0:$ Point,#Point.y
%4 = apply%1(%2,%3):$(Double,Double)-> Double
return%4:Double
}
//定义SIL vtable。匹配动态分派的方法
//标识与其已知静态类类型的实现相匹配。
sil_vtable Button {
#Button.onClick!1:@ _TC5norms6Button7onClickfS0_FT_T_
#Button.onMouseDown!1:@ _TC5norms6Button11onMouseDownfS0_FT_T_
#Button.onMouseUp!1:@ _TC5norms6Button9onMouseUpfS0_FT_T_
}