Swift的高級中間語言:SIL

簡介

在LLVM的官方文檔中對Swift的編譯器設計描述如下: Swift編程語言是在LLVM上構建,并且使用LLVM IR和LLVM的后端去生成代碼。但是Swift編譯器還包含新的高級別的中間語言,稱為SILSIL會對Swift進行較高級別的語義分析和優化。 我們下面分析一下SIL設計的動機和SIL的應用,包括高級別的語義分析,診斷轉換,去虛擬化,特化,引用計數優化,TBAA(Type Based Alias Analysis)等。并且會在某些流程中加入對SIL和LLVM IR對比。

SIL介紹

SIL是為了實現swift編程語言而設計的,包含高級語義信息的SSA格式的中間語言.SIL包含以下功能:

  • 一系列的高級別優化保障,用于對運行時和診斷行為提供可預測的底線

  • 對swift語言數據流分析強制要求,對不滿足強制要求的問題產生診斷。例如變量和結構體必須明確初始化,代碼可達性即方法return的檢測,switch的覆蓋率

  • 確保高級別優化。包含retain/release優化,動態方法的去虛擬化(devirtualization,不了解虛函數可以查看之前文章static vs dynamic dispatch),閉包內聯,內存初始化提升和泛型方法實例 化.

  • 可用于分配"脆弱"內聯的穩定分配格式,將Swift庫組件的泛型優化為二進制。

和LLVM IR不同,SIL一般是target無關的獨立格式的表示,可用于代碼分發.但是也可以和LLVM一樣表達具體target概念. 如果想查看更多SIL的實現和SIL通道的開發信息,可以查看SIL開發手冊(原英文文檔為SILProgrammersManual.md)。

我們下面對Clang的Swift編譯器的傳遞流程進行對比:

編譯流程對比

Clang編譯器流程

image

Clang編譯流程存在以下問題:

  • 在源碼和LLVM IR直接存在非常大的抽象鴻溝

  • IR不適用對源碼進行分析和檢查 使用了Analysis通過CFG進行分析,分析和代碼生成是兩部分

  • CFG(控制流圖)不夠精確

  • CFG不是主道(hot path)

  • 在CFG和IR降級中會出現重復分析,做無用功

Swift編譯器流程

Swift作為一個高級別和安全的語言具有以下特點:

高級別語言

  • 通過代碼充分的展示語言的特性

  • 支持基于協議的泛型

安全語言

  • 充分的數據流檢查:未初始化變量,函數返回處理檢測,這些項在檢測不合格時會產生對應的編譯錯誤

  • 邊界和溢出的檢測

Swift編譯流程圖如下:

image

Swift編譯器提供的SIL具有以下優勢:

  • 對程序語義信息重復表示

  • 可以用于代碼生成和分析 Clang不可以

  • 處于編譯器的主道

  • 可以連接源碼和LLVM的抽象鴻溝

SIL的設計

SIL流程分析

Swift編譯器作為高級編譯器,具有以下嚴格的傳遞流程結構。 Swift編譯器的流程如下

  • Parse: 語法分析組件從Swift源碼構成AST

  • 語義分析組件對AST進行類型檢查,并對其進行類型信息注釋。

  • SILGen組件從AST形成"生的(raw)"SIL

  • 一系列在 SIL上運行的,用于確定優化診斷合格,對不合格的代碼嵌入特定的語言診斷。這些操作一定會執行,即使在-Onone選項下也不例外。之后產生 正式(canonical) SIL.

  • 一般情況下,是否在正式SIL上運行SIL優化是可選的,這個檢測可以提升結果可執行文件的性能.可以通過優化級別來控制,在-Onone模式下不會執行.

  • IRGen會將正式SIL降級為LLVM IR.

  • LLVM后端提供LLVM優化,執行LLVM代碼生成器并產生二進制碼.

SIL操作流程分析

SILGen

SILGen遍歷Swift進行了類型檢查的AST,產生 raw SIL.SILGen產生的SIL格式具有如下屬性:

  • 屬性會被加載和存儲在可變內存地址,而不是使用嚴格的SSA(靜態單賦值形式:每個變量僅被賦值一次)。這和Clang前端產生的繁重的LLVM IR(例如初始化alloca)類似。但是Swift的變量在大多數情況下使用了引用計數器,使得變量可以被retained,release和被閉包引用。

  • 數據流檢測。例如明確的內存分配,方法return檢查,switch覆蓋等.此環節目前不是強制執行的

  • transparent函數優化目前未實現.

這些特性會被接下來的確保優化診斷檢查使用,這兩項在 raw SIL上一定會運行。

確保優化和診斷檢查

SILGen之后,會在raw SIL上運行確定順序的優化。我們并不希望編譯器產生的診斷改變編譯器的進展,所以這些優化的設計是簡單和可預測.

  • Mandatory inlining: 強制內聯對于transparent函數進行內聯。

    透明函數即,如果一個函數只會受到入參的變化,那么這個函數每次的調用都會是相同的,同樣的入參一定會返回一樣的返回值,在確定入參的時候,返回值是可預測的。這樣的函數,就可以進行內聯優化。

  • 內存提升實現分為兩個優化階段:

    alloc_box結構優化為alloc_stack

    提升無暴露地址(non_address-exposed)的alloc_stack說明到SSA注冊.

  • 常數傳播: Constant propagation折疊常量表達,繁殖常量值.如果在計算常量表達式時出現算術溢出,就會產生警告.

  • 返回分析查證每個方法在每個代碼路徑只返回一個值,并且不會在定義的末端出現無返回值的錯誤.如果不需要返回值的函數return了也會報錯.

  • 臨界拆分: critical edge splitting不支持任意的基礎block參數通過終端進行臨界拆分. 在 Advanced Compiler Design & Implementation的第13.3章節,第407,408頁這樣描述臨界分裂

這個算法的核心作用體現為:流程圖中的臨界如果在流分析前被拆分的話,會使得運算更近高效. 原文: A key point in the algorithm is that it can be much more effective if the critical edges in the flowgraph have been split before the flow analysis is performed.

如果診斷通道完成后,會產生規范SIL.

  • 泛型特化: Generic specialization

  • -Onone模式下的ARC性能優化.

說完了處理raw SIL的特定流程,我們對上面提到的優化通道: optimization passes進行下說明.

泛型優化

SIL獲取語言特定的類型信息,使得無法在LLVM IR實現的高級優化在swift編譯器中得以實現.

  • 泛型特化分析泛型函數的特定調用,并生成新的特定版本的函數.然后將泛型的特定用法全部重寫為對應的特定函數的指甲調用. 例如
func min<T: Comparable>(x: T, y: T) -> T {
return y < x ? y : x
}

從普通的泛型展開

func min<T: Comparable>(x: T, y: T, FTable: FunctionTable) -> T {
let xCopy = FTable.copy(x)
let yCopy = FTable.copy(y)
let m = FTable.lessThan(yCopy, xCopy) ? y : x
FTable.release(x)
FTable.release(y)
return m
}

在確定入參類型時,比如Int,可以優化為

func min<Int>(x: Int, y: Int) -> Int {
return y < x ? y : x
}

從而減少泛型調用的開銷

  • witness和虛函數表的去虛擬化優化通過給定類型去查找關聯的類的虛函數表或者類型的witness表,并將虛函數調用替換為調用函數映射

  • 性能內聯

  • 引用計數優化

  • 內存提升/優化

  • 高級領域特定優化swift編譯器對基礎的swift類型容器(類似Array或String)實現了高級優化.領域特定優化需要在標準庫和優化器之間定義交互.詳情可以參考 :ref:HighLevelSILOptimizations

SIL語法

SIL依賴于swift的類型系統和聲明,所以SIL語法是swift的延伸.一個.sil文件是一個增加了SIL定義的swift源文件.swift源文件只會針對聲明進行語法分析.swift的func方法體(除了嵌套聲明)和最高階的代碼會被SIL語法分析器忽略.在.sil文件中沒有隱式import.如果使用swift或者Buildin標準組件的話必須明確的引入. 以下是一個.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 是swift函數名taxicabNorm重整之后的命名
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    //萃取Point結構體內的x
 %3 = struct_extract %0 : $Point, #Point.y    ////萃取Point結構體內的y
 %4 = apply %1(%2, %3) : $(Double, Double) -> Double  //冒號前為計算體實現通過引用的展開,冒號后為類型說明
 return %4 : Double  //返回值
}
?
// 定義一個SIL虛函數表,匹配的是動態分派中函數實現的id,這個動態分派是在已知的靜態類的類型虛函數表中
sil_vtable Button {
 #Button.onClick!1: @_TC5norms6Button7onClickfS0_FT_T_
 #Button.onMouseDown!1: @_TC5norms6Button11onMouseDownfS0_FT_T_
 #Button.onMouseUp!1: @_TC5norms6Button9onMouseUpfS0_FT_T_
}

SIL階段

decl ::= sil-stage-decl
sil-stage-decl ::= 'sil_stage' sil-stage
?
sil-stage ::= 'raw'
sil-stage ::= 'canonical'

基于操作的不同階段,SIL擁有不同的聲明.

  • Raw SIL, 生的SIL是通過SILGen產生的,并未經過保證優化或者診斷通道.Raw SIL可能沒有完善結構的SSA圖表.可能會包含數據流錯誤.一些說明可能會以非規范的方式展示,例如無地址的assigndestory_addr的數值.Raw SIL不應該用于本地代碼的生成或分發.

  • Canonical SIL,規范SIL是在保證優化和診斷之后的SIL.數據流錯誤必須被消除掉,肯定說明也必須被規范化為更簡單的形式.性能優化和本地代碼是生成都是從這種格式衍生的.包含這種格式SIL的組件可以被分發. SIL文件通過在頂部聲明sil_stage rawsil_stage canonical來說明當前的操作階段.一個文件之后出現一種階段的聲明.

SIL類型

sil-type ::= '/pre> '*'? generic-parameter-list? type

SIL的類型是通過$符號進行標記的。SIL的類型系統和swift的密切相關.所以$之后的類型會根據swift的類型語法進行語法分析。

類型降級: type lowering

swift的正式類型系統,傾向于對大量的類型信息進行抽象概括.但是SIL目標是展示更多的實現細節,這個區別也體現在SIL的類型系統中.所以把正式類型降級為較低類型的操作稱為類型降級。

提取區別:Abstraction Difference

包含未約束類型的通用函數一定會被非直接調用.比如分配充足內存和創建地址指針指向這塊地址。如下的泛型函數

func generateArray<T>(n : Int, generator : () -> T) -> [T]

函數generator會通過一個隱式指針,指向存儲在一個非直接調用的地址中,(可以參考之前static vs dynamic dispatch中虛函數表的設計和實現).在處理任意類型值時操作都是一樣的.

  • 我們不希望對generateArray的每個T的類型去產生一個新的拷貝

  • 我們不希望對每個類型進行普遍聲明

  • 我們不希望通過T的類型動態的去構造對于genetator的調用

    但是我們也不希望現有的通用系統對我們的非通用代碼進行低效處理。例如,我們希望()->Int可以直接返回結果。但是()->Int()->T的代替(subsitution),對于generateArray<Int>的調用應該向generator傳遞()->Int。 所以一個正式類型在通用上下文中的表現可能會因為正式類型的的代替而不同.我們將這種不同成為提取區別.

SIL對于類型的提取區別的設計是,在每個級別的代替中,提取數值都可以被使用。

為了可以實現如上設計,泛型實例的正式類型應該一直使用非替換正式類型的提取方式進行降級.例如

struct Generator<T> {
 var fn : () -> T
}
var intGen : Generator<Int>

其中intGen.fn擁有代替類型()->Int,可以被降級為@callee_owned () -> Int,可以直接返回結果.但是如果更恰當的使用非代替方式,()->T就會變成@callee_owned () -> @out Int

當使用非代替的提取方式進行類型降級時,可以看做將擁有相同構造的類型中的具體類型替換為現有類型,以此來實現類型降級. 對于gGenerator<(Int, Int) -> Float>,g.fn是使用()->T進行降級的,簡單理解就是,類型是否是具體類型,如果是,才能進行提取方式進行降級,不然只能產生

@callee_owned () -> @owned @callee_owned (@in (Int, Int)) -> @out Float.

所以提取區別來代替通用函數中類型的標準是:是否是具體類型.is materializable or not 這個系統具有通過重復代替的方式實現提取方式的屬性.所以可以把降級的類型看做提取方式的編碼. SILGen已經擁有了使用提取方式轉換類型的工序. 目前只有函數和元祖類型會通過提取區別進行改變.

合法的SIL類型

SIL類型的值應該是這樣的:

  • 可被加載的SIL類型,$T

  • 合法SIL類型的地址$*T 或者如果T是一個合法的SIL類型需要滿足以下條件 不展開,需要查看SIL語法中的Legal SIL Types

類型T滿足一下條件才是一個合法的SIL類型

  • 函數類型符合SIL的約束條件
  • metatype可以描述功能
  • 原組的內部元素,類型也是合法的SIL類型
  • 可選Optional<U>,U也是合法類型
  • 非函數,原組,可選類型,metatype,或者l-value類型的合法的Swift類型
  • 包含合法類型的@box

注意,在遞歸條件內的類型,還需要是正式類型。例如泛型內的參數,仍然是Swift類型,而不是SIL降級類型。

地址類型

地址類型$*T指針指向的是任意引用的值或者$T
地址不是引用計數指針,不能被retained或released。

Box類型

本地變量和非直接的數值類型都是存儲在堆上的,@box T是一個引用計數類型,指向的是包含了多種T的盒子。盒子使用的是Swift的原生引用計數。

Metatype類型

SIL內的metatype類型必須描述自身表示:

  • @thin 意思是不需要內存
  • @thick 指存儲的是類型的引用或類型子類的引用
  • @objc 指存儲的是一個OC類對象的表示而不是Swift類型對象。

函數類型

SIL中的函數類型和Swift中的函數類型有以下區別:

  • SIL函數可能是泛型。例如,通過function_ref返回一個泛型函數類型。

  • SIL函數可以聲明為@noescape@noescape函數類型必須是convention(thin)或者@callee_guatanteed

  • SIL函數類型聲明了以下幾種處理上下文的情景:

    • @convention(thin)不要求上下文。這種類型也可以通過@noescape聲明。
    • @callee_guatanteed會被認為直接參數。也意味著convention(thick)
    • @callee_owned上下文值被認為是不擁有的直接參數。也意味著convention(thick)
    • @convention(block)上下文值被認為是不擁有的直接參數。
    • 其他函數類型會被描述為Properties of TypesCalling Convention
  • SIL函數必須聲明參數的協議。非直接的參數類型是*T,直接參數類型是T

    • @in是非直接參數。地址必須是已經初始化的對象,函數負責銷毀內部持有的值。
    • @inout是非直接參數。內存必須是已經初始化的對象。在函數返回之前,必須保證內存是被初始化的。
  • SIL函數需要聲明返回值的協議。

    • @out是非直接的結果。地址必須是未初始化的對象。

VTables

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm decl ::= sil-vtable
sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
?
sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-name

SIL使用class_method, super_method, objc_method,和 objc_super_method來表示類方法的動態分派dynamic dispatch class_methodsuper_method的實現是通過sil_vtable進行追蹤的.sil_vtable的聲明包含一個類的所有方法.

class A {
 func foo()
 func bar()
 func bas()
}
?
sil @A_foo : $@convention(thin) (@owned A) -> ()
sil @A_bar : $@convention(thin) (@owned A) -> ()
sil @A_bas : $@convention(thin) (@owned A) -> ()
?
sil_vtable A {
 #A.foo!1: @A_foo
 #A.bar!1: @A_bar
 #A.bas!1: @A_bas
}
?
class B : A {
 func bar()
}
?
sil @B_bar : $@convention(thin) (@owned B) -> ()
?
sil_vtable B {
 #A.foo!1: @A_foo
 #A.bar!1: @B_bar
 #A.bas!1: @A_bas
}
?
class C : B {
 func bas()
}
?
sil @C_bas : $@convention(thin) (@owned C) -> ()
?
sil_vtable C {
 #A.foo!1: @A_foo
 #A.bar!1: @B_bar
 #A.bas!1: @C_bas
}

swift的AST包含重載關系,可以用于在SIL的虛函數表中查找衍生類重載方法. 為了避免SIL方法是thunks,方法名是連接在原始方法實現之前.

Witness Tables

decl ::= sil-witness-table
sil-witness-table ::= 'sil_witness_table' sil-linkage?
 normal-protocol-conformance '{' sil-witness-entry* '}'

SIL將通用類型動態分派所需的信息編碼為witness表.這些信息用于在生成二進制碼時產生運行時分配表(runtime dispatch table).也可以用于對特定通用函數的SIL優化.每個明確的一致性聲明都會產生witness表.通用類型的所有實例共享一個通用witness表.衍生類會繼承基類的witness表.

protocol-conformance ::= normal-protocol-conformance
protocol-conformance ::= 'inherit' '(' protocol-conformance ')'
protocol-conformance ::= 'specialize' '<' substitution* '>'
 '(' protocol-conformance ')'
protocol-conformance ::= 'dependent'
normal-protocol-conformance ::= identifier ':' identifier 'module' identifier

witness的關鍵在于協議一致性.它是對于具體類型協議一致性的唯一標識.

  • 標準的協議一致性命名了一種協議方法需要遵守的類型.屬于該類型或擴展的組件,需要提供遵守協議方法的聲明

  • 如果派生類實現了基類遵守的協議,會體現為繼承協議一致性,簡單引用基類的協議一致性即可.

  • 如果通用類型的實例遵守一個協議,是通過特定遵守的方式去實現的.這種方式向標準一致性提供了用于通用類型的通用參數構建. witness table只會直接關聯標準一致性.繼承和特定一致性是在標準一致性下的間接引用.

sil-witness-entry ::= 'base_protocol' identifier ':' protocol-conformance
sil-witness-entry ::= 'method' sil-decl-ref ':' sil-function-name
sil-witness-entry ::= 'associated_type' identifier
sil-witness-entry ::= 'associated_type_protocol'
 '(' identifier ':' identifier ')' ':' protocol-conformance

witness table由以下內容構成

  • 基協議項提供的對于協議一致性的引用,可以用于witness協議的繼承協議

  • 方法項將協議中要求方法映射為SIL中實現了witness類型的方法.每個方法項必須對應witness協議中的要求方法

  • associate type關聯類型項將必須實現的協議方法中的關聯類型映射為符合witness的類型.注意witness類似是一個資源級別的swift類型,不是SIL類型(上面分析過SIL類型和swift類型的區別).關聯類型項必須覆蓋witness協議中的所有強制關聯項.

  • 關聯類型協議項將關聯類型中的協議映射為關聯類型的協議一致性.

witness table作用

swift中的協議是通過結構體實現的,可以支持交互.例如參數,屬性都可以是結構體.當將結構體傳遞給協議參數時,結構體特定的部分可能會丟失(在編譯期).協議的witness table就可以發揮作用(在運行時).

Default Witness Tables

decl ::= sil-default-witness-table
sil-default-witness-table ::= 'sil_default_witness_table'
 identifier minimum-witness-table-size
 '{' sil-default-witness-entry* '}'
minimum-witness-table-size ::= integer

SIL編碼要求默認witness table有開放(resilient)的默認實現.包含以下條件

  • 強制方法有默認實現

  • 不是協議中最后一個默認方法或繼承的強制方法,都有開放的默認實現.

強制方法的開放的默認實現,存儲在協議的元數據中. 默認witness表關鍵在在自身協議.只有公共可見協議才需要默認witness表.私有協議和內部協議是對外部組件不可見的,所以他們沒有增加新的強制方法的開放性問題.

sil-default-witness-entry ::= 'method' sil-decl-ref ':' sil-function-name

默認witness表目前只包含一項內容

  • 方法像,將協議中的要求方法映射到SIL中實現了管理所有witness類型的方法.

全局變量

數據流錯誤

數據流錯誤可能存在于Raw SIL中,swift從語義上將那些條件定義為錯誤,所以他們必須使用診斷通道進行診斷,并且不能存在于規范SIL中. 定義初始化 swift要求所有的本地變量在使用前必須被初始化.在構造函數中,結構體,枚舉或類類型的實例變量必須在對象被使用前初始化. 未全面覆蓋(unreachable)的控制流 unreachableraw SIL中生成,標記錯誤的控制流.例如對于非Void的函數沒有返回值,或者switch沒有完全覆蓋所有的條件.這種dead code消解的保證,可以避免unreachable的基礎block,也可以避免方法返回不合法的空類型.

運行時錯誤

一些操作,比如無條件的檢查轉換次數失敗或者編譯器Buildin.trap.都會引起運行時錯誤,這種錯誤會無條件的終止當前操作.如果可以檢驗運行時錯誤會發生或者已經發生.只要將它們排列到程序操作之后就可以將這些運行時錯誤重新安排.例如對于沒有確定開始和結尾的for循環代碼

// Given unknown start and end values, this loop may overflow
for var i = unknownStartValue; i != unknownEndValue; ++i {
 ...
}

會將內存溢出掛起,產生loop的關聯運行時錯誤,之后檢測循環的起始和結束點.只要循環體對于當前的操作沒有可見影響即可.

未定義的行為

某些操作的錯誤使用成為未定義行為.例如對于Buildin.RawPointer的不可用未檢測的類型轉換.或者使用低于LLVM說明的編譯器內建函數,調用當前LLVM不支持的行為.SIL程序中的未定義行為是無意義的,就像C中的未定義行為一樣,沒有語義對其進行預測.未定義行為不應該被合法的SIL文件觸發,但是在SIL級別不一定會被檢測和證實.

調用協議

以下內容討論swift函數是如何生成SIL的.

swift調用協議 @convention(swift) swift本地方法默認使用siwft調用協議是. 入參為原組的函數被遞歸解構為單獨的參數,即包含被調用的基礎塊的入口,也包含調用者的apply說明

func foo(_ x:Int, y:Int)
?
sil @foo : $(x:Int, y:Int) -> () {
entry(%x : $Int, %y : $Int):
 ...
}
?
func bar(_ x:Int, y:(Int, Int))
?
sil @bar : $(x:Int, y:(Int, Int)) -> () {
entry(%x : $Int, %y0 : $Int, %y1 : $Int):
 ...
}
?
func call_foo_and_bar() {
 foo(1, 2)
 bar(4, (5, 6))
}
?
sil @call_foo_and_bar : $() -> () {
entry:
 ...
 %foo = function_ref @foo : $(x:Int, y:Int) -> ()
 %foo_result = apply %foo(%1, %2) : $(x:Int, y:Int) -> ()
 ...
 %bar = function_ref @bar : $(x:Int, y:(Int, Int)) -> ()
 %bar_result = apply %bar(%4, %5, %6) : $(x:Int, y:(Int, Int)) -> ()
}

調用以繁瑣數據類型作為入參和輸出值的函數時

func foo(_ x:Int, y:Float) -> UnicodeScalar
?
foo(x, y)

SIL內如下體現

%foo = constant_ref $(Int, Float) -> UnicodeScalar, @foo
%z = apply %foo(%x, %y) : $(Int, Float) -> UnicodeScalar

swift方法調用協議@convention(method) 方法調用協議用于獨立方法的調用協議.柯里化method,使用self作為內部和外部參數.如果是非柯里化函數,self會在最后被傳入

struct Foo {
 func method(_ x:Int) -> Int {}
}
?
sil @Foo_method_1 : $((x : Int), @inout Foo) -> Int { ... }

witness方法調用協議@convention(witness_method) witness方法調用協議是用于witness tables中的協議witness方法.它幾乎等同于方法調用協議,只有對通用類型參數處理方面不同.對于非witness方法來說,機器協議可能會通過方法類型將方法簽名進行靜態轉換.但是因為witness必須在Self類型下進行多態分配,所以Self相關的元數據必須通過最大化的提取規則傳輸.

C調用協議@convention(c) 在swift的C組件編譯器中,C類型會被SIL對照到swift類型.C的函數入參和返回值,都會被SIL平臺調用協議忽略. SIL和swift目前都不能調用包含可變參數的C的方法.

OC調用協議@convention(objc_method) SIL中的OC方法使用規范和ARC內一致.也可以從OC定義中引入屬性. 使用@convention(block)并不會影響block的引用計數.

SIL中OC方法的self參數是非柯里化的最后一個參數.就像原生swift方法

@objc class NSString {
 func stringByPaddingToLength(Int) withString(NSString) startingAtIndex(Int)
}
?
sil @NSString_stringByPaddingToLength_withString_startingAtIndex \
 : $((Int, NSString, Int), NSString)

IR級別的將self作為第一個參數的行為在SIL中提取了的.比如現存的_cmd方法參數.

基于類型的別名分析: type based alias analysis

SIL提供了兩種類型別名分析(TBAA: Type Based Alias Analysis):類TBAA和類型訪問TBAA

指令集

感興趣可以自行查看SIL指令集

初始化和銷毀

alloc_stack

sil-instruction ::= 'alloc_stack' sil-type (',' debug-var-attr)*
?
%1 = alloc_stack $T
// %1 has type $*T

在棧區開辟充分符合T類型的內存空間。指令的返回結果是初始化的內存地址。

如果類型的尺寸在運行時才能確定,編譯器必須動態初始化內存。所以并不能確保內存一定是初始化在棧區,例如如果是特別大的數值,可能會在堆區初始化,棧區持有指針。

alloc_stack標記了值聲明周期的開始。在結束時必須使用dealloc_stack銷毀。

內存不能被retain,如果想初始化可retain的類型,使用alloc_box

總結alloc_stack在棧區為值類型開辟內存。不使用引用計數。

alloc_box

sil-instruction ::= 'alloc_box' sil-type (',' debug-var-attr)*
?
%1 = alloc_box $T
//   %1 has type $@box T

在堆上開辟足夠大的內存來支持各種類型的T,以@box持有引用計數。這個指令的結果是@box的引用計數持有的box,project_box是要來過去box內部的值的地址的。

box初始化時引用計數為1,但是內存并不會被初始化。box持有內部的值,在引用計數為0時使用destory_addr對內部值進行釋放,無法釋放box的值沒有被初始化的情況。這時候需要用到dealloc_box

總結alloc_box在堆上初始化指針類型的值,并且需要手動管理內存。

alloc_box和alloc_stack對比

alloc_boxalloc_stack最大的區別在于值的生命周期。舉例,如果在閉包之外有一個變量聲明,在閉包內使用了該變量。變量的值是可以被修改的,所以需要使用alloc_box來引用變量。

對于var聲明的變量,因為可以多次修改它的值,甚至在作用域外也可以修改。所以使用alloc_box管理引用計數。

優化:Alloc box to stack

在SILGen階段,會對閉包內使用變量的情況,通過alloc_box進行管理。

在SIL guaranteed transformations階段,即生成正式SIL的階段,會對于在閉包內沒有進行值修改的變量內存分配進行優化,將alloc_box替換為alloc_stack。這個功能是在AllocBoxToStack組件內實現的。內部實現是將堆區不必要的初始化移動到棧區。

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374