Swift高級進階-Swift編譯過程,”SIL代碼“,“IR語法”


swift編譯過程

如果不懂LLVM,Clang的同學可以去了解下它的知識點  一些文章中有詳細介紹 OC 的編譯過程 ,本文來探索一下 Swift 的編譯過程。Swift 的編譯過程中使用 Swiftc ,與 Clang 一樣,Swiftc 是LLVM編譯架構的一個前端。

swiftc常用命令:

-dump-ast              解析和類型檢查源文件 & 轉換成 AST
-dump-parse            解析源文件 & 轉換成 AST  
  -emit-assembly         生成匯編文件
  -emit-bc               生成 LLVM Bitcode 文件
  -emit-executable       生成已鏈接的可執行文件
  -emit-imported-modules 生成已導入的庫
  -emit-ir               生成 LLVM IR 文件
  -emit-library          生成已連接的庫
  -emit-object           生成目標文件
  -emit-silgen           生成 raw SIL 文件(第一個階段)
  -emit-sil              生成 canonical SIL 文件(第2個階段)
  -index-file            為源文件生成索引數據
  -print-ast             解析和類型檢查源文件 & 轉換成更簡約的格式更好的 AST
  -typecheck             解析和類型檢查源文件

swift 的編譯流程:

image.png

與 Clang 相比, LLVM前端的流程中,在AST 和 IR之間,多了一層中間語言SIL (Swift Intermediate Language ) , 這么做的目的是希望彌補一些 Clang 編譯器的缺陷,如無法執行一些高級分析,可靠的診斷和優化,而 AST 和LLVM IR 都不是合適的選擇。因此,SIL應運而生,用來解決現有的缺陷。

SIL代碼

SIL 官方文檔傳送門

由於 SIL 是 Swift 在編譯過程中的中間產物, 通過 SIL 可以了解swift 底層的實現細節,幫助我們理解一些問題)

源碼生成 SIL 的命令如下:

// 將m ain.swift 編譯成 SIL 代碼
swiftc -emit-sil main.swift 

// 將 main.swift 編譯成 SIL,並保存到 main.sil 文件中
swiftc -emit-sil main.swift >> main.sil

// 將 main.swift 編譯成 SIL的同時, 將命名重整后的符號恢復原樣,並保存到 main.sil 文件中
swiftc -emit-sil main.swift | xcrun swift-demangle >> main.sil


SIL 語法和 IR 語法有點相似,常見語法含義:

  • load: 讀取數據
  • sil_global:標記變量為全局變量 
  • hidden: 標記只針對同一個Swift模塊中的對象可見 
  • alloc_global:  開辟全局變量的內存 
  • global_addr: 獲取全局變量的地址 
  • ref_element_addr: 獲取元素地址
  • init_existential_addr: 指令會生成 Existential Container 結構, 包裹着實例變量和協議對應的 PWT
  • destroy_addr
  • bb0 / bb1 ... : basic block 數字,表示一個代碼塊,SIL中沒有分支語句,只有入口和出口
  • alloc_ref / dealloc_ref: 開辟/釋放內存
  • function_ref: 獲取直接派發函數地址. 
  • class_method: 通過函數表獲取方法. 
  • witness_method: 通過 PWT 獲取對應的函數地址
  • objc_method : 獲取OC 方法地址
  • apply:調用函數 
  • store A to B : 把A 的值存儲到B中。
  • begin_access / end_access: 開始、結束訪問
    • [modify] / [read] / [deinit] :修改型訪問、讀取型訪問、刪除型訪問
    • [dynamic]:動態訪問
    • [static]:靜態訪問
  • retain_value: 引用計數 + 1 
  • release_value: 引用計數 - 1 
  • metatype 獲得元類型
    • @thick 描述元類型代表的形式,是引用 對象類型或是其子類, 
    • @thin 代表一個確切的值 類型,不需要存儲, 
  • $ : 類型標識 
  • %:  表示寄存器,類似局部常量,賦值后不可修改。如果再需要新的寄存器,就增加寄存器編號,這樣操作有利於編譯器的優化;后續進行降級操作 時,才會把這些帶編號的虛擬寄存器 轉換成對應體系結構的真實寄存器。
  • @ : SIL中所有標識符均以@符號開頭
    • @main 方法名字是 main
    • @_hasStorage 標識屬性是存儲屬性
    • @_hasInitialValue 標識屬性有初始值
    • @owned 代表函數接收者負責銷毀返回值
    • @convention 這個標識用於明確指定當函數調用時參數和返回值應該如何被處理
      • @convention(c) 表示使用C函數的方式進行調用
      • @convention(swift) 純Swift函數的默認調用方式 
      • @convention(method) 柯里化的函數調用方式 
      • @convention(witness_method) 協議方法調用,它等同於convention(method),除了在處理范型類型參數時 
      • @convention(objc_method) Objective-C方式調用

常見ARM64匯編指令

bl : 地址跳轉

blr : 帶返回的地址跳轉, 跳轉回指令后面跟隨寄存器中保存的地址

mov: 把一個寄存器里的值,復制到另一個寄存器

🌰  mov x0, x8 把 x0 的值,復制到 x8 中

ldr: 把內存中的值,讀取到寄存器中

🌰  ldr x0, [x0, x8]  把 x0 + x8 的地址里面的值,寫到 x0 寄存器 中

str: 把寄存器里面的值寫入到內存中

🌰  str x0, [x0, x8]  把寄存器 x0 的值,寫入到 x0 + x8 的地址中

4. 常見的 IR 語法:

官方文檔傳送門☞官方文檔

  • @    全局標識
  • %       局部標識
  • alloca  開辟空間
  • align  內存對齊
  • i32 32位, 4字節
  • store  寫入內存
  • load  讀取數據
  • call  調用函數
  • ret  返回

bitcast 

以新的數據類型去讀取原類的值(也就是類型轉換)


//以ty2 的數據類型 去讀取 ty 類型的 value,讀取步長是ty2
<result> = bitcast <ty> <value> to <ty2>
// eg: bitcast (%struct.str* @global to i8*)
// 以i8* 的數據類型 去讀取 struct.str* 類型的 global,讀取步長是i8*

getelementptr

  • 指令(GER):進行地址計算,來獲取復合數據結構子元素的地址,並不訪問內存。
    • 至少要有2個參數
    • 第一個參數是類型 & 地址,也就是開始讀取內存的變量首地址
    • 第二個及以后的參數,表示要進行計算的參數,比如結構體或者數據的第幾個元素
// 獲取復合數據類型的指定元素的地址
getelementptr <ty> <value>, <index>, <index>, ...

如果 p 是一個數組, 第二個參數表示獲取數組里索引是1的值,也就是p[1]

getelementptr %struct.munger_struct* %P, i32 1

如果 p 是一個數組,里面放了幾個結構體, 第二個參數依舊表示p[1],第3個參數表示獲取數組索引為1的結構體的索引為0的元素,即p[1][0]

getelementptr %struct.munger_struct* %P, i32 1, i32 0

如果結構體內還有數據類型的嵌套,再獲取其內部嵌套的值,可以繼續追加參數, 所以說參數至少有2個,第一個是數據的首地址,第二個參數開始都表示索引值。

官網:


struct munger_struct {
  int f1;
  int f2;
};
void munge(struct munger_struct *P) {
  P[0].f1 = P[1].f1 + P[2].f2;
}
...
munger_struct Array[3];
...
munge(Array);

轉成IR代碼:

void %munge(%struct.munger_struct* %P) {
entry:
  %tmp = getelementptr %struct.munger_struct* %P, i32 1, i32 0
  %tmp = load i32* %tmp
  %tmp6 = getelementptr %struct.munger_struct* %P, i32 2, i32 1
  %tmp7 = load i32* %tmp6
  %tmp8 = add i32 %tmp7, %tmp
  %tmp9 = getelementptr %struct.munger_struct* %P, i32 0, i32 0
  store i32 %tmp8, i32* %tmp9
  ret void
}


getelementptr inbounds: 

  • 與 getelementptr 類似, 不過它將類型與值分開了,分別作為2個參數。所以這個指令,至少有3個參數,前兩個是類型和值,第三個開始是索引參數

<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*

image-2.png

extractvalue

// 獲取復合數據類型的指定元素的值
<result> = extractvalue <aggregate type> <val>, <idx>{, <idx>}*

insertvalue

// 像復合類型中的指定索引位置插入值
<result> = insertvalue <aggregate type> <val>, <ty> <elt>, <idx>{, <idx>}* ; 
// insertvalue %struct.tm %ret_val.fca.0.insert, i32 %b1, 1

// 向類型為%struct.tm的%ret_val.fca.0.insert變量的索引為 1 的位置,插入%b1

結構體

%T = type {<type list>}
// eg: %swift.refcounted = type { %swift.type*, i64 }

數組


[<elementnumber> x <elementtype>]
// eg: alloca[24 x i8], align8 表示數組里面放了24個 i8 的整數

指針

<type> *
//eg: i64* 表示64 位的整型

青山不改,綠水長流,后會有期,感謝每一位佳人的支持!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM