Swift的高級中間語言:SIL

簡介

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

image

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

image

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ī)范的方式展示,例如無地址的assigndestory_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 rawsil_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)類型降級. 對于gGenerator<(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 TypesCalling 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_methodsuper_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)的控制流 unreachableraw 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_boxalloc_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ū)。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容