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
clang.png

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
swift.png


2. V-Table

常见的编译型语言的动态派发方式,编译器层使用一个表格结构来存储类型声明中的每一个函数指针。C++中称之为虚函数表VTable,也是其支持多态的基础。

在Swift中,拥有继承关系的Class采用此种方式,即每一个类会维护一个函数表,该表会记录该类中所有的函数指针:

  1. 由父类继承而来的方法执行地址。
  2. 如果子类重写父类方法,表中会保存被重载之后的函数。
  3. 子类新增的函数会加入到表的末尾。

然后在程序运行期间查表执行具体实现。

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_ 
}


7. Swift’s use of SIL 流程

Two Phases of SIL Passes.png

你可能感兴趣的:(Swift’s use of SIL)