- 上一節(jié),我們完成了源碼編譯。本節(jié),我們
探索Swift的編譯流程和類結(jié)構(gòu)
- swift編譯流程
- 類結(jié)構(gòu)
強(qiáng)烈建議先閱讀LLVM入門,再開始本節(jié)的閱讀
1. swift編譯流程
在了解swift編譯流程前,我們需要知道LLVM是什么(?? LLVM入門)。
LLVM是架構(gòu)編譯器。可以為任何編程語言獨立編寫前端,也可以為任何硬件架構(gòu)獨立編寫后端。
image.png
比如:swift語言使用的前端編譯器是swiftc,而oc語言使用的前端編譯器是Clang,但它們都會將源代碼編譯為IR中間代碼,交給LLVM,而LLVM會輸出指定硬件架構(gòu)(如手機(jī)arm64、電腦x86_64)的.O 機(jī)器可執(zhí)行文件。
oc的clang編譯流程,我們在LLVM入門中分析得十分清楚。
- 現(xiàn)在,我們開始
swift的編譯全流程分析。
1.1 語法命令
- 打開
終端,輸入swiftc -h,查看語法命令:
-dump-ast 語法和類型檢查,打印AST語法樹
-dump-parse 語法檢查,打印AST語法樹
-dump-pcm 轉(zhuǎn)儲有關(guān)預(yù)編譯Clang模塊的調(diào)試信息
-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 輸出一個可執(zhí)行文件
-emit-imported-modules 展示導(dǎo)入的模塊列表
-emit-ir 展示IR中間代碼
-emit-library 輸出一個dylib動態(tài)庫
-emit-object 輸出一個.o機(jī)器文件
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen 輸出一個.sib的原始SIL文件
-emit-sib 輸出一個.sib的標(biāo)準(zhǔn)SIL文件
-emit-silgen 展示原始SIL文件
-emit-sil 展示標(biāo)準(zhǔn)的SIL文件
-index-file 為源文件生成索引數(shù)據(jù)
-parse 解析文件
-print-ast 解析文件并打?。ㄆ?簡潔的)語法樹
-resolve-imports 解析import導(dǎo)入的文件
-typecheck 檢查文件類型
- 新建一個
HTDemo的swift項目,新建HTPerson.swift文件,測試代碼:
class HTPerson {
var age: Int = 18
var name: String = "ht"
}
let t = HTPerson()
拓展命令:
- 將
打印結(jié)果,輸出為文檔的命令:命令語句后 + `>> ./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機(jī)器文件
swiftc -emit-object HTPerson.swift(ps:以上是各環(huán)節(jié)
關(guān)鍵命令,其他命令可自行嘗試和查看)
1.3 SIL分析
SIL(
Swift intermediate language):swift中間語言
調(diào)用swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil命令,生成HTPerson.sil文件。用VSCode打開SIL文件
1.3.1 main函數(shù)分析
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// 創(chuàng)建全局變量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() 函數(shù)地址給%5
%5 = function_ref @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %6
// 調(diào)用%5函數(shù),將返回值給%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
// 構(gòu)建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標(biāo)識當(dāng)前HTPerson.swift文件的入口函數(shù),SIL標(biāo)識符號名稱以@作為前綴%0,%1...在SIL中也叫寄存器,類似代碼中的常量,一旦賦值后不可修改。如果SIL中還要繼續(xù)使用,就需要使用新的寄存器
(此寄存器是虛擬的,只作為臨時標(biāo)識,最終運行到機(jī)器,會使用真的寄存器)
1.3.2 實例化分析
-
HTPerson()實際調(diào)用如下:
// 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()函數(shù)地址,給%2
%2 = function_ref @$s8HTPersonAACABycfc : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %3
// 調(diào)用`alloc_ref`創(chuàng)建一個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. 類結(jié)構(gòu)
對象初始化
- OC:
[[HTPerosn alloc] init]
一般alloc申請內(nèi)存空間并創(chuàng)建對象,init對象進(jìn)行統(tǒng)一初始化處理。- Swift:
HTPerson()
直接()就完成了對象的創(chuàng)建。
我們一起去探索Swift的對象創(chuàng)建流程:
2.1 對象的創(chuàng)建(alloc)
創(chuàng)建
測試代碼,實例化處加斷點:
image.png頂部
Debug->Debug workflow->Always Show Disassembly,勾選匯編模式:
image.png
運行代碼,可以看到是調(diào)用了__allocating_init()進(jìn)行的實例化。
image.png添加
__allocating_init符號斷點,繼續(xù)運行,發(fā)現(xiàn)內(nèi)部調(diào)用了swift_allocObject:
image.png添加
swift_allocObject符號斷點,繼續(xù)運行,發(fā)現(xiàn)內(nèi)部調(diào)用了_swift_allocObject_后,繼續(xù)調(diào)用swift_slowAlloc:
image.png添加
swift_slowAlloc符號斷點,繼續(xù)運行,發(fā)現(xiàn)內(nèi)部調(diào)用了malloc_zone_malloc進(jìn)行內(nèi)存申請:
image.png
- swift對象
創(chuàng)建流程:
__allocating_init->swift_allocObject->_swift_allocObject_->swift_slowAlloc->malloc_zone_malloc
VSCode調(diào)試
驗證:
- VSCode打開
Swift源碼,搜索_swift_allocObject_,加入斷點。
image.png
run起來后,在底部編輯區(qū),逐行輸入:
(ps:編輯器會判斷語法是否結(jié)束(花括號對稱),來判斷你輸入是否完成)class HTPerson { var age: Int = 18 var name: String = "ht" }再輸入
var p = HTPerson()創(chuàng)建變量,按回車,會進(jìn)入斷點:
image.png
- 進(jìn)入
swift_slowAlloc內(nèi)部,可以看到是申請了size大小的堆空間,并返回了空間的指針地址
image.png
這就是
swift的alloc,申請內(nèi)存空間并返回一個object指針。返回后,使用reinterpret_cast強(qiáng)轉(zhuǎn)為HeapObject類型。
// 堆中申請內(nèi)存空間,地址返回給object
auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));
reinterpret_cast:強(qiáng)制類型轉(zhuǎn)換符【用法】
new_type a = reinterpret_cast <new_type> (value)
- 將
value的值轉(zhuǎn)成new_type類型的值,a和value的值一模一樣,比特位不變。reinterpret_cast用在任意指針(或引用)類型之間的轉(zhuǎn)換,以及指針與足夠大的整數(shù)類型之間的轉(zhuǎn)換;從整數(shù)類型(包括枚舉類型)到指針類型,無視大小。
注意: 此時object是強(qiáng)轉(zhuǎn)的HeapObject類型,實際是一個指向內(nèi)存空間的對象指針,而HeapObject需要使用metadata進(jìn)行初始化
// object對象類型為HeapObject,通過metadata(元數(shù)據(jù))初始化
new (object) HeapObject(metadata);

2.2 類的大小
在探索init的初始化操作之前,我們先了解一下類的大小。
- 首先,通過
Xcode工程打印類的大?。?/li>
【總大小】
40字節(jié)(分別使用MemoryLayout和class_getInstanceSize打印大小)
image.png【age】:是
struct類型,64位系統(tǒng)下與Int64大小一樣。占8字節(jié)
image.png【name】: 是
struct類型,打印發(fā)現(xiàn),占16字節(jié)
- 其次,通過
VSCode編譯,查看大小:
image.png
Q:
OC類的大小,就是isa指針大小,8字節(jié),為什么Swift類是16字節(jié)?
- 最后,探索
Swift類的組成(16字節(jié)):
VSCode點擊進(jìn)入HeapObject結(jié)構(gòu):
image.png【metadata】:是
HeapMetedata類型,進(jìn)入探究:TargetHeapMetadata->TargetMetadata,是struct結(jié)構(gòu)體。
結(jié)構(gòu)體的大小,由內(nèi)部屬性決定,當(dāng)前TargetMetadata結(jié)構(gòu)體只有Kind一個屬性,類型為StoredPointer(本質(zhì)是unsigned long類型,8字節(jié))
image.png【refCounts】:是
InlineRefCounts類型,進(jìn)入探究:InlineRefCounts->RefCounts,是class類型,占8字節(jié)。swift也使用ARC進(jìn)行內(nèi)存管理。
image.png
HeapObject結(jié)構(gòu):
struct HeapObject { let metadata: UnsafeRawPointer // 8字節(jié) let strongRef: UInt32 // 4字節(jié) let weakRef: UInt32 // 4字節(jié) 【... 自定義屬性 ...】 // ... }
- 總結(jié):
swift類本質(zhì)是HeapObjectHeapObject默認(rèn)大小為16字節(jié):metadata(struct)8字節(jié)和refCounts(class)8字節(jié)HTPerson的age(Int)占8字節(jié),name(String)占16字節(jié)所以
HTPerson的size為40字節(jié)
2.3 對象的初始化(init)
- alloc
申請內(nèi)存空間并拿到空間地址后,通過metadata(元數(shù)據(jù))初始化HeapObject對象。

-
簡版流程圖:
image.png
swift類結(jié)構(gòu):
struct swift_class_t {
unsigned long Kind; // 8字節(jié) (swift中是Kind,OC中是isa)
void * Superclass; // 8字節(jié) 父類
void * CacheData[2]; // 16字節(jié) 緩存數(shù)據(jù)(2個元素)
void * Data ; // 8字節(jié) 自定義數(shù)據(jù)
uint32_t Flags; // 4字節(jié)
uint32_t InstanceAddressPoint; // 4字節(jié)
uint32_t InstanceSize; // 4字節(jié)
uint16_t InstanceAlignMask; // 2字節(jié)
uint16_t Reserved; // 2字節(jié)
uint32_t ClassSize; // 4字節(jié)
uint32_t ClassAddressPoint; // 4字節(jié)
void * Description; // 8字節(jié) 描述
}
- 附上
完整流程圖。(大圖,放大觀看)
image.png
本節(jié),熟悉了swift編譯流程和swift類結(jié)構(gòu),關(guān)于swift的探索,才剛剛起步 ??

















