簡(jiǎn)介
在LLVM的官方文檔中對(duì)Swift的編譯器設(shè)計(jì)描述如下: Swift編程語(yǔ)言是在LLVM上構(gòu)建珍促,并且使用LLVM IR和LLVM的后端去生成代碼吩愧。但是Swift編譯器還包含新的高級(jí)別的中間語(yǔ)言,稱為SIL
。SIL
會(huì)對(duì)Swift進(jìn)行較高級(jí)別的語(yǔ)義分析和優(yōu)化。 我們下面分析一下SIL
設(shè)計(jì)的動(dòng)機(jī)和SIL
的應(yīng)用漫拭,包括高級(jí)別的語(yǔ)義分析改览,診斷轉(zhuǎn)換下翎,去虛擬化,特化宝当,引用計(jì)數(shù)優(yōu)化视事,TBAA(Type Based Alias Analysis)等。并且會(huì)在某些流程中加入對(duì)SIL
和LLVM IR對(duì)比庆揩。
SIL
介紹
SIL
是為了實(shí)現(xiàn)swift編程語(yǔ)言而設(shè)計(jì)的,包含高級(jí)語(yǔ)義信息的SSA格式的中間語(yǔ)言.SIL
包含以下功能:
一系列的高級(jí)別優(yōu)化保障俐东,用于對(duì)運(yùn)行時(shí)和診斷行為提供可預(yù)測(cè)的底線
對(duì)swift語(yǔ)言數(shù)據(jù)流分析強(qiáng)制要求,對(duì)不滿足強(qiáng)制要求的問(wèn)題產(chǎn)生診斷订晌。例如變量和結(jié)構(gòu)體必須明確初始化虏辫,代碼可達(dá)性即方法return的檢測(cè),switch的覆蓋率
確保高級(jí)別優(yōu)化锈拨。包含retain/release優(yōu)化砌庄,動(dòng)態(tài)方法的去虛擬化(devirtualization,不了解虛函數(shù)可以查看之前文章static vs dynamic dispatch)奕枢,閉包內(nèi)聯(lián)娄昆,內(nèi)存初始化提升和泛型方法實(shí)例 化.
可用于分配"脆弱"內(nèi)聯(lián)的穩(wěn)定分配格式,將Swift庫(kù)組件的泛型優(yōu)化為二進(jìn)制缝彬。
和LLVM IR不同,SIL
一般是target無(wú)關(guān)的獨(dú)立格式的表示,可用于代碼分發(fā).但是也可以和LLVM一樣表達(dá)具體target概念. 如果想查看更多SIL
的實(shí)現(xiàn)和SIL
通道的開發(fā)信息,可以查看SIL開發(fā)手冊(cè)(原英文文檔為SILProgrammersManual.md)萌焰。
我們下面對(duì)Clang的Swift編譯器的傳遞流程進(jìn)行對(duì)比:
編譯流程對(duì)比
Clang編譯器流程
Clang編譯流程存在以下問(wèn)題:
在源碼和LLVM IR直接存在非常大的抽象鴻溝
IR不適用對(duì)源碼進(jìn)行分析和檢查 使用了Analysis通過(guò)CFG進(jìn)行分析,分析和代碼生成是兩部分
CFG(控制流圖)不夠精確
CFG不是主道(hot path)
在CFG和IR降級(jí)中會(huì)出現(xiàn)重復(fù)分析谷浅,做無(wú)用功
Swift編譯器流程
Swift作為一個(gè)高級(jí)別和安全的語(yǔ)言具有以下特點(diǎn):
高級(jí)別語(yǔ)言
通過(guò)代碼充分的展示語(yǔ)言的特性
支持基于協(xié)議的泛型
安全語(yǔ)言
充分的數(shù)據(jù)流檢查:未初始化變量扒俯,函數(shù)返回處理檢測(cè),這些項(xiàng)在檢測(cè)不合格時(shí)會(huì)產(chǎn)生對(duì)應(yīng)的編譯錯(cuò)誤
邊界和溢出的檢測(cè)
Swift編譯流程圖如下:
Swift編譯器提供的SIL
具有以下優(yōu)勢(shì):
對(duì)程序語(yǔ)義信息重復(fù)表示
可以用于代碼生成和分析 Clang不可以
處于編譯器的主道
可以連接源碼和LLVM的抽象鴻溝
SIL的設(shè)計(jì)
SIL流程分析
Swift編譯器作為高級(jí)編譯器一疯,具有以下嚴(yán)格的傳遞流程結(jié)構(gòu)陵珍。 Swift編譯器的流程如下
Parse: 語(yǔ)法分析組件從Swift源碼構(gòu)成AST
語(yǔ)義分析組件對(duì)AST進(jìn)行類型檢查,并對(duì)其進(jìn)行類型信息注釋违施。
SILGen組件從AST形成"生的(raw)"SIL
一系列在 生 SIL上運(yùn)行的,用于確定優(yōu)化和診斷合格,對(duì)不合格的代碼嵌入特定的語(yǔ)言診斷瑟幕。這些操作一定會(huì)執(zhí)行,即使在
-Onone
選項(xiàng)下也不例外磕蒲。之后產(chǎn)生 正式(canonical) SIL.一般情況下,是否在正式
SIL
上運(yùn)行SIL
優(yōu)化是可選的,這個(gè)檢測(cè)可以提升結(jié)果可執(zhí)行文件的性能.可以通過(guò)優(yōu)化級(jí)別來(lái)控制,在-Onone
模式下不會(huì)執(zhí)行.IRGen會(huì)將
正式SIL
降級(jí)為L(zhǎng)LVM 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
格式具有如下屬性:
屬性會(huì)被加載和存儲(chǔ)在可變內(nèi)存地址,而不是使用嚴(yán)格的SSA(靜態(tài)單賦值形式:每個(gè)變量?jī)H被賦值一次)只盹。這和Clang前端產(chǎn)生的繁重的LLVM IR(例如初始化
alloca
)類似辣往。但是Swift的變量在大多數(shù)情況下使用了引用計(jì)數(shù)器,使得變量可以被retained,release和被閉包引用。數(shù)據(jù)流檢測(cè)殖卑。例如明確的內(nèi)存分配,方法return檢查,switch覆蓋等.此環(huán)節(jié)目前不是強(qiáng)制執(zhí)行的
transparent
函數(shù)優(yōu)化目前未實(shí)現(xiàn).
這些特性會(huì)被接下來(lái)的確保優(yōu)化和診斷檢查使用站削,這兩項(xiàng)在 raw SIL
上一定會(huì)運(yùn)行。
確保優(yōu)化和診斷檢查
在SILGen
之后,會(huì)在raw SIL
上運(yùn)行確定順序的優(yōu)化孵稽。我們并不希望編譯器產(chǎn)生的診斷改變編譯器的進(jìn)展许起,所以這些優(yōu)化的設(shè)計(jì)是簡(jiǎn)單和可預(yù)測(cè).
-
Mandatory inlining: 強(qiáng)制內(nèi)聯(lián)對(duì)于transparent函數(shù)進(jìn)行內(nèi)聯(lián)十偶。
透明函數(shù)即,如果一個(gè)函數(shù)只會(huì)受到入?yún)⒌淖兓跋福敲催@個(gè)函數(shù)每次的調(diào)用都會(huì)是相同的惦积,同樣的入?yún)⒁欢〞?huì)返回一樣的返回值,在確定入?yún)⒌臅r(shí)候猛频,返回值是可預(yù)測(cè)的狮崩。這樣的函數(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
提升無(wú)暴露地址(
non_address-exposed
)的alloc_stack
說(shuō)明到SSA注冊(cè). 常數(shù)傳播: Constant propagation折疊常量表達(dá),繁殖常量值.如果在計(jì)算常量表達(dá)式時(shí)出現(xiàn)算術(shù)溢出,就會(huì)產(chǎn)生警告.
返回分析查證每個(gè)方法在每個(gè)代碼路徑只返回一個(gè)值,并且不會(huì)在定義的末端出現(xiàn)無(wú)返回值的錯(cuò)誤.如果不需要返回值的函數(shù)return了也會(huì)報(bào)錯(cuò).
臨界拆分: critical edge splitting不支持任意的基礎(chǔ)block參數(shù)通過(guò)終端進(jìn)行臨界拆分. 在 Advanced Compiler Design & Implementation的第13.3章節(jié),第407,408頁(yè)這樣描述臨界分裂
這個(gè)算法的核心作用體現(xiàn)為:流程圖中的臨界如果在流分析前被拆分的話,會(huì)使得運(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.
如果診斷通道完成后,會(huì)產(chǎn)生規(guī)范SIL.
泛型特化: Generic specialization
在
-Onone
模式下的ARC性能優(yōu)化.
說(shuō)完了處理raw SIL
的特定流程,我們對(duì)上面提到的優(yōu)化通道: optimization passes
進(jìn)行下說(shuō)明.
泛型優(yōu)化
SIL
獲取語(yǔ)言特定的類型信息,使得無(wú)法在LLVM IR實(shí)現(xiàn)的高級(jí)優(yōu)化在swift編譯器中得以實(shí)現(xiàn).
- 泛型特化分析泛型函數(shù)的特定調(diào)用,并生成新的特定版本的函數(shù).然后將泛型的特定用法全部重寫為對(duì)應(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ò)給定類型去查找關(guān)聯(lián)的類的虛函數(shù)表或者類型的witness表,并將虛函數(shù)調(diào)用替換為調(diào)用函數(shù)映射
性能內(nèi)聯(lián)
引用計(jì)數(shù)優(yōu)化
內(nèi)存提升/優(yōu)化
高級(jí)領(lǐng)域特定優(yōu)化swift編譯器對(duì)基礎(chǔ)的swift類型容器(類似Array或String)實(shí)現(xiàn)了高級(jí)優(yōu)化.領(lǐng)域特定優(yōu)化需要在標(biāo)準(zhǔn)庫(kù)和優(yōu)化器之間定義交互.詳情可以參考 :ref:
HighLevelSILOptimizations
SIL語(yǔ)法
SIL
依賴于swift的類型系統(tǒng)和聲明,所以SIL
語(yǔ)法是swift的延伸.一個(gè).sil
文件是一個(gè)增加了SIL
定義的swift源文件.swift源文件只會(huì)針對(duì)聲明進(jìn)行語(yǔ)法分析.swift的func
方法體(除了嵌套聲明)和最高階的代碼會(huì)被SIL
語(yǔ)法分析器忽略.在.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ù)體會(huì)被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 //冒號(hào)前為計(jì)算體實(shí)現(xiàn)通過(guò)引用的展開,冒號(hào)后為類型說(shuō)明
return %4 : Double //返回值
}
?
// 定義一個(gè)SIL虛函數(shù)表,匹配的是動(dòng)態(tài)分派中函數(shù)實(shí)現(xiàn)的id,這個(gè)動(dòng)態(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是通過(guò)
SILGen
產(chǎn)生的,并未經(jīng)過(guò)保證優(yōu)化或者診斷通道.Raw SIL
可能沒有完善結(jié)構(gòu)的SSA圖表.可能會(huì)包含數(shù)據(jù)流錯(cuò)誤.一些說(shuō)明可能會(huì)以非規(guī)范的方式展示,例如無(wú)地址的assign
和destory_addr
的數(shù)值.Raw SIL
不應(yīng)該用于本地代碼的生成或分發(fā).Canonical SIL,規(guī)范SIL是在保證優(yōu)化和診斷之后的
SIL
.數(shù)據(jù)流錯(cuò)誤必須被消除掉,肯定說(shuō)明也必須被規(guī)范化為更簡(jiǎn)單的形式.性能優(yōu)化和本地代碼是生成都是從這種格式衍生的.包含這種格式SIL
的組件可以被分發(fā).SIL
文件通過(guò)在頂部聲明sil_stage raw
或sil_stage canonical
來(lái)說(shuō)明當(dāng)前的操作階段.一個(gè)文件之后出現(xiàn)一種階段的聲明.
SIL類型
sil-type ::= '/pre> '*'? generic-parameter-list? type
SIL
的類型是通過(guò)$
符號(hào)進(jìn)行標(biāo)記的睦柴。SIL
的類型系統(tǒng)和swift的密切相關(guān).所以$
之后的類型會(huì)根據(jù)swift的類型語(yǔ)法進(jìn)行語(yǔ)法分析。
類型降級(jí): type lowering
swift的正式類型系統(tǒng),傾向于對(duì)大量的類型信息進(jìn)行抽象概括.但是SIL
目標(biāo)是展示更多的實(shí)現(xiàn)細(xì)節(jié),這個(gè)區(qū)別也體現(xiàn)在SIL
的類型系統(tǒng)中.所以把正式類型降級(jí)為較低類型的操作稱為類型降級(jí)毡熏。
提取區(qū)別:Abstraction Difference
包含未約束類型的通用函數(shù)一定會(huì)被非直接調(diào)用.比如分配充足內(nèi)存和創(chuàng)建地址指針指向這塊地址坦敌。如下的泛型函數(shù)
func generateArray<T>(n : Int, generator : () -> T) -> [T]
函數(shù)generator
會(huì)通過(guò)一個(gè)隱式指針,指向存儲(chǔ)在一個(gè)非直接調(diào)用的地址中,(可以參考之前static vs dynamic dispatch中虛函數(shù)表的設(shè)計(jì)和實(shí)現(xiàn)).在處理任意類型值時(shí)操作都是一樣的.
我們不希望對(duì)
generateArray
的每個(gè)T
的類型去產(chǎn)生一個(gè)新的拷貝我們不希望對(duì)每個(gè)類型進(jìn)行普遍聲明
-
我們不希望通過(guò)
T
的類型動(dòng)態(tài)的去構(gòu)造對(duì)于genetator
的調(diào)用但是我們也不希望現(xiàn)有的通用系統(tǒng)對(duì)我們的非通用代碼進(jìn)行低效處理。例如招刹,我們希望
()->Int
可以直接返回結(jié)果恬试。但是()->Int
是()->T
的代替(subsitution),對(duì)于generateArray<Int>
的調(diào)用應(yīng)該向generator傳遞()->Int
疯暑。 所以一個(gè)正式類型在通用上下文中的表現(xiàn)可能會(huì)因?yàn)檎筋愋偷牡拇娑煌?我們將這種不同成為提取區(qū)別.
SIL
對(duì)于類型的提取區(qū)別的設(shè)計(jì)是训柴,在每個(gè)級(jí)別的代替中,提取數(shù)值都可以被使用妇拯。
為了可以實(shí)現(xiàn)如上設(shè)計(jì),泛型實(shí)例的正式類型應(yīng)該一直使用非替換正式類型的提取方式進(jìn)行降級(jí).例如
struct Generator<T> {
var fn : () -> T
}
var intGen : Generator<Int>
其中intGen.fn
擁有代替類型()->Int
幻馁,可以被降級(jí)為@callee_owned () -> Int
,可以直接返回結(jié)果.但是如果更恰當(dāng)?shù)氖褂梅谴娣绞剑?code>()->T就會(huì)變成@callee_owned () -> @out Int
當(dāng)使用非代替的提取方式進(jìn)行類型降級(jí)時(shí)越锈,可以看做將擁有相同構(gòu)造的類型中的具體類型替換為現(xiàn)有類型,以此來(lái)實(shí)現(xiàn)類型降級(jí). 對(duì)于g
的Generator<(Int, Int) -> Float>
,g.fn
是使用()->T
進(jìn)行降級(jí)的仗嗦,簡(jiǎn)單理解就是,類型是否是具體類型,如果是,才能進(jìn)行提取方式進(jìn)行降級(jí),不然只能產(chǎn)生
@callee_owned () -> @owned @callee_owned (@in (Int, Int)) -> @out Float.
所以提取區(qū)別來(lái)代替通用函數(shù)中類型的標(biāo)準(zhǔn)是:是否是具體類型.is materializable or not
這個(gè)系統(tǒng)具有通過(guò)重復(fù)代替的方式實(shí)現(xiàn)提取方式的屬性.所以可以把降級(jí)的類型看做提取方式的編碼. SILGen
已經(jīng)擁有了使用提取方式轉(zhuǎn)換類型的工序. 目前只有函數(shù)和元祖類型會(huì)通過(guò)提取區(qū)別進(jìn)行改變.
合法的SIL類型
SIL
類型的值應(yīng)該是這樣的:
可被加載的SIL類型,
$T
合法SIL類型的地址
$*T
或者如果T
是一個(gè)合法的SIL類型需要滿足以下條件 不展開,需要查看SIL語(yǔ)法中的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
降級(jí)類型。
地址類型
地址類型$*T
指針指向的是任意引用的值或者$T
坯苹。
地址不是引用計(jì)數(shù)指針隆檀,不能被retained或released。
Box類型
本地變量和非直接的數(shù)值類型都是存儲(chǔ)在堆上的,@box T
是一個(gè)引用計(jì)數(shù)類型恐仑,指向的是包含了多種T
的盒子泉坐。盒子使用的是Swift的原生引用計(jì)數(shù)。
Metatype類型
SIL
內(nèi)的metatype類型必須描述自身表示:
-
@thin
意思是不需要內(nèi)存 -
@thick
指存儲(chǔ)的是類型的引用或類型子類的引用 -
@objc
指存儲(chǔ)的是一個(gè)OC類對(duì)象的表示而不是Swift類型對(duì)象菊霜。
函數(shù)類型
SIL
中的函數(shù)類型和Swift中的函數(shù)類型有以下區(qū)別:
SIL
函數(shù)可能是泛型坚冀。例如,通過(guò)function_ref
返回一個(gè)泛型函數(shù)類型鉴逞。SIL
函數(shù)可以聲明為@noescape
记某。@noescape
函數(shù)類型必須是convention(thin)
或者@callee_guatanteed
。-
SIL
函數(shù)類型聲明了以下幾種處理上下文的情景:-
@convention(thin)
不要求上下文构捡。這種類型也可以通過(guò)@noescape
聲明液南。 -
@callee_guatanteed
會(huì)被認(rèn)為直接參數(shù)。也意味著convention(thick)
勾徽。 -
@callee_owned
上下文值被認(rèn)為是不擁有的直接參數(shù)滑凉。也意味著convention(thick)
。 -
@convention(block)
上下文值被認(rèn)為是不擁有的直接參數(shù)喘帚。 - 其他函數(shù)類型會(huì)被描述為
Properties of Types
和Calling Convention
-
-
SIL
函數(shù)必須聲明參數(shù)的協(xié)議畅姊。非直接的參數(shù)類型是*T
,直接參數(shù)類型是T
-
@in
是非直接參數(shù)。地址必須是已經(jīng)初始化的對(duì)象吹由,函數(shù)負(fù)責(zé)銷毀內(nèi)部持有的值若未。 -
@inout
是非直接參數(shù)。內(nèi)存必須是已經(jīng)初始化的對(duì)象倾鲫。在函數(shù)返回之前粗合,必須保證內(nèi)存是被初始化的。
-
-
SIL
函數(shù)需要聲明返回值的協(xié)議乌昔。-
@out
是非直接的結(jié)果隙疚。地址必須是未初始化的對(duì)象。
-
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來(lái)表示類方法的動(dòng)態(tài)分派dynamic dispatch
class_method 和 super_method的實(shí)現(xiàn)是通過(guò)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
將通用類型動(dòng)態(tài)分派所需的信息編碼為witness表.這些信息用于在生成二進(jìn)制碼時(shí)產(chǎn)生運(yùn)行時(shí)分配表(runtime dispatch table).也可以用于對(duì)特定通用函數(shù)的SIL
優(yōu)化.每個(gè)明確的一致性聲明都會(huì)產(chǎn)生witness表.通用類型的所有實(shí)例共享一個(gè)通用witness表.衍生類會(huì)繼承基類的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é)議一致性.它是對(duì)于具體類型協(xié)議一致性的唯一標(biāo)識(shí).
標(biāo)準(zhǔn)的協(xié)議一致性命名了一種協(xié)議方法需要遵守的類型.屬于該類型或擴(kuò)展的組件,需要提供遵守協(xié)議方法的聲明
如果派生類實(shí)現(xiàn)了基類遵守的協(xié)議,會(huì)體現(xiàn)為繼承協(xié)議一致性,簡(jiǎn)單引用基類的協(xié)議一致性即可.
如果通用類型的實(shí)例遵守一個(gè)協(xié)議,是通過(guò)特定遵守的方式去實(shí)現(xiàn)的.這種方式向標(biāo)準(zhǔn)一致性提供了用于通用類型的通用參數(shù)構(gòu)建.
witness table
只會(huì)直接關(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)提供的對(duì)于協(xié)議一致性的引用,可以用于witness協(xié)議的繼承協(xié)議
方法項(xiàng)將協(xié)議中要求方法映射為
SIL
中實(shí)現(xiàn)了witness類型的方法.每個(gè)方法項(xiàng)必須對(duì)應(yīng)witness協(xié)議中的要求方法associate type關(guān)聯(lián)類型項(xiàng)將必須實(shí)現(xiàn)的協(xié)議方法中的關(guān)聯(lián)類型映射為符合witness的類型.注意witness類似是一個(gè)資源級(jí)別的swift類型,不是
SIL
類型(上面分析過(guò)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é)議是通過(guò)結(jié)構(gòu)體實(shí)現(xiàn)的,可以支持交互.例如參數(shù),屬性都可以是結(jié)構(gòu)體.當(dāng)將結(jié)構(gòu)體傳遞給協(xié)議參數(shù)時(shí),結(jié)構(gòu)體特定的部分可能會(huì)丟失(在編譯期).協(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),存儲(chǔ)在協(xié)議的元數(shù)據(jù)中. 默認(rèn)witness表關(guān)鍵在在自身協(xié)議.只有公共可見協(xié)議才需要默認(rèn)witness表.私有協(xié)議和內(nèi)部協(xié)議是對(duì)外部組件不可見的,所以他們沒有增加新的強(qiáng)制方法的開放性問(wèn)題.
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從語(yǔ)義上將那些條件定義為錯(cuò)誤,所以他們必須使用診斷通道進(jìn)行診斷,并且不能存在于規(guī)范SIL中. 定義初始化 swift要求所有的本地變量在使用前必須被初始化.在構(gòu)造函數(shù)中,結(jié)構(gòu)體,枚舉或類類型的實(shí)例變量必須在對(duì)象被使用前初始化. 未全面覆蓋(unreachable)的控制流 unreachable
在raw SIL
中生成,標(biāo)記錯(cuò)誤的控制流.例如對(duì)于非Void
的函數(shù)沒有返回值,或者switch
沒有完全覆蓋所有的條件.這種dead code
消解的保證,可以避免unreachable
的基礎(chǔ)block,也可以避免方法返回不合法的空類型.
運(yùn)行時(shí)錯(cuò)誤
一些操作,比如無(wú)條件的檢查轉(zhuǎn)換次數(shù)失敗或者編譯器Buildin.trap
.都會(huì)引起運(yùn)行時(shí)錯(cuò)誤,這種錯(cuò)誤會(huì)無(wú)條件的終止當(dāng)前操作.如果可以檢驗(yàn)運(yùn)行時(shí)錯(cuò)誤會(huì)發(fā)生或者已經(jīng)發(fā)生.只要將它們排列到程序操作之后就可以將這些運(yùn)行時(shí)錯(cuò)誤重新安排.例如對(duì)于沒有確定開始和結(jié)尾的for循環(huán)代碼
// Given unknown start and end values, this loop may overflow
for var i = unknownStartValue; i != unknownEndValue; ++i {
...
}
會(huì)將內(nèi)存溢出掛起,產(chǎn)生loop的關(guān)聯(lián)運(yùn)行時(shí)錯(cuò)誤,之后檢測(cè)循環(huán)的起始和結(jié)束點(diǎn).只要循環(huán)體對(duì)于當(dāng)前的操作沒有可見影響即可.
未定義的行為
某些操作的錯(cuò)誤使用成為未定義行為.例如對(duì)于Buildin.RawPointer
的不可用未檢測(cè)的類型轉(zhuǎn)換.或者使用低于LLVM說(shuō)明的編譯器內(nèi)建函數(shù),調(diào)用當(dāng)前LLVM不支持的行為.SIL
程序中的未定義行為是無(wú)意義的,就像C中的未定義行為一樣,沒有語(yǔ)義對(duì)其進(jìn)行預(yù)測(cè).未定義行為不應(yīng)該被合法的SIL文件觸發(fā),但是在SIL
級(jí)別不一定會(huì)被檢測(cè)和證實(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
說(shuō)明
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會(huì)在最后被傳入
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é)議,只有對(duì)通用類型參數(shù)處理方面不同.對(duì)于非witness方法來(lái)說(shuō),機(jī)器協(xié)議可能會(huì)通過(guò)方法類型將方法簽名進(jìn)行靜態(tài)轉(zhuǎn)換.但是因?yàn)閣itness必須在Self
類型下進(jìn)行多態(tài)分配,所以Self
相關(guān)的元數(shù)據(jù)必須通過(guò)最大化的提取規(guī)則傳輸.
C調(diào)用協(xié)議@convention(c) 在swift的C組件編譯器中,C類型會(huì)被SIL
對(duì)照到swift類型.C的函數(shù)入?yún)⒑头祷刂?都會(huì)被SIL
平臺(tái)調(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)
并不會(huì)影響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級(jí)別的將self
作為第一個(gè)參數(shù)的行為在SIL
中提取了的.比如現(xiàn)存的_cmd
方法參數(shù).
基于類型的別名分析: type based alias analysis
SIL
提供了兩種類型別名分析(TBAA
: Type Based Alias Analysis):類TBAA和類型訪問(wèn)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í)才能確定,編譯器必須動(dòng)態(tài)初始化內(nèi)存溺蕉。所以并不能確保內(nèi)存一定是初始化在棧區(qū)伶丐,例如如果是特別大的數(shù)值,可能會(huì)在堆區(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)存來(lái)支持各種類型的T
吃衅,以@box
持有引用計(jì)數(shù)往踢。這個(gè)指令的結(jié)果是@box
的引用計(jì)數(shù)持有的box,project_box
是要來(lái)過(guò)去box內(nèi)部的值的地址的徘层。
box初始化時(shí)引用計(jì)數(shù)為1峻呕,但是內(nèi)存并不會(huì)被初始化。box持有內(nèi)部的值趣效,在引用計(jì)數(shù)為0時(shí)使用destory_addr
對(duì)內(nèi)部值進(jìn)行釋放瘦癌,無(wú)法釋放box的值沒有被初始化的情況。這時(shí)候需要用到dealloc_box
跷敬。
總結(jié):alloc_box
在堆上初始化指針類型的值讯私,并且需要手動(dòng)管理內(nèi)存。
alloc_box和alloc_stack對(duì)比
alloc_box
和alloc_stack
最大的區(qū)別在于值的生命周期西傀。舉例斤寇,如果在閉包之外有一個(gè)變量聲明,在閉包內(nèi)使用了該變量拥褂。變量的值是可以被修改的娘锁,所以需要使用alloc_box
來(lái)引用變量。
對(duì)于var
聲明的變量肿仑,因?yàn)榭梢远啻涡薷乃闹抵旅耍踔猎谧饔糜蛲庖部梢孕薷摹K允褂?code>alloc_box管理引用計(jì)數(shù)尤慰。
優(yōu)化:Alloc box to stack
在SILGen階段馏锡,會(huì)對(duì)閉包內(nèi)使用變量的情況,通過(guò)alloc_box
進(jìn)行管理伟端。
在SIL guaranteed transformations階段杯道,即生成正式SIL的階段,會(huì)對(duì)于在閉包內(nèi)沒有進(jìn)行值修改的變量?jī)?nèi)存分配進(jìn)行優(yōu)化责蝠,將alloc_box
替換為alloc_stack
党巾。這個(gè)功能是在AllocBoxToStack組件內(nèi)實(shí)現(xiàn)的。內(nèi)部實(shí)現(xiàn)是將堆區(qū)不必要的初始化移動(dòng)到棧區(qū)霜医。