簡介
在LLVM的官方文檔中對Swift的編譯器設(shè)計(jì)描述如下: Swift編程語言是在LLVM上構(gòu)建,并且使用LLVM IR和LLVM的后端去生成代碼。但是Swift編譯器還包含新的高級別的中間語言,稱為SIL。SIL會對Swift進(jìn)行較高級別的語義分析和優(yōu)化。 我們下面分析一下SIL設(shè)計(jì)的動機(jī)和SIL的應(yīng)用,包括高級別的語義分析,診斷轉(zhuǎn)換,去虛擬化,特化,引用計(jì)數(shù)優(yōu)化,TBAA(Type Based Alias Analysis)等。并且會在某些流程中加入對SIL和LLVM IR對比。
SIL介紹
SIL是為了實(shí)現(xiàn)swift編程語言而設(shè)計(jì)的,包含高級語義信息的SSA格式的中間語言.SIL包含以下功能:
一系列的高級別優(yōu)化保障,用于對運(yùn)行時(shí)和診斷行為提供可預(yù)測的底線
對swift語言數(shù)據(jù)流分析強(qiáng)制要求,對不滿足強(qiáng)制要求的問題產(chǎn)生診斷。例如變量和結(jié)構(gòu)體必須明確初始化,代碼可達(dá)性即方法return的檢測,switch的覆蓋率
確保高級別優(yōu)化。包含retain/release優(yōu)化,動態(tài)方法的去虛擬化(devirtualization,不了解虛函數(shù)可以查看之前文章static vs dynamic dispatch),閉包內(nèi)聯(lián),內(nèi)存初始化提升和泛型方法實(shí)例 化.
可用于分配"脆弱"內(nèi)聯(lián)的穩(wěn)定分配格式,將Swift庫組件的泛型優(yōu)化為二進(jìn)制。
和LLVM IR不同,SIL一般是target無關(guān)的獨(dú)立格式的表示,可用于代碼分發(fā).但是也可以和LLVM一樣表達(dá)具體target概念. 如果想查看更多SIL的實(shí)現(xiàn)和SIL通道的開發(fā)信息,可以查看SIL開發(fā)手冊(原英文文檔為SILProgrammersManual.md)。
我們下面對Clang的Swift編譯器的傳遞流程進(jìn)行對比:
編譯流程對比
Clang編譯器流程
Clang編譯流程存在以下問題:
在源碼和LLVM IR直接存在非常大的抽象鴻溝
IR不適用對源碼進(jìn)行分析和檢查 使用了Analysis通過CFG進(jìn)行分析,分析和代碼生成是兩部分
CFG(控制流圖)不夠精確
CFG不是主道(hot path)
在CFG和IR降級中會出現(xiàn)重復(fù)分析,做無用功
Swift編譯器流程
Swift作為一個(gè)高級別和安全的語言具有以下特點(diǎn):
高級別語言
通過代碼充分的展示語言的特性
支持基于協(xié)議的泛型
安全語言
充分的數(shù)據(jù)流檢查:未初始化變量,函數(shù)返回處理檢測,這些項(xiàng)在檢測不合格時(shí)會產(chǎn)生對應(yīng)的編譯錯(cuò)誤
邊界和溢出的檢測
Swift編譯流程圖如下:
Swift編譯器提供的SIL具有以下優(yōu)勢:
對程序語義信息重復(fù)表示
可以用于代碼生成和分析 Clang不可以
處于編譯器的主道
可以連接源碼和LLVM的抽象鴻溝
SIL的設(shè)計(jì)
SIL流程分析
Swift編譯器作為高級編譯器,具有以下嚴(yán)格的傳遞流程結(jié)構(gòu)。 Swift編譯器的流程如下
Parse: 語法分析組件從Swift源碼構(gòu)成AST
語義分析組件對AST進(jìn)行類型檢查,并對其進(jìn)行類型信息注釋。
SILGen組件從AST形成"生的(raw)"SIL
一系列在 生 SIL上運(yùn)行的,用于確定優(yōu)化和診斷合格,對不合格的代碼嵌入特定的語言診斷。這些操作一定會執(zhí)行,即使在
-Onone選項(xiàng)下也不例外。之后產(chǎn)生 正式(canonical) SIL.一般情況下,是否在正式
SIL上運(yùn)行SIL優(yōu)化是可選的,這個(gè)檢測可以提升結(jié)果可執(zhí)行文件的性能.可以通過優(yōu)化級別來控制,在-Onone模式下不會執(zhí)行.IRGen會將
正式SIL降級為LLVM IR.LLVM后端提供LLVM優(yōu)化,執(zhí)行LLVM代碼生成器并產(chǎn)生二進(jìn)制碼.
SIL操作流程分析
SILGen
SILGen遍歷Swift進(jìn)行了類型檢查的AST,產(chǎn)生 raw SIL.SILGen產(chǎn)生的SIL格式具有如下屬性:
屬性會被加載和存儲在可變內(nèi)存地址,而不是使用嚴(yán)格的SSA(靜態(tài)單賦值形式:每個(gè)變量僅被賦值一次)。這和Clang前端產(chǎn)生的繁重的LLVM IR(例如初始化
alloca)類似。但是Swift的變量在大多數(shù)情況下使用了引用計(jì)數(shù)器,使得變量可以被retained,release和被閉包引用。數(shù)據(jù)流檢測。例如明確的內(nèi)存分配,方法return檢查,switch覆蓋等.此環(huán)節(jié)目前不是強(qiáng)制執(zhí)行的
transparent函數(shù)優(yōu)化目前未實(shí)現(xiàn).
這些特性會被接下來的確保優(yōu)化和診斷檢查使用,這兩項(xiàng)在 raw SIL上一定會運(yùn)行。
確保優(yōu)化和診斷檢查
在SILGen之后,會在raw SIL上運(yùn)行確定順序的優(yōu)化。我們并不希望編譯器產(chǎn)生的診斷改變編譯器的進(jìn)展,所以這些優(yōu)化的設(shè)計(jì)是簡單和可預(yù)測.
-
Mandatory inlining: 強(qiáng)制內(nèi)聯(lián)對于transparent函數(shù)進(jìn)行內(nèi)聯(lián)。
透明函數(shù)即,如果一個(gè)函數(shù)只會受到入?yún)⒌淖兓?,那么這個(gè)函數(shù)每次的調(diào)用都會是相同的,同樣的入?yún)⒁欢〞祷匾粯拥姆祷刂?,在確定入?yún)⒌臅r(shí)候,返回值是可預(yù)測的。這樣的函數(shù),就可以進(jìn)行內(nèi)聯(lián)優(yōu)化。
-
內(nèi)存提升實(shí)現(xiàn)分為兩個(gè)優(yōu)化階段:
將
alloc_box結(jié)構(gòu)優(yōu)化為alloc_stack提升無暴露地址(
non_address-exposed)的alloc_stack說明到SSA注冊. 常數(shù)傳播: Constant propagation折疊常量表達(dá),繁殖常量值.如果在計(jì)算常量表達(dá)式時(shí)出現(xiàn)算術(shù)溢出,就會產(chǎn)生警告.
返回分析查證每個(gè)方法在每個(gè)代碼路徑只返回一個(gè)值,并且不會在定義的末端出現(xiàn)無返回值的錯(cuò)誤.如果不需要返回值的函數(shù)return了也會報(bào)錯(cuò).
臨界拆分: critical edge splitting不支持任意的基礎(chǔ)block參數(shù)通過終端進(jìn)行臨界拆分. 在 Advanced Compiler Design & Implementation的第13.3章節(jié),第407,408頁這樣描述臨界分裂
這個(gè)算法的核心作用體現(xiàn)為:流程圖中的臨界如果在流分析前被拆分的話,會使得運(yùn)算更近高效. 原文: 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.
如果診斷通道完成后,會產(chǎn)生規(guī)范SIL.
泛型特化: Generic specialization
在
-Onone模式下的ARC性能優(yōu)化.
說完了處理raw SIL的特定流程,我們對上面提到的優(yōu)化通道: optimization passes進(jìn)行下說明.
泛型優(yōu)化
SIL獲取語言特定的類型信息,使得無法在LLVM IR實(shí)現(xiàn)的高級優(yōu)化在swift編譯器中得以實(shí)現(xiàn).
- 泛型特化分析泛型函數(shù)的特定調(diào)用,并生成新的特定版本的函數(shù).然后將泛型的特定用法全部重寫為對應(yīng)的特定函數(shù)的指甲調(diào)用. 例如
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
}
在確定入?yún)㈩愋蜁r(shí),比如Int,可以優(yōu)化為
func min<Int>(x: Int, y: Int) -> Int {
return y < x ? y : x
}
從而減少泛型調(diào)用的開銷
witness和虛函數(shù)表的去虛擬化優(yōu)化通過給定類型去查找關(guān)聯(lián)的類的虛函數(shù)表或者類型的witness表,并將虛函數(shù)調(diào)用替換為調(diào)用函數(shù)映射
性能內(nèi)聯(lián)
引用計(jì)數(shù)優(yōu)化
內(nèi)存提升/優(yōu)化
高級領(lǐng)域特定優(yōu)化swift編譯器對基礎(chǔ)的swift類型容器(類似Array或String)實(shí)現(xiàn)了高級優(yōu)化.領(lǐng)域特定優(yōu)化需要在標(biāo)準(zhǔn)庫和優(yōu)化器之間定義交互.詳情可以參考 :ref:
HighLevelSILOptimizations
SIL語法
SIL依賴于swift的類型系統(tǒng)和聲明,所以SIL語法是swift的延伸.一個(gè).sil文件是一個(gè)增加了SIL定義的swift源文件.swift源文件只會針對聲明進(jìn)行語法分析.swift的func方法體(除了嵌套聲明)和最高階的代碼會被SIL語法分析器忽略.在.sil文件中沒有隱式import.如果使用swift或者Buildin標(biāo)準(zhǔn)組件的話必須明確的引入. 以下是一個(gè).sil文件的示例
sil_stage canonical
?
import Swift
?
// 定義用于SIL函數(shù)的類型
?
struct Point {
var x : Double
var y : Double
}
?
class Button {
func onClick()
func onMouseDown()
func onMouseUp()
}
?
// 定義一個(gè)swift函數(shù),函數(shù)體會被SIL忽略
func taxicabNorm(_ a:Point) -> Double {
return a.x + a.y
}
?
// 定義一個(gè)SIL函數(shù)
// @_T5norms11taxicabNormfT1aV5norms5Point_Sd 是swift函數(shù)名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結(jié)構(gòu)體內(nèi)的x
%3 = struct_extract %0 : $Point, #Point.y ////萃取Point結(jié)構(gòu)體內(nèi)的y
%4 = apply %1(%2, %3) : $(Double, Double) -> Double //冒號前為計(jì)算體實(shí)現(xiàn)通過引用的展開,冒號后為類型說明
return %4 : Double //返回值
}
?
// 定義一個(gè)SIL虛函數(shù)表,匹配的是動態(tài)分派中函數(shù)實(shí)現(xiàn)的id,這個(gè)動態(tài)分派是在已知的靜態(tài)類的類型虛函數(shù)表中
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產(chǎn)生的,并未經(jīng)過保證優(yōu)化或者診斷通道.Raw SIL可能沒有完善結(jié)構(gòu)的SSA圖表.可能會包含數(shù)據(jù)流錯(cuò)誤.一些說明可能會以非規(guī)范的方式展示,例如無地址的assign和destory_addr的數(shù)值.Raw SIL不應(yīng)該用于本地代碼的生成或分發(fā).Canonical SIL,規(guī)范SIL是在保證優(yōu)化和診斷之后的
SIL.數(shù)據(jù)流錯(cuò)誤必須被消除掉,肯定說明也必須被規(guī)范化為更簡單的形式.性能優(yōu)化和本地代碼是生成都是從這種格式衍生的.包含這種格式SIL的組件可以被分發(fā).SIL文件通過在頂部聲明sil_stage raw或sil_stage canonical來說明當(dāng)前的操作階段.一個(gè)文件之后出現(xiàn)一種階段的聲明.
SIL類型
sil-type ::= '/pre> '*'? generic-parameter-list? type
SIL的類型是通過$符號進(jìn)行標(biāo)記的。SIL的類型系統(tǒng)和swift的密切相關(guān).所以$之后的類型會根據(jù)swift的類型語法進(jìn)行語法分析。
類型降級: type lowering
swift的正式類型系統(tǒng),傾向于對大量的類型信息進(jìn)行抽象概括.但是SIL目標(biāo)是展示更多的實(shí)現(xiàn)細(xì)節(jié),這個(gè)區(qū)別也體現(xiàn)在SIL的類型系統(tǒng)中.所以把正式類型降級為較低類型的操作稱為類型降級。
提取區(qū)別:Abstraction Difference
包含未約束類型的通用函數(shù)一定會被非直接調(diào)用.比如分配充足內(nèi)存和創(chuàng)建地址指針指向這塊地址。如下的泛型函數(shù)
func generateArray<T>(n : Int, generator : () -> T) -> [T]
函數(shù)generator會通過一個(gè)隱式指針,指向存儲在一個(gè)非直接調(diào)用的地址中,(可以參考之前static vs dynamic dispatch中虛函數(shù)表的設(shè)計(jì)和實(shí)現(xiàn)).在處理任意類型值時(shí)操作都是一樣的.
我們不希望對
generateArray的每個(gè)T的類型去產(chǎn)生一個(gè)新的拷貝我們不希望對每個(gè)類型進(jìn)行普遍聲明
-
我們不希望通過
T的類型動態(tài)的去構(gòu)造對于genetator的調(diào)用但是我們也不希望現(xiàn)有的通用系統(tǒng)對我們的非通用代碼進(jìn)行低效處理。例如,我們希望
()->Int可以直接返回結(jié)果。但是()->Int是()->T的代替(subsitution),對于generateArray<Int>的調(diào)用應(yīng)該向generator傳遞()->Int。 所以一個(gè)正式類型在通用上下文中的表現(xiàn)可能會因?yàn)檎筋愋偷牡拇娑煌?我們將這種不同成為提取區(qū)別.
SIL對于類型的提取區(qū)別的設(shè)計(jì)是,在每個(gè)級別的代替中,提取數(shù)值都可以被使用。
為了可以實(shí)現(xiàn)如上設(shè)計(jì),泛型實(shí)例的正式類型應(yīng)該一直使用非替換正式類型的提取方式進(jìn)行降級.例如
struct Generator<T> {
var fn : () -> T
}
var intGen : Generator<Int>
其中intGen.fn擁有代替類型()->Int,可以被降級為@callee_owned () -> Int,可以直接返回結(jié)果.但是如果更恰當(dāng)?shù)氖褂梅谴娣绞剑?code>()->T就會變成@callee_owned () -> @out Int
當(dāng)使用非代替的提取方式進(jìn)行類型降級時(shí),可以看做將擁有相同構(gòu)造的類型中的具體類型替換為現(xiàn)有類型,以此來實(shí)現(xiàn)類型降級. 對于g 的Generator<(Int, Int) -> Float>,g.fn是使用()->T進(jìn)行降級的,簡單理解就是,類型是否是具體類型,如果是,才能進(jìn)行提取方式進(jìn)行降級,不然只能產(chǎn)生
@callee_owned () -> @owned @callee_owned (@in (Int, Int)) -> @out Float.
所以提取區(qū)別來代替通用函數(shù)中類型的標(biāo)準(zhǔn)是:是否是具體類型.is materializable or not 這個(gè)系統(tǒng)具有通過重復(fù)代替的方式實(shí)現(xiàn)提取方式的屬性.所以可以把降級的類型看做提取方式的編碼. SILGen已經(jīng)擁有了使用提取方式轉(zhuǎn)換類型的工序. 目前只有函數(shù)和元祖類型會通過提取區(qū)別進(jìn)行改變.
合法的SIL類型
SIL類型的值應(yīng)該是這樣的:
可被加載的SIL類型,
$T合法SIL類型的地址
$*T或者如果T是一個(gè)合法的SIL類型需要滿足以下條件 不展開,需要查看SIL語法中的Legal SIL Types
類型T滿足一下條件才是一個(gè)合法的SIL類型
- 函數(shù)類型符合
SIL的約束條件 - metatype可以描述功能
- 原組的內(nèi)部元素,類型也是合法的
SIL類型 - 可選
Optional<U>,U也是合法類型 - 非函數(shù),原組,可選類型,metatype,或者l-value類型的合法的Swift類型
- 包含合法類型的
@box
注意,在遞歸條件內(nèi)的類型,還需要是正式類型。例如泛型內(nèi)的參數(shù),仍然是Swift類型,而不是SIL降級類型。
地址類型
地址類型$*T指針指向的是任意引用的值或者$T。
地址不是引用計(jì)數(shù)指針,不能被retained或released。
Box類型
本地變量和非直接的數(shù)值類型都是存儲在堆上的,@box T是一個(gè)引用計(jì)數(shù)類型,指向的是包含了多種T的盒子。盒子使用的是Swift的原生引用計(jì)數(shù)。
Metatype類型
SIL內(nèi)的metatype類型必須描述自身表示:
-
@thin意思是不需要內(nèi)存 -
@thick指存儲的是類型的引用或類型子類的引用 -
@objc指存儲的是一個(gè)OC類對象的表示而不是Swift類型對象。
函數(shù)類型
SIL中的函數(shù)類型和Swift中的函數(shù)類型有以下區(qū)別:
SIL函數(shù)可能是泛型。例如,通過function_ref返回一個(gè)泛型函數(shù)類型。SIL函數(shù)可以聲明為@noescape。@noescape函數(shù)類型必須是convention(thin)或者@callee_guatanteed。-
SIL函數(shù)類型聲明了以下幾種處理上下文的情景:-
@convention(thin)不要求上下文。這種類型也可以通過@noescape聲明。 -
@callee_guatanteed會被認(rèn)為直接參數(shù)。也意味著convention(thick)。 -
@callee_owned上下文值被認(rèn)為是不擁有的直接參數(shù)。也意味著convention(thick)。 -
@convention(block)上下文值被認(rèn)為是不擁有的直接參數(shù)。 - 其他函數(shù)類型會被描述為
Properties of Types和Calling Convention
-
-
SIL函數(shù)必須聲明參數(shù)的協(xié)議。非直接的參數(shù)類型是*T,直接參數(shù)類型是T-
@in是非直接參數(shù)。地址必須是已經(jīng)初始化的對象,函數(shù)負(fù)責(zé)銷毀內(nèi)部持有的值。 -
@inout是非直接參數(shù)。內(nèi)存必須是已經(jīng)初始化的對象。在函數(shù)返回之前,必須保證內(nèi)存是被初始化的。
-
-
SIL函數(shù)需要聲明返回值的協(xié)議。-
@out是非直接的結(jié)果。地址必須是未初始化的對象。
-
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來表示類方法的動態(tài)分派dynamic dispatch class_method 和 super_method的實(shí)現(xiàn)是通過sil_vtable進(jìn)行追蹤的.sil_vtable的聲明包含一個(gè)類的所有方法.
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包含重載關(guān)系,可以用于在SIL的虛函數(shù)表中查找衍生類重載方法. 為了避免SIL方法是thunks,方法名是連接在原始方法實(shí)現(xiàn)之前.
Witness Tables
decl ::= sil-witness-table
sil-witness-table ::= 'sil_witness_table' sil-linkage?
normal-protocol-conformance '{' sil-witness-entry* '}'
SIL將通用類型動態(tài)分派所需的信息編碼為witness表.這些信息用于在生成二進(jìn)制碼時(shí)產(chǎn)生運(yùn)行時(shí)分配表(runtime dispatch table).也可以用于對特定通用函數(shù)的SIL優(yōu)化.每個(gè)明確的一致性聲明都會產(chǎn)生witness表.通用類型的所有實(shí)例共享一個(gè)通用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的關(guān)鍵在于協(xié)議一致性.它是對于具體類型協(xié)議一致性的唯一標(biāo)識.
標(biāo)準(zhǔn)的協(xié)議一致性命名了一種協(xié)議方法需要遵守的類型.屬于該類型或擴(kuò)展的組件,需要提供遵守協(xié)議方法的聲明
如果派生類實(shí)現(xiàn)了基類遵守的協(xié)議,會體現(xiàn)為繼承協(xié)議一致性,簡單引用基類的協(xié)議一致性即可.
如果通用類型的實(shí)例遵守一個(gè)協(xié)議,是通過特定遵守的方式去實(shí)現(xiàn)的.這種方式向標(biāo)準(zhǔn)一致性提供了用于通用類型的通用參數(shù)構(gòu)建.
witness table只會直接關(guān)聯(lián)標(biāo)準(zhǔn)一致性.繼承和特定一致性是在標(biāo)準(zhǔn)一致性下的間接引用.
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由以下內(nèi)容構(gòu)成
基協(xié)議項(xiàng)提供的對于協(xié)議一致性的引用,可以用于witness協(xié)議的繼承協(xié)議
方法項(xiàng)將協(xié)議中要求方法映射為
SIL中實(shí)現(xiàn)了witness類型的方法.每個(gè)方法項(xiàng)必須對應(yīng)witness協(xié)議中的要求方法associate type關(guān)聯(lián)類型項(xiàng)將必須實(shí)現(xiàn)的協(xié)議方法中的關(guān)聯(lián)類型映射為符合witness的類型.注意witness類似是一個(gè)資源級別的swift類型,不是
SIL類型(上面分析過SIL類型和swift類型的區(qū)別).關(guān)聯(lián)類型項(xiàng)必須覆蓋witness協(xié)議中的所有強(qiáng)制關(guān)聯(lián)項(xiàng).關(guān)聯(lián)類型協(xié)議項(xiàng)將關(guān)聯(lián)類型中的協(xié)議映射為關(guān)聯(lián)類型的協(xié)議一致性.
witness table作用
swift中的協(xié)議是通過結(jié)構(gòu)體實(shí)現(xiàn)的,可以支持交互.例如參數(shù),屬性都可以是結(jié)構(gòu)體.當(dāng)將結(jié)構(gòu)體傳遞給協(xié)議參數(shù)時(shí),結(jié)構(gòu)體特定的部分可能會丟失(在編譯期).協(xié)議的witness table就可以發(fā)揮作用(在運(yùn)行時(shí)).
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編碼要求默認(rèn)witness table有開放(resilient)的默認(rèn)實(shí)現(xiàn).包含以下條件
強(qiáng)制方法有默認(rèn)實(shí)現(xiàn)
不是協(xié)議中最后一個(gè)默認(rèn)方法或繼承的強(qiáng)制方法,都有開放的默認(rèn)實(shí)現(xiàn).
強(qiáng)制方法的開放的默認(rèn)實(shí)現(xiàn),存儲在協(xié)議的元數(shù)據(jù)中. 默認(rèn)witness表關(guān)鍵在在自身協(xié)議.只有公共可見協(xié)議才需要默認(rèn)witness表.私有協(xié)議和內(nèi)部協(xié)議是對外部組件不可見的,所以他們沒有增加新的強(qiáng)制方法的開放性問題.
sil-default-witness-entry ::= 'method' sil-decl-ref ':' sil-function-name
默認(rèn)witness表目前只包含一項(xiàng)內(nèi)容
- 方法像,將協(xié)議中的要求方法映射到
SIL中實(shí)現(xiàn)了管理所有witness類型的方法.
全局變量
數(shù)據(jù)流錯(cuò)誤
數(shù)據(jù)流錯(cuò)誤可能存在于Raw SIL中,swift從語義上將那些條件定義為錯(cuò)誤,所以他們必須使用診斷通道進(jìn)行診斷,并且不能存在于規(guī)范SIL中. 定義初始化 swift要求所有的本地變量在使用前必須被初始化.在構(gòu)造函數(shù)中,結(jié)構(gòu)體,枚舉或類類型的實(shí)例變量必須在對象被使用前初始化. 未全面覆蓋(unreachable)的控制流 unreachable在raw SIL中生成,標(biāo)記錯(cuò)誤的控制流.例如對于非Void的函數(shù)沒有返回值,或者switch沒有完全覆蓋所有的條件.這種dead code消解的保證,可以避免unreachable的基礎(chǔ)block,也可以避免方法返回不合法的空類型.
運(yùn)行時(shí)錯(cuò)誤
一些操作,比如無條件的檢查轉(zhuǎn)換次數(shù)失敗或者編譯器Buildin.trap.都會引起運(yùn)行時(shí)錯(cuò)誤,這種錯(cuò)誤會無條件的終止當(dāng)前操作.如果可以檢驗(yàn)運(yùn)行時(shí)錯(cuò)誤會發(fā)生或者已經(jīng)發(fā)生.只要將它們排列到程序操作之后就可以將這些運(yùn)行時(shí)錯(cuò)誤重新安排.例如對于沒有確定開始和結(jié)尾的for循環(huán)代碼
// Given unknown start and end values, this loop may overflow
for var i = unknownStartValue; i != unknownEndValue; ++i {
...
}
會將內(nèi)存溢出掛起,產(chǎn)生loop的關(guān)聯(lián)運(yùn)行時(shí)錯(cuò)誤,之后檢測循環(huán)的起始和結(jié)束點(diǎn).只要循環(huán)體對于當(dāng)前的操作沒有可見影響即可.
未定義的行為
某些操作的錯(cuò)誤使用成為未定義行為.例如對于Buildin.RawPointer的不可用未檢測的類型轉(zhuǎn)換.或者使用低于LLVM說明的編譯器內(nèi)建函數(shù),調(diào)用當(dāng)前LLVM不支持的行為.SIL程序中的未定義行為是無意義的,就像C中的未定義行為一樣,沒有語義對其進(jìn)行預(yù)測.未定義行為不應(yīng)該被合法的SIL文件觸發(fā),但是在SIL級別不一定會被檢測和證實(shí).
調(diào)用協(xié)議
以下內(nèi)容討論swift函數(shù)是如何生成SIL的.
swift調(diào)用協(xié)議 @convention(swift) swift本地方法默認(rèn)使用siwft調(diào)用協(xié)議是. 入?yún)樵M的函數(shù)被遞歸解構(gòu)為單獨(dú)的參數(shù),即包含被調(diào)用的基礎(chǔ)塊的入口,也包含調(diào)用者的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)) -> ()
}
調(diào)用以繁瑣數(shù)據(jù)類型作為入?yún)⒑洼敵鲋档暮瘮?shù)時(shí)
func foo(_ x:Int, y:Float) -> UnicodeScalar
?
foo(x, y)
在SIL內(nèi)如下體現(xiàn)
%foo = constant_ref $(Int, Float) -> UnicodeScalar, @foo
%z = apply %foo(%x, %y) : $(Int, Float) -> UnicodeScalar
swift方法調(diào)用協(xié)議@convention(method) 方法調(diào)用協(xié)議用于獨(dú)立方法的調(diào)用協(xié)議.柯里化method,使用self作為內(nèi)部和外部參數(shù).如果是非柯里化函數(shù),self會在最后被傳入
struct Foo {
func method(_ x:Int) -> Int {}
}
?
sil @Foo_method_1 : $((x : Int), @inout Foo) -> Int { ... }
witness方法調(diào)用協(xié)議@convention(witness_method) witness方法調(diào)用協(xié)議是用于witness tables中的協(xié)議witness方法.它幾乎等同于方法調(diào)用協(xié)議,只有對通用類型參數(shù)處理方面不同.對于非witness方法來說,機(jī)器協(xié)議可能會通過方法類型將方法簽名進(jìn)行靜態(tài)轉(zhuǎn)換.但是因?yàn)閣itness必須在Self類型下進(jìn)行多態(tài)分配,所以Self相關(guān)的元數(shù)據(jù)必須通過最大化的提取規(guī)則傳輸.
C調(diào)用協(xié)議@convention(c) 在swift的C組件編譯器中,C類型會被SIL對照到swift類型.C的函數(shù)入?yún)⒑头祷刂?都會被SIL平臺調(diào)用協(xié)議忽略. SIL和swift目前都不能調(diào)用包含可變參數(shù)的C的方法.
OC調(diào)用協(xié)議@convention(objc_method) SIL中的OC方法使用規(guī)范和ARC內(nèi)一致.也可以從OC定義中引入屬性. 使用@convention(block)并不會影響block的引用計(jì)數(shù).
在SIL中OC方法的self參數(shù)是非柯里化的最后一個(gè)參數(shù).就像原生swift方法
@objc class NSString {
func stringByPaddingToLength(Int) withString(NSString) startingAtIndex(Int)
}
?
sil @NSString_stringByPaddingToLength_withString_startingAtIndex \
: $((Int, NSString, Int), NSString)
IR級別的將self作為第一個(gè)參數(shù)的行為在SIL中提取了的.比如現(xiàn)存的_cmd方法參數(shù).
基于類型的別名分析: 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
在棧區(qū)開辟充分符合T類型的內(nèi)存空間。指令的返回結(jié)果是初始化的內(nèi)存地址。
如果類型的尺寸在運(yùn)行時(shí)才能確定,編譯器必須動態(tài)初始化內(nèi)存。所以并不能確保內(nèi)存一定是初始化在棧區(qū),例如如果是特別大的數(shù)值,可能會在堆區(qū)初始化,棧區(qū)持有指針。
alloc_stack標(biāo)記了值聲明周期的開始。在結(jié)束時(shí)必須使用dealloc_stack銷毀。
內(nèi)存不能被retain,如果想初始化可retain的類型,使用alloc_box。
總結(jié):alloc_stack在棧區(qū)為值類型開辟內(nèi)存。不使用引用計(jì)數(shù)。
alloc_box
sil-instruction ::= 'alloc_box' sil-type (',' debug-var-attr)*
?
%1 = alloc_box $T
// %1 has type $@box T
在堆上開辟足夠大的內(nèi)存來支持各種類型的T,以@box持有引用計(jì)數(shù)。這個(gè)指令的結(jié)果是@box的引用計(jì)數(shù)持有的box,project_box是要來過去box內(nèi)部的值的地址的。
box初始化時(shí)引用計(jì)數(shù)為1,但是內(nèi)存并不會被初始化。box持有內(nèi)部的值,在引用計(jì)數(shù)為0時(shí)使用destory_addr對內(nèi)部值進(jìn)行釋放,無法釋放box的值沒有被初始化的情況。這時(shí)候需要用到dealloc_box。
總結(jié):alloc_box在堆上初始化指針類型的值,并且需要手動管理內(nèi)存。
alloc_box和alloc_stack對比
alloc_box和alloc_stack最大的區(qū)別在于值的生命周期。舉例,如果在閉包之外有一個(gè)變量聲明,在閉包內(nèi)使用了該變量。變量的值是可以被修改的,所以需要使用alloc_box來引用變量。
對于var聲明的變量,因?yàn)榭梢远啻涡薷乃闹担踔猎谧饔糜蛲庖部梢孕薷?。所以使?code>alloc_box管理引用計(jì)數(shù)。
優(yōu)化:Alloc box to stack
在SILGen階段,會對閉包內(nèi)使用變量的情況,通過alloc_box進(jìn)行管理。
在SIL guaranteed transformations階段,即生成正式SIL的階段,會對于在閉包內(nèi)沒有進(jìn)行值修改的變量內(nèi)存分配進(jìn)行優(yōu)化,將alloc_box替換為alloc_stack。這個(gè)功能是在AllocBoxToStack組件內(nèi)實(shí)現(xiàn)的。內(nèi)部實(shí)現(xiàn)是將堆區(qū)不必要的初始化移動到棧區(qū)。