Swift編譯器中間碼SIL

文章同步發在我的博客上,地址Swift編譯器中間碼SILgit

爲何要設計SIL

clang.png

上圖是傳統的基於LLVM的編譯器流程,好比C、C++以及Objective-C。代碼分析主要是基於CFG(AST級別),CFG全稱Control Flow Graph(函數流程控制圖),是在clang這一層,可是這有不少缺點。github

缺點:golang

  • 源碼和LLVM IR之間的巨大抽象差距不適用於源碼級分析
  • CFG缺少保真度
  • CFG不是在hot path上,hot path是指被屢次迭代的代碼塊,圖上能夠看到不是在編譯器流程的主路徑上
  • CFG和IR下降中有不少的重複工做

Swift做爲一種高級語言,有些高級特性,好比基於protocol的泛型。並且也是一門安全的語言,確保變量在使用以前被初始化、檢測不可執行的代碼(unreachable code)。因而爲Swift編譯器增長了一層SIL來作這些事情。算法

SIL編程

  • 可以徹底保留程序的語義
  • 專爲代碼生成和分析而設計
  • 在編譯器流程的hot path上
  • 彌補源碼和LLVM之間的巨大抽象

SIL簡介

swiftc_pipeline.png

Swift編譯器在AST和LLVM IR之間有一箇中間表示形式,稱爲SIL。經過使用訪問者(Visitor)模式掃描AST來生成SIL。SIL會對Swift進行高級別的語意分析和優化。像LLVM IR同樣,也具備諸如Module,Function和BasicBlock之類的結構。與LLVM IR不一樣,它具備更豐富的類型系統,有關循環和錯誤處理的信息仍然保留,而且虛函數表和類型信息以結構化形式保留。它旨在保留Swift的含義,以實現強大的錯誤檢測,內存管理和高級優化。swift

同時像LLVM IR同樣,SIL是靜態單賦值Static-Single-Assignment (SSA),所以永遠都不能從新定義值。當一條指令引用一個值時,該值要麼是當前基本塊的輸入參數,要麼由該塊中的單個惟一指令定義。注意,與「真正的」編程語言不一樣,SIL是「扁平的」,由於在語法上沒有嵌套的結構。每條指令引用其餘指令產生的值,並對它們執行一個邏輯運算以產生新值。後端

相關代碼:
/lib/SILGen
/lib/SIL安全

SSA

SSA 表明 static single-assignment,是一種IR(中間表示代碼),要保證每一個變量只被賦值一次。這個能幫助簡化編譯器的優化算法。bash

x = 0;
x = 1;
y = 2 * x;

複製代碼

好比上面這段代碼,y = 1實際上是不可用的,這個要經過定義的可達分析來肯定y是要用1仍是2,而SSA有一個標識符能夠稱之爲版本或者"代"。app

x1 = 0;
x2 = 1;
y = 2 * x2;
複製代碼

這樣就沒有任何間接值了。用SSA表示的好處是對於同一個變量的無關使用表示成不一樣"代",能夠方便不少編譯器的優化算法的實現。

歸納起來,SSA帶來四大益處:

  • 由於SSA使得每一個變量都有惟一的定義,所以數據流分析和優化算法能夠更加簡單。
  • 使用-定義關係鏈所消耗空間從指數增加下降爲線性增加。若一個變量有N個使用和M個定義,若不採用SSA,則存在M×N個使用-定義關係。
  • SSA中由於使用和定義的關係更加的精確,能簡化構建干擾圖的算法。
  • 源程序中對同一個變量的不相關的若干次使用,在SSA形式中會轉變成對不一樣變量的使用,所以能消除不少沒必要要的依賴關係。

有了精確的對象使用–定義關係,許多利用使用–定義關係的優化就能更精確、更完全、更高效。如

  • 常數傳播
  • 死代碼刪除
  • 全局
  • 部分冗餘刪除
  • 強度削弱
  • 寄存器分配

關於SSA這裏就很少講了,還沒深刻研究過,以後可能會單獨寫篇文章講一講,另外推薦一本書Static Single Assignment Book

SIL語言特色

  • 單行指令,即一行表示一條指令
  • 強類型
  • 方法分解成連續的building-blocks(也就是BB)
  • 包含ARC指令
  • 專爲代碼分發而設計
  • SIL寄存器數目是無限的,從%0,%1,%2這樣遞增

SIL結構

SIL程序是命名函數的集合,每一個函數由一個或多個基本塊組成。基本塊是線性的指令序列,每一個塊中的最後一條指令將控制權轉移到另外一個基本塊,或從函數返回。

Module

整個SIL源文件,中文叫模塊,由SILFunction和SILGlobalVariable組成。能夠經過begin() 和 end() 得到迭代器,經過迭代器能夠快速遍歷該模塊中全部的函數。

using iterator = FunctionListType::iterator;
iterator begin() { return functions.begin(); }
iterator end() { return functions.end(); }
複製代碼

完整接口可經過下面地址查看:

include/swift/SIL/SILModule.h

Function

包含函數定義和聲明有關的全部對象。每一個SILFunction由SILBasicBlock和SILArgument組成,能夠當作是Swift函數的直接轉換。經過isDefinition()可檢查它是不是在本模塊中聲明的。這兩種狀況下都包含參數列表,可經過getArgument()來得到參數列表。

SILArgument *getArgument(unsigned i) 
  
ArrayRef<SILArgument *> getArguments() const
複製代碼

完整接口可經過下面地址查看: include/swift/SIL/SILFunction.h

Basic Block

SILBasicBlock由SILInstruction組成,是線性的指令序列,每一個SILBasicBlock中的最後一條指令將控制權轉移到另外一個SILBasicBlock,或從函數返回。可經過begin() / end()訪問SILInstruction。也可使用getTerminator()方法直接訪問最後一條指令。

TermInst *getTerminator()
複製代碼

完整接口可經過下面地址查看: include/swift/SIL/SILBasicBlock.h

Instruction

SIL中的基本單元,實際操做值或調用函數的指令。

SIL Structure.png

SILValue和SILType

SILValue

SILValue定義了use_begin() 和 use_end()方法用於遍歷User,或者經過getUses() 來得到全部User的範圍,這對於迭代全部User頗有用,若是要忽略調試信息指令,能夠改用getNonDebugUses,經過這些方法能夠簡單訪問它的def-use鏈。

inline use_range getUses() const;
複製代碼

SILType

每一個SIL值都有一個SIL類型,能夠在這裏查看源碼SILType.h。有SIL類型能夠分爲兩大種,object(對象) 和 addresses(地址)類型。這裏的對象和傳統的面向對象編程的對象不一樣,對象類型包括整型,一個類的實例對象,結構值或函數。地址是存儲指向對象類型的指針的值。能夠經過isAddress() 和 isObject() 來判斷類型。

/// True if the type is an address type.
bool isAddress() const { return getCategory() == SILValueCategory::Address; }

/// True if the type is an object type.
bool isObject() const { return getCategory() == SILValueCategory::Object; }
複製代碼

Metatype Types
SILType有不少種,元數據類型是其中一種,SIL中一個具體的元數據類型必須描述其表現形式

  • @thin,表明一個確切的具體類型,不須要存儲。
  • @thick,描述元類型表明的形式,是引用一個對象類型或是其子類
  • @objc,表明使用Objective-C類對象表示,而不是純Swift類型對象表示

type lowering
type lowering類型降級,咱們日常在寫swift中用到的系統提供的類型稱爲formal type,也就是正式類型。Swift的形式類型系統有意抽象了許多表明性的問題,例如全部權轉移約定和參數的直接性。而SIL旨在表明大多數此類實現細節,這些差別應在SIL類型系統中獲得體現,所以SIL type要豐富的多。從formal type到SIL type的轉換的操做稱爲類型降級,SIL type又稱爲「lowered types」,下降的類型。

因爲SIL是一種中間語言,SIL值大體對應於抽象機的無限寄存器。address-only(純地址)類型本質上是那些「太複雜」而沒法存儲在寄存器中的類型。 非address-only(純地址)類型稱爲loadable(可加載)類型,這意味着它們能夠被加載到寄存器中。

將一個地址類型指向一個非address-only(純地址)類型是合法的,可是對象類型包含address-only(純地址)類型是不合法的。

能夠在/lib/SIL/IR/TypeLowering.cpp查看實現細節,主要方法是getLoweredType(),從正式類型返回SIL類型。

Builtin

由於下面例子中出現Builtin,因此這裏稍微解釋一下,採起Swift's mysterious Builtin module文中一段話:

在Swift中,Int實際上一個struct,而+是一個針對Int重載的全局方法(global function)。嚴格說來,Int和+不是Swift語言的一部分,它們是Swift標準庫的一部分。既然不是原生態,是否是就意味着操做Int或+的時候會有額外的負擔,致使Swift跑得慢?固然不是,由於咱們有Builtin。

Builtin將LLVM IR的類型和方法直接暴露給Swift標準庫,因此咱們在操做Int和+的時候,沒有額外的運行時負擔。

以Int爲例,Int在標準庫中是一個struct,定義了一個屬性value,類型是Builtin.Int64。咱們能夠用unsafeBitCast將value屬性在Int和Builtin.Int64之間相互轉換。Int還重載了init方法,使得咱們能夠從Builtin.Int64直接構造一個Int。這些都是高效的操做,不會致使性能損失。

raw SIL 與 canonical SIL

developing-siloptimizer-pass.png

SIL有兩種形式,raw SIL(原始SIL) 和 canonical SIL(規範SIL),剛剛從SILGen中出來的未經優化的SIL稱爲raw SIL。

能夠經過swiftc-emit-silgen將Swift源碼轉變爲raw SIL。

swiftc -emit-silgen Source.swift -o Source.sil
複製代碼

通過SIL Optimizer優化以後產生的優化SIL,稱爲規範SIL。能夠經過swiftc-emit-sil將raw SIL轉變爲canonical SIL。

swiftc Source.sil -emit-sil  > Source-canonical.sil
複製代碼

也能夠直接將Swift源碼轉變爲canonical SIL。

swiftc Source.swift -emit-sil  > Source-canonical.sil
複製代碼

例子講解

來看一個最簡單的例子

func test(number: Int) -> Bool {
    if number > 0 {
        return true
    } else {
        return false
    }
}

複製代碼

使用一下swiftc命令轉換成原始SIL,代碼以下:

swiftc -emit-silgen Source.swift -o Source.sil

複製代碼
sil_stage raw

import Builtin
import Swift
import SwiftShims

func test(number: Int) -> Bool

// main
sil [ossa] @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// test(number:)
sil hidden [ossa] @$s6Source4test6numberSbSi_tF : $@convention(thin) (Int) -> Bool {
// %0                                             // users: %8, %1
bb0(%0 : $Int):
  debug_value %0 : $Int, let, name "number", argno 1 // id: %1
  %2 = metatype $@thin Int.Type                   // user: %8
  %3 = integer_literal $Builtin.IntLiteral, 0     // user: %6
  %4 = metatype $@thin Int.Type                   // user: %6
  // function_ref Int.init(_builtinIntegerLiteral:)
  %5 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6
  %6 = apply %5(%3, %4) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %8
  // function_ref static Int.> infix(_:_:)
  %7 = function_ref @$sSi1goiySbSi_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %8
  %8 = apply %7(%0, %6, %2) : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %9
  %9 = struct_extract %8 : $Bool, #Bool._value // user: %10
  cond_br %9, bb1, bb2                            // id: %10

bb1:                                              // Preds: bb0
  %11 = integer_literal $Builtin.Int1, -1         // user: %14
  %12 = metatype $@thin Bool.Type                 // user: %14
  // function_ref Bool.init(_builtinBooleanLiteral:)
  %13 = function_ref @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool // user: %14
  %14 = apply %13(%11, %12) : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool // user: %15
  br bb3(%14 : $Bool)                             // id: %15

bb2:                                              // Preds: bb0
  %16 = integer_literal $Builtin.Int1, 0          // user: %19
  %17 = metatype $@thin Bool.Type                 // user: %19
  // function_ref Bool.init(_builtinBooleanLiteral:)
  %18 = function_ref @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool // user: %19
  %19 = apply %18(%16, %17) : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool // user: %20
  br bb3(%19 : $Bool)                             // id: %20

// %21                                            // user: %22
bb3(%21 : $Bool):                                 // Preds: bb2 bb1
  return %21 : $Bool                              // id: %22
} // end sil function '$s6Source4test6numberSbSi_tF'

// Int.init(_builtinIntegerLiteral:)
sil [transparent] [serialized] @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int

// static Int.> infix(_:_:)
sil [transparent] [serialized] @$sSi1goiySbSi_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Bool

// Bool.init(_builtinBooleanLiteral:)
sil [transparent] [serialized] @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool

複製代碼

第一個是main函數,這裏就不講了。從test(number:)看起

sil hidden [ossa] @$s6Source4test6numberSbSi_tF : $@convention(thin) (Int) -> Bool {
   .....
}
複製代碼
  • 這是一個SILFunction,裏面包含三個SILBasicBlock,分別是bb0、bb一、bb2和bb3。
  • 方法名s6Source4test6numberSbSi_tF,是test(number:)通過命令重整以後的。SIL中的標識符名稱以@符號開頭。
  • convention(thin) (Int) -> Bool,convention(thin) 表示是Swift的函數調用,參數爲Int類型,返回值爲Bool類型。
bb0(%0 : $Int):
  debug_value %0 : $Int, let, name "number", argno 1 // id: %1
  %2 = metatype $@thin Int.Type                   // user: %8
  %3 = integer_literal $Builtin.IntLiteral, 0     // user: %6
  %4 = metatype $@thin Int.Type                   // user: %6
  // function_ref Int.init(_builtinIntegerLiteral:)
  %5 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6
  %6 = apply %5(%3, %4) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %8
  // function_ref static Int.> infix(_:_:)
  %7 = function_ref @$sSi1goiySbSi_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %8
  %8 = apply %7(%0, %6, %2) : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %9
  %9 = struct_extract %8 : $Bool, #Bool._value // user: %10
  cond_br %9, bb1, bb2 
複製代碼

這個簡單的方法被分紅4個代碼塊,bb0對應的是執行number > 0語句,bb1是對應if代碼塊,bb2對應else的代碼塊,bb3是return Bool代碼塊,接收一個Bool類型返回一個Bool類型。咱們這裏只分析bb0,其餘的就不分析了。

  • 這是一個SILBasicBlock。
  • bb0(%0 : $Int):%符號表示一個寄存器,這裏第一個參數是Int類型,存放在%0。
  • debug_value %0 : $Int, let, name "number", argno 1 // id: %1:// id: %1是註釋,說明分配的寄存器爲%1。
  • %2 = metatype $@thin Int.Type // user: %8:建立一個Int類型的的元類型對象,@thin表示該元類型不須要存儲,由於它是精確類型。使用者是%8。
  • %3 = integer_literal $Builtin.IntLiteral, 0 // user: %6:建立一個整數文字值,類型Builtin.IntLiteral,該類型必須爲內置整數類型。文字值是使用Swift的整數文字語法指定的,值爲0,使用者%8。
  • %4 = metatype $@thin Int.Type // user: %6,建立一個Int類型的的元類型對象,@thin表示該元類型不須要存儲,由於它是精確類型。使用者是%6。
  • %5 = function_ref @sSi22\_builtinIntegerLiteralSiBI_tcfC :@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6:function_ref是建立函數的引用,參數類型是Builtin.IntLiteral和 @thin Int.Type,返回值是Int,這個函數其實是把整數文字值0轉變成Int類型值0。
  • %6 = apply %5(%3, %4) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %8:apply 調用函數,函數是 %5,傳入參數是寄存器%3, %4),執行完返回結果存儲在寄存器%8。
  • %7 = function_ref @sSi1goiySbSi_SitFZ :@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %8:建立函數的引用,參數類型分別是Int, Int, @thin Int.Type,使用者是%8。
  • %8 = apply %7(%0, %6, %2) : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %9:apply 調用函數爲%7,傳入參數是寄存器%0, %6, %2,執行完返回結果存儲在寄存器%9 ;
  • cond_br %9, bb1, bb2:cond_br是SILBasicBlock終止指令Terminators的一種,條件分支指令,若是%9是true,跳轉到bb1,false跳轉到bb2。其實就是if number > 0 的條件跳轉。

SIL潛在用途

  • Swift熱更新,Rollout就是經過在 SIL層給每一個方法添加一個前綴來操做應用的
func add(a:Int, b:Int) -> Int {
if Rollout_shouldPatch(ROLLOUT_a79ee6d5a41da8daaa2fef82124dcf74) {
    let resultRollout : Int =
    Rollout_invokeReturn(Rollout_tweakData!,
        target:self,
        arguments:[a,
            b,
            origClosure: { args in return self.add(a:args[0],b:args[1]);});
    return resultRollout;
複製代碼

在上面的代碼中,Rollout_invokeReturn 負責執行一個從 Rollout 雲下載的 JavaScript 函數。若是須要,該函數能夠回調初始的方法。

  • Mutation tests,突變測試,又叫作「變異分析」,一般是改變代碼中某個地方,測試程序是否能走到錯誤的代碼邏輯。其實這個經過編譯後端LLVM的JIT也能夠,只是難度很大。

Swift Intermediate Language (SIL)
2015 LLVM Developers’ Meeting: Joseph Groff & Chris Lattner 「Swift's High-Level IR: A Case Study..."
benng.me/2017/08/27/…
How to talk to your kids about SIL type use
Cocoaheads KRK #29 Swift Intermediate Language - Bartosz Polaczyk
什麼是SSA以及SSA的做用
Swift's mysterious Builtin module
The secret life of types in Swift

相關文章
相關標籤/搜索