簡介
在LLVM的官方文檔中對Swift的編譯器設計描述如下: Swift編程語言是在LLVM上構建,并且使用LLVM IR和LLVM的后端去生成代碼。但是Swift編譯器還包含新的高級別的中間語言,稱為SIL
。SIL
會對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編譯器流程
Clang編譯流程存在以下問題:
在源碼和LLVM IR直接存在非常大的抽象鴻溝
IR不適用對源碼進行分析和檢查 使用了Analysis通過CFG進行分析,分析和代碼生成是兩部分
CFG(控制流圖)不夠精確
CFG不是主道(hot path)
在CFG和IR降級中會出現重復分析,做無用功
Swift編譯器流程
Swift作為一個高級別和安全的語言具有以下特點:
高級別語言
通過代碼充分的展示語言的特性
支持基于協議的泛型
安全語言
充分的數據流檢查:未初始化變量,函數返回處理檢測,這些項在檢測不合格時會產生對應的編譯錯誤
邊界和溢出的檢測
Swift編譯流程圖如下:
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圖表.可能會包含數據流錯誤.一些說明可能會以非規范的方式展示,例如無地址的assign
和destory_addr
的數值.Raw SIL
不應該用于本地代碼的生成或分發.Canonical SIL,規范SIL是在保證優化和診斷之后的
SIL
.數據流錯誤必須被消除掉,肯定說明也必須被規范化為更簡單的形式.性能優化和本地代碼是生成都是從這種格式衍生的.包含這種格式SIL
的組件可以被分發.SIL
文件通過在頂部聲明sil_stage raw
或sil_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
當使用非代替的提取方式進行類型降級時,可以看做將擁有相同構造的類型中的具體類型替換為現有類型,以此來實現類型降級. 對于g
的Generator<(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 Types
和Calling 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_method 和 super_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)的控制流 unreachable
在raw 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_box
和alloc_stack
最大的區別在于值的生命周期。舉例,如果在閉包之外有一個變量聲明,在閉包內使用了該變量。變量的值是可以被修改的,所以需要使用alloc_box
來引用變量。
對于var
聲明的變量,因為可以多次修改它的值,甚至在作用域外也可以修改。所以使用alloc_box
管理引用計數。
優化:Alloc box to stack
在SILGen階段,會對閉包內使用變量的情況,通過alloc_box
進行管理。
在SIL guaranteed transformations階段,即生成正式SIL的階段,會對于在閉包內沒有進行值修改的變量內存分配進行優化,將alloc_box
替換為alloc_stack
。這個功能是在AllocBoxToStack組件內實現的。內部實現是將堆區不必要的初始化移動到棧區。