- 上一節,我們完成了源碼編譯。本節,我們
探索Swift
的編譯流程
和類結構
- swift編譯流程
- 類結構
強烈建議
先閱讀LLVM入門,再開始本節的閱讀
1. swift編譯流程
在了解swift編譯
流程前,我們需要知道LLVM
是什么(?? LLVM入門)。
LLVM
是架構編譯器
。可以為任何編程語言
獨立編寫前端
,也可以為任何硬件架構
獨立編寫后端
。
image.png
比如:swift語言
使用的前端編譯器
是swiftc
,而oc語言
使用的前端編譯器
是Clang
,但它們都會將源代碼
編譯為IR中間代碼
,交給LLVM
,而LLVM
會輸出指定硬件架構
(如手機arm64、電腦x86_64)的.O 機器可執行文件
。
oc
的clang
編譯流程,我們在LLVM入門中分析得十分清楚。
- 現在,我們開始
swift
的編譯全流程
分析。
1.1 語法命令
- 打開
終端
,輸入swiftc -h
,查看語法命令
:
-dump-ast 語法和類型檢查,打印AST語法樹
-dump-parse 語法檢查,打印AST語法樹
-dump-pcm 轉儲有關預編譯Clang模塊的調試信息
-dump-scope-maps <expanded-or-list-of-line:column>
Parse and type-check input file(s) and dump the scope map(s)
-dump-type-info Output YAML dump of fixed-size types from all imported modules
-dump-type-refinement-contexts
Type-check input file(s) and dump type refinement contexts(s)
-emit-assembly Emit assembly file(s) (-S)
-emit-bc 輸出一個LLVM的BC文件
-emit-executable 輸出一個可執行文件
-emit-imported-modules 展示導入的模塊列表
-emit-ir 展示IR中間代碼
-emit-library 輸出一個dylib動態庫
-emit-object 輸出一個.o機器文件
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen 輸出一個.sib的原始SIL文件
-emit-sib 輸出一個.sib的標準SIL文件
-emit-silgen 展示原始SIL文件
-emit-sil 展示標準的SIL文件
-index-file 為源文件生成索引數據
-parse 解析文件
-print-ast 解析文件并打?。ㄆ?簡潔的)語法樹
-resolve-imports 解析import導入的文件
-typecheck 檢查文件類型
- 新建一個
HTDemo
的swift
項目,新建HTPerson.swift
文件,測試代碼:
class HTPerson {
var age: Int = 18
var name: String = "ht"
}
let t = HTPerson()
拓展命令:
- 將
打印結果
,輸出
為文檔
的命令
:命令語句后 + `>> ./XXX && open XXX` // 命令語句: swiftc -emit-sil HTPerson.swift // 輸出文件: >> ./HTPerson.sil // 打開文件: open HTPerson.sil 例如: swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil && open HTPerson.sil
- 另一個相同
命令
:命令語句后 + ` | col -b > XXX` // 命令語句為:`swiftc -print-ast HTPerson.swift` // 輸出文檔為`ast.swift` 例如: swiftc -dump-ast HTPerson.swift | col -b > ast.swift
1.2 Swift編譯流程
Swift編譯流程
- 將
swift源碼
編譯為AST語法樹
swiftc -dump-ast HTPerson.swift >> ast.swift
- 生成
SIL
源碼
swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil
- 生成
IR
中間代碼
swiftc -emit-ir HTPerson.swift >> ir.swift
- 輸出
.o
機器文件
swiftc -emit-object HTPerson.swift
(ps:以上是各環節
關鍵命令
,其他命令
可自行嘗試
和查看
)
1.3 SIL分析
SIL(
Swift intermediate language
):swift中間語言
調用swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil
命令,生成HTPerson.sil
文件。用VSCode
打開SIL
文件
1.3.1 main
函數分析
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// 創建全局變量HTPerson
alloc_global @$s8HTPerson1tA2ACvp // id: %2
// 讀取全局變量HTPerson地址,賦值給%3
%3 = global_addr @$s8HTPerson1tA2ACvp : $*HTPerson // user: %7
// metatype讀取HTPerson的Type(Metadata),賦值給%4
%4 = metatype $@thick HTPerson.Type // user: %6
// 將HTPerson.__allocating_init() 函數地址給%5
%5 = function_ref @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %6
// 調用%5函數,將返回值給%6
%6 = apply %5(%4) : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %7
// 將%6存儲到%3 (%3是HTPerson類型)
store %6 to %3 : $*HTPerson // id: %7
// 構建Int并return
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} // end sil function 'main'
@main
標識當前HTPerson.swift
文件的入口函數
,SIL標識符號
名稱以@
作為前綴
%0
,%1
...在SIL
中也叫寄存器
,類似
代碼中的常量
,一旦賦值
后不可修改
。如果SIL
中還要繼續使用
,就需要
使用新
的寄存器
(此寄存器是虛擬
的,只作為臨時標識
,最終運行
到機器,會使用真
的寄存器
)
1.3.2 實例化
分析
-
HTPerson()
實際調用如下:
// HTPerson.__allocating_init()
sil hidden [exact_self_class] @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson {
// %0 "$metatype"
bb0(%0 : $@thick HTPerson.Type):
// 讀取HTPerson的`alloc_ref`方法地址,給%1
%1 = alloc_ref $HTPerson // user: %3
// 讀取HTPerson.init()函數地址,給%2
%2 = function_ref @$s8HTPersonAACABycfc : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %3
// 調用`alloc_ref`創建一個HTPerson實例對象,給%3
%3 = apply %2(%1) : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %4
// 返回%3(HTPerosn類型)
return %3 : $HTPerson // id: %4
} // end sil function '$s8HTPersonAACABycfC'
- 類似的
SIL分析
,可以多看
幾個實例
,慢慢找感覺
。
2. 類結構
對象初始化
- OC:
[[HTPerosn alloc] init]
一般alloc
申請內存空間并創建對象
,init
對象進行統一初始化
處理。- Swift:
HTPerson()
直接()
就完成
了對象的創建
。
我們一起去探索Swift
的對象創建
流程:
2.1 對象的創建(alloc)
創建
測試代碼
,實例化
處加斷點
:
image.png頂部
Debug
->Debug workflow
->Always Show Disassembly
,勾選匯編模式
:
image.png
運行
代碼,可以看到是調用了__allocating_init()
進行的實例化。
image.png添加
__allocating_init
符號斷點,繼續運行,發現內部調用了swift_allocObject
:
image.png添加
swift_allocObject
符號斷點,繼續運行,發現內部調用了_swift_allocObject_
后,繼續調用swift_slowAlloc
:
image.png添加
swift_slowAlloc
符號斷點,繼續運行,發現內部調用了malloc_zone_malloc
進行內存申請:
image.png
- swift對象
創建流程
:
__allocating_init
->swift_allocObject
->_swift_allocObject_
->swift_slowAlloc
->malloc_zone_malloc
VSCode調試
驗證
:
- VSCode打開
Swift
源碼,搜索_swift_allocObject_
,加入斷點
。
image.png
run
起來后,在底部編輯區
,逐行
輸入:
(ps:編輯器
會判斷語法
是否結束
(花括號對稱),來判斷
你輸入
是否完成
)class HTPerson { var age: Int = 18 var name: String = "ht" }
再輸入
var p = HTPerson()
創建變量,按回車,會進入斷點
:
image.png
- 進入
swift_slowAlloc
內部,可以看到是申請
了size
大小的堆空間
,并返回了空間的指針地址
image.png
這就是
swift
的alloc
,申請內存空間
并返回
一個object
指針。返回
后,使用reinterpret_cast
強轉為HeapObject
類型。
// 堆中申請內存空間,地址返回給object
auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));
reinterpret_cast
:強制類型轉換符【用法】
new_type a = reinterpret_cast <new_type> (value)
- 將
value
的值轉成new_type類型
的值
,a
和value
的值一模一樣
,比特位
不變。reinterpret_cast
用在任意指針
(或引用
)類型
之間的轉換
,以及指針
與足夠大
的整數類型
之間的轉換
;從整數類型
(包括枚舉類型
)到指針類型
,無視大小
。
注意: 此時object
是強轉的HeapObject
類型,實際是一個指向內存空間
的對象指針
,而HeapObject
需要使用metadata
進行初始化
// object對象類型為HeapObject,通過metadata(元數據)初始化
new (object) HeapObject(metadata);
2.2 類的大小
在探索init
的初始化
操作之前,我們先了解一下類的大小
。
- 首先,通過
Xcode
工程打印
類的大?。?/li>
【總大小】
40字節
(分別使用MemoryLayout
和class_getInstanceSize
打印大小)
image.png【age】:是
struct類型
,64位系統
下與Int64
大小一樣。占8字節
image.png【name】: 是
struct類型
,打印發現,占16字節
- 其次,通過
VSCode
編譯,查看大小
:
image.png
Q:
OC類
的大小
,就是isa指針
大小,8字節
,為什么Swift類
是16字節
?
- 最后,探索
Swift類
的組成(16字節
):
VSCode
點擊進入HeapObject
結構:
image.png【metadata】:是
HeapMetedata
類型,進入探究:TargetHeapMetadata
->TargetMetadata
,是struct
結構體。
結構體
的大小
,由內部屬性
決定,當前TargetMetadata
結構體只有Kind
一個屬性,類型為StoredPointer
(本質是unsigned long
類型,8字節
)
image.png【refCounts】:是
InlineRefCounts
類型,進入探究:InlineRefCounts
->RefCounts
,是class
類型,占8字節
。swift
也使用ARC
進行內存管理
。
image.png
HeapObject結構:
struct HeapObject { let metadata: UnsafeRawPointer // 8字節 let strongRef: UInt32 // 4字節 let weakRef: UInt32 // 4字節 【... 自定義屬性 ...】 // ... }
- 總結:
swift類
本質是HeapObject
HeapObject
默認大小為16字節
:metadata
(struct)8字節
和refCounts
(class)8字節
HTPerson
的age
(Int)占8字節
,name
(String)占16字節
所以
HTPerson
的size
為40字節
2.3 對象的初始化(init)
- alloc
申請
內存空間并拿到
空間地址
后,通過metadata
(元數據)初始化HeapObject
對象。
-
簡版流程圖
:
image.png
swift類結構
:
struct swift_class_t {
unsigned long Kind; // 8字節 (swift中是Kind,OC中是isa)
void * Superclass; // 8字節 父類
void * CacheData[2]; // 16字節 緩存數據(2個元素)
void * Data ; // 8字節 自定義數據
uint32_t Flags; // 4字節
uint32_t InstanceAddressPoint; // 4字節
uint32_t InstanceSize; // 4字節
uint16_t InstanceAlignMask; // 2字節
uint16_t Reserved; // 2字節
uint32_t ClassSize; // 4字節
uint32_t ClassAddressPoint; // 4字節
void * Description; // 8字節 描述
}
- 附上
完整流程圖
。(大圖,放大觀看)
image.png
本節,熟悉了swift編譯流程
和swift類結構
,關于swift的探索
,才剛剛起步
??