swift進(jìn)階二:編譯流程 & 類結(jié)構(gòu)探索

swift進(jìn)階 學(xué)習(xí)大綱

  • 上一節(jié),我們完成了源碼編譯。本節(jié),我們探索Swift編譯流程類結(jié)構(gòu)
  1. swift編譯流程
  2. 類結(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í)行文件

  • occlang編譯流程,我們在LLVM入門中分析得十分清楚。
  • 現(xiàn)在,我們開始swift編譯全流程分析。

可參考WWDC - 2015 LLVM視頻

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             檢查文件類型
  • 新建一個HTDemoswift項目,新建HTPerson.swift文件,測試代碼:
class HTPerson {
    var age: Int = 18
    var name: String = "ht"
}

let t = HTPerson()

拓展命令:

  1. 打印結(jié)果,輸出文檔命令
命令語句后 +  `>> ./XXX && open XXX`

// 命令語句: swiftc -emit-sil HTPerson.swift
// 輸出文件: >> ./HTPerson.sil
// 打開文件: open HTPerson.sil
例如: swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil && open HTPerson.sil
  1. 另一個相同命令
命令語句后 + ` | col -b > XXX`

// 命令語句為:`swiftc -print-ast HTPerson.swift`
// 輸出文檔為`ast.swift`
例如: swiftc -dump-ast HTPerson.swift | col -b > ast.swift

1.2 Swift編譯流程

image.png

Swift編譯流程

  1. swift源碼編譯為AST語法樹
    swiftc -dump-ast HTPerson.swift >> ast.swift
  2. 生成SIL源碼
    swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil
  3. 生成IR中間代碼
    swiftc -emit-ir HTPerson.swift >> ir.swift
  4. 輸出.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'
  1. @main標(biāo)識當(dāng)前HTPerson.swift文件的入口函數(shù),SIL標(biāo)識符號名稱以@作為前綴
  2. %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)試驗證

    1. VSCode打開Swift源碼,搜索_swift_allocObject_,加入斷點。
      image.png
    1. 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
  • 這就是swiftalloc,申請內(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類型avalue的值一模一樣,比特位不變。
  • 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);
image.png

2.2 類的大小

在探索init初始化操作之前,我們先了解一下類的大小

  • 首先,通過Xcode工程打印類的大?。?/li>

【總大小】40字節(jié) (分別使用MemoryLayoutclass_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é)
  1. swift類本質(zhì)是HeapObject
  2. HeapObject默認(rèn)大小為16字節(jié)metadata(struct)8字節(jié)refCounts(class)8字節(jié)
  3. HTPersonage(Int)占8字節(jié),name(String)占16字節(jié)

所以HTPersonsize40字節(jié)

2.3 對象的初始化(init)

  • alloc申請內(nèi)存空間并拿到空間地址后,通過metadata(元數(shù)據(jù))初始化HeapObject對象。
image.png
  • 簡版流程圖
    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的探索,才剛剛起步 ??

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

相關(guān)閱讀更多精彩內(nèi)容

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