Swift的高級(jí)中間語(yǔ)言:SIL

簡(jiǎn)介

在LLVM的官方文檔中對(duì)Swift的編譯器設(shè)計(jì)描述如下: Swift編程語(yǔ)言是在LLVM上構(gòu)建珍促,并且使用LLVM IR和LLVM的后端去生成代碼吩愧。但是Swift編譯器還包含新的高級(jí)別的中間語(yǔ)言,稱為SILSIL會(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編譯器流程

image

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編譯流程圖如下:

image

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ú)地址的assigndestory_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 rawsil_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ì)于gGenerator<(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 TypesCalling 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_methodsuper_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)的控制流 unreachableraw 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_boxalloc_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ū)霜医。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末齿拂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肴敛,更是在濱河造成了極大的恐慌署海,老刑警劉巖吗购,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異砸狞,居然都是意外死亡捻勉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門刀森,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)踱启,“玉大人,你說(shuō)我怎么就攤上這事研底〔撼ィ” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵飘哨,是天一觀的道長(zhǎng)胚想。 經(jīng)常有香客問(wèn)我,道長(zhǎng)芽隆,這世上最難降的妖魔是什么浊服? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮胚吁,結(jié)果婚禮上牙躺,老公的妹妹穿的比我還像新娘。我一直安慰自己腕扶,他們只是感情好孽拷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著半抱,像睡著了一般脓恕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窿侈,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天炼幔,我揣著相機(jī)與錄音,去河邊找鬼史简。 笑死乃秀,一個(gè)胖子當(dāng)著我的面吹牛弦疮,可吹牛的內(nèi)容都是我干的窑睁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嘁圈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼殉农!你這毒婦竟也來(lái)了刀脏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤超凳,失蹤者是張志新(化名)和其女友劉穎愈污,沒想到半個(gè)月后危队,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钙畔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了金麸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擎析。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挥下,靈堂內(nèi)的尸體忽然破棺而出揍魂,到底是詐尸還是另有隱情,我是刑警寧澤棚瘟,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布现斋,位于F島的核電站,受9級(jí)特大地震影響偎蘸,放射性物質(zhì)發(fā)生泄漏庄蹋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一迷雪、第九天 我趴在偏房一處隱蔽的房頂上張望限书。 院中可真熱鬧,春花似錦章咧、人聲如沸倦西。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扰柠。三九已至,卻和暖如春疼约,著一層夾襖步出監(jiān)牢的瞬間卤档,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工忆谓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裆装,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓倡缠,卻偏偏與公主長(zhǎng)得像哨免,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昙沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345