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 的編譯流程:
與 Clang 相比, LLVM前端的流程中,在AST 和 IR之間,多了一層中間語言SIL (Swift Intermediate Language ) , 這么做的目的是希望彌補一些 Clang 編譯器的缺陷,如無法執行一些高級分析,可靠的診斷和優化,而 AST 和LLVM IR 都不是合適的選擇。因此,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>}*
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 位的整型
青山不改,綠水長流,后會有期,感謝每一位佳人的支持!