Swift編譯流程 & Swift類

前言

本篇文章會(huì)大致分析一下swift的編譯流程,這個(gè)作為了解即可,然后會(huì)重點(diǎn)分析一下swift中類的結(jié)構(gòu),這個(gè)知識(shí)點(diǎn)我們需要掌握。

一、swift編譯流程

1.1 LLVM

在了解swift編譯流程之前,我們最好清楚LLVM是什么?請(qǐng)參考??LLVM編譯流程

LLVM是架構(gòu)編譯器的框架系統(tǒng),以C++編寫而成,用于優(yōu)化任意程序語言編寫的程序的編譯時(shí)間(compile-time)、鏈接時(shí)間(link-time)、運(yùn)行時(shí)間(run-time)以及空閑時(shí)間(idle-time)。對(duì)開發(fā)者保持開放,并兼容已有腳本。

對(duì)于我們iOS系統(tǒng),OC語言前端使用Clang編譯器,而swift語言前端使用swift編譯器,這兩個(gè)編譯器將我們寫的代碼編譯生成IR中間代碼,交給LLVM優(yōu)化器進(jìn)行優(yōu)化,接著交給代碼生成器生成機(jī)器語言,最終形成.o機(jī)器執(zhí)行文件。整個(gè)過程如下圖??

1.2 swift的編譯詳細(xì)流程

接著我們?cè)僭敿?xì)看看swift的編譯流程,如下圖??


大致分為以下幾步??

  1. swift源碼經(jīng)過parse解析、ast編譯,生成AST語法樹 。相關(guān)指令??

swiftc -dump-ast LGPerson.swift >> ast.swift

  1. 通過SIL生成器生成SIL源碼。相關(guān)指令??

swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil

  1. 生成IR中間代碼。相關(guān)指令??

swiftc -emit-ir LGPerson.swift >> ir.swift

  1. 輸出.o機(jī)器文件。相關(guān)指令??

swiftc -emit-object LGPerson.swift

1.3 示例查看編譯流程

在示例查看之前,我們可以在【終端】中swiftc -h查看swiftc的所有相關(guān)指令??

常用的一些指令的含義??

  -dump-ast             語法和類型檢查,打印AST語法樹
  -dump-parse           語法檢查,打印AST語法樹
  -dump-pcm             轉(zhuǎn)儲(chǔ)有關(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               輸出一個(gè)LLVM的BC文件
  -emit-executable       輸出一個(gè)可執(zhí)行文件
  -emit-imported-modules 展示導(dǎo)入的模塊列表
  -emit-ir               展示IR中間代碼
  -emit-library          輸出一個(gè)dylib動(dòng)態(tài)庫
  -emit-object           輸出一個(gè).o機(jī)器文件
  -emit-pcm              Emit a precompiled Clang module from a module map
  -emit-sibgen           輸出一個(gè).sib的原始SIL文件
  -emit-sib              輸出一個(gè).sib的標(biāo)準(zhǔn)SIL文件
  -emit-silgen           展示原始SIL文件
  -emit-sil              展示標(biāo)準(zhǔn)的SIL文件
  -index-file            為源文件生成索引數(shù)據(jù)
  -parse                 解析文件
  -print-ast             解析文件并打印(漂亮/簡(jiǎn)潔的)語法樹
  -resolve-imports       解析import導(dǎo)入的文件
  -typecheck             檢查文件類型

首先,我們創(chuàng)建一個(gè)swift demo項(xiàng)目,定義一個(gè)類LGPerson.swift類

class LGPerson {
    var age: Int = 18
    var name: String = "luoji"
}

let t = LGPerson()

接著,打開【終端】,進(jìn)入到項(xiàng)目的目錄

  • 查看抽象語法樹:swiftc -dump-ast LGPerson.swift ??
  • 生成SIL文件:swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil


可以使用VSCode打開它看看??代碼量巨多

附:可以在.zshrc中做了如下配置,這樣就能在終端中指定軟件打開相應(yīng)文件

// 打開.zshrc,如果提示打不開,則使用touch .zshrc 創(chuàng)建一個(gè)
$ open .zshrc

// 添加以下別名
alias vscode='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'

// 如果之前是創(chuàng)建的,那么還需執(zhí)行
source .zshrc

//使用
$ swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil && vscode LGPerson.sil

  • 如果想SIL文件高亮,需要安裝插件:VSCode SIL??

再次打開LGPerson.sil?? 沒有那么全白了

觀察LGPerson.sil代碼,可以發(fā)現(xiàn)很多都是經(jīng)過混淆處理的,可以通過下面的指令反混淆

xcrun swift-demangle 你的混淆的代碼

1.4 SIL分析

SIL: Swift intermediate language --> swift中間語言

1.4.1 main函數(shù)入口

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s8LGPerson1tA2ACvp               // id: %2
  %3 = global_addr @$s8LGPerson1tA2ACvp : $*LGPerson // user: %7
  %4 = metatype $@thick LGPerson.Type             // user: %6
  // function_ref LGPerson.__allocating_init()
  %5 = function_ref @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson // user: %7
  store %6 to %3 : $*LGPerson                     // id: %7
  %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)識(shí)當(dāng)前LGPerson.swift文件的入口函數(shù),SIL標(biāo)識(shí)符號(hào)名稱以@作為前綴。
  2. %0,%1...在SIL中也叫寄存器,類似代碼中的常量,一旦賦值后不可修改。如果SIL中還要繼續(xù)使用,就需要使用新的寄存器。

我們一句句的看

  • alloc_global @$s8LGPerson1tA2ACvp
    s8LGPerson1tA2ACvp反混淆出來就是LGPerson,那么就是創(chuàng)建全局變量LGPerson
  • %3 = global_addr @$s8LGPerson1tA2ACvp : $*LGPerson 這句很簡(jiǎn)單,讀取全局變量LGPerson地址,賦值給%3
  • %4 = metatype $@thick LGPerson.Type讀取LGPerson的Type,賦值給%4
  • %5 = function_ref @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson 定義一個(gè)function_ref即函數(shù),就是%5,這個(gè)函數(shù)入?yún)⑹荓GPerson.Type
  • %6 = apply %5(%4) : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson apply調(diào)用函數(shù)%5,入?yún)⒕褪?4,將返回結(jié)果賦給%6
  • store %6 to %3 : $*LGPerson 將%6的結(jié)果存儲(chǔ)到%3,%3是LGPerson的地址
  • %8 = integer_literal $Builtin.Int32, 0%9 = struct $Int32 (%8 : $Builtin.Int32)就是構(gòu)建一個(gè)Int值,最終返回return %9 : $Int32

綜上所述,main函數(shù)就是構(gòu)建了一個(gè)全局變量LGPerson,并對(duì)其Type值做了一個(gè)返回處理,那么,這個(gè)Type代表什么意思呢?后面我們會(huì)仔細(xì)分析swift類的底層結(jié)構(gòu)。

1.4.2 實(shí)例化相關(guān)代碼

// LGPerson.__allocating_init()
sil hidden [exact_self_class] @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson {
// %0 "$metatype"
bb0(%0 : $@thick LGPerson.Type):
  %1 = alloc_ref $LGPerson                        // user: %3
  // function_ref LGPerson.init()
  %2 = function_ref @$s8LGPersonAACABycfc : $@convention(method) (@owned LGPerson) -> @owned LGPerson // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned LGPerson) -> @owned LGPerson // user: %4
  return %3 : $LGPerson                           // id: %4
} // end sil function '$s8LGPersonAACABycfC'

同樣一句句的看

  • %1 = alloc_ref $LGPerson 讀取LGPerson的alloc_ref方法地址,給%1
  • %2 = function_ref @$s8LGPersonAACABycfc : $@convention(method) (@owned LGPerson) -> @owned LGPerson 讀取LGPerson.init()函數(shù)地址,給%2
  • %3 = apply %2(%1) : $@convention(method) (@owned LGPerson) -> @owned LGPerson 調(diào)用alloc_ref創(chuàng)建一個(gè)LGPerson實(shí)例對(duì)象,給%3
  • return %3 : $LGPerson返回%3的實(shí)例對(duì)象

這個(gè)__allocating_init()也不難,其實(shí)就是一個(gè)簡(jiǎn)單的方法調(diào)用過程。對(duì)于這個(gè)SIL代碼,我們平時(shí)可以自己多看多分析,習(xí)慣就好了,哈哈!

二、swift類的結(jié)構(gòu)

帶著上面的問題??LGPerson中的Type 就是存儲(chǔ)的是什么值,代表什么意思?我們現(xiàn)在來看看swift的類class在底層中的結(jié)構(gòu)是什么樣的。

大家都知道,如果要?jiǎng)?chuàng)建一個(gè)對(duì)象,OC 與 swift的寫法是這樣的??

  • OC: [[LGPerosn alloc] init]
    一般alloc申請(qǐng)內(nèi)存空間并創(chuàng)建對(duì)象,init對(duì)其進(jìn)行統(tǒng)一初始化處理。
  • Swift:LGPerson()
    直接()就完成了對(duì)象的創(chuàng)建。

2.1 找入口

我們現(xiàn)在就來看看LGPerson()在匯編層是調(diào)用了哪些函數(shù)?
新建一個(gè)SwiftDemo項(xiàng)目工程,在ViewController.swift中添加下面的代碼,并打上斷點(diǎn)??

運(yùn)行項(xiàng)目,查看匯編??

我們找到了對(duì)象的初始化入口??函數(shù)__allocating_init(),接著我們?cè)?code>__allocating_init()這一行打上斷點(diǎn),按住control按鍵 stepinto進(jìn)入查看??

定位到了swift_allocObject,同理還是在swift_allocObject這行加上斷點(diǎn),stepinto進(jìn)入查看??

沒找到實(shí)用的信息,那么換種思路,添加符號(hào)斷點(diǎn)swift_allocObject,再次run??

接著,添加符號(hào)斷點(diǎn)swift_slowAlloc??

最終來到了比較熟悉的malloc_zone_malloc。

至此,swift實(shí)例對(duì)象的創(chuàng)建流程??
__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc_zone_malloc

2.2 查看源碼驗(yàn)證流程

首先,用VSCode打開swift源碼項(xiàng)目,搜索__allocating_init,打上斷點(diǎn)??

接著,run項(xiàng)目,在終端輸入代碼??

class LGPerson {
       var age: Int = 18
       var name: String = "luoji"
}

回車,再輸入var t = LGPerson(),會(huì)觸發(fā)斷點(diǎn)??

我們可以看左上角,發(fā)現(xiàn)requiredSize空間大小值是40,requiredAlignmentMask字節(jié)對(duì)齊值是7。

接著我們順著流程來到swift_slowAlloc??

然后返回申請(qǐng)空間后的地址,即返回指針p。再回到_swift_allocObject_,那么接著執(zhí)行

  • auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));
    通過reinterpret_cast將指針p轉(zhuǎn)換成HeapObject類型,然后執(zhí)行
  • new (object) HeapObject(metadata);
    因?yàn)閛bject是強(qiáng)轉(zhuǎn)的HeapObject類型,其里面值仍是一個(gè)指向內(nèi)存空間的對(duì)象指針,所以這里執(zhí)行HeapObject(metadata)做一個(gè)初始化操作,這樣object就是真正的HeapObject結(jié)構(gòu)了。

整體流程圖??

2.2.1 類的大小

接下來我們具體看看,類的大小size是怎么算出來的?
首先,我們先在lldb中打印查看一下大小,打開Xcode,新建一個(gè)swift項(xiàng)目,加入代碼

import UIKit

class LGPerson {
    var age: Int = 18
    var name: String = "luoji"
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
                
        print("Int32 大小: \(MemoryLayout<Int32>.size)")
        print("Int64 大小: \(MemoryLayout<Int64>.size)")
        print("Int 大小: \(MemoryLayout<Int>.size)")
        print("Srtring 大小: \(MemoryLayout<String>.size)")
        
        print("LGPerson 大小: \(class_getInstanceSize(LGPerson.self))")
        
    }
}

run??

我們通過MemoryLayout得知Int大小是8,String大小是16,但是通過class_getInstanceSize得知類LGPerson的大小卻是40,40-16-8=16,這額外的16字節(jié)大小是什么?

之前我們?cè)谠创a調(diào)試的時(shí)候,也發(fā)現(xiàn)類LGPerson初始化實(shí)例對(duì)象時(shí)的requiredSize空間大小值也是40,如下圖??

額外的16字節(jié)從何而來?

我們進(jìn)入源碼工程,查看HeapObject結(jié)構(gòu)體??

結(jié)構(gòu)體大小主要看成員變量,HeapObject結(jié)構(gòu)體有兩個(gè)成員變量元數(shù)據(jù)metadata引用計(jì)數(shù)refCounts。

metadata

是 HeapMetedata類型,還是查看源碼??

template <typename Target> struct TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};

TargetHeapMetadata->TargetMetadata,也是struct結(jié)構(gòu)體。接著看TargetMetadata??

最后我們看看StoredPointer源碼,發(fā)現(xiàn)搜不到,但是根據(jù)注釋

  /// The kind. Only valid for non-class metadata; getKind() must be used to get
  /// the kind value.
  StoredPointer Kind;

我們發(fā)現(xiàn), 可以通過 getKind()方法入?yún)⒅??

  /// Get the metadata kind.
  MetadataKind getKind() const {
    return getEnumeratedMetadataKind(Kind);
  }

/// Try to translate the 'isa' value of a type/heap metadata into a value
/// of the MetadataKind enum.
inline MetadataKind getEnumeratedMetadataKind(uint64_t kind) {
  if (kind > LastEnumeratedMetadataKind)
    return MetadataKind::Class;
  return MetadataKind(kind);
}

getKind是調(diào)用getEnumeratedMetadataKind,而入?yún)?code>kind是uint64_t類型,占8字節(jié)大小,所以元數(shù)據(jù)metadata8字節(jié)大小

MetadataKind

這時(shí)我們注意到,getKind()返回值是MetadataKind類型,我們進(jìn)入源碼看看,里面有一個(gè)#include "MetadataKind.def",點(diǎn)擊進(jìn)入,其中記錄了所有類型的元數(shù)據(jù),所以kind種類總結(jié)如下:

name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

接著我們回到TargetMetaData結(jié)構(gòu)體定義中,找方法getClassObject,在該方法中去匹配kind返回值是TargetClassMetadata類型??

如果是Class,則直接對(duì)this(當(dāng)前指針,即metadata)強(qiáng)轉(zhuǎn)ClassMetadata。所以,TargetMetadataTargetClassMetadata 本質(zhì)上是一樣的,因?yàn)樵趦?nèi)存結(jié)構(gòu)中,可以直接進(jìn)行指針的轉(zhuǎn)換,那么我們可以這么認(rèn)為??結(jié)構(gòu)體其實(shí)就是TargetClassMetadata。

接著,我們?cè)倏纯?code>TargetClassMetadata里還有哪些成員??

template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
    ...
    //swift特有的標(biāo)志
    ClassFlags Flags;
    //實(shí)力對(duì)象內(nèi)存大小
    uint32_t InstanceSize;
    //實(shí)例對(duì)象內(nèi)存對(duì)齊方式
    uint16_t InstanceAlignMask;
    //運(yùn)行時(shí)保留字段
    uint16_t Reserved;
    //類的內(nèi)存大小
    uint32_t ClassSize;
    //類的內(nèi)存首地址
    uint32_t ClassAddressPoint;
  ...
}

然后我們看看繼承鏈?? TargetClassMetadata繼承TargetAnyClassMetadata繼承TargetHeapMetadata。

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
    ...
    ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
    TargetPointer<Runtime, void> CacheData[2];
    StoredSize Data;
    ...
}

至此,當(dāng)metadata的kind為Class時(shí),有如下繼承鏈:

  • 類class在底層中的實(shí)際類型是TargetClassMetadata,而TargetMetaData中只有一個(gè)屬性kindTargetAnyClassMetaData中有4個(gè)屬性,分別是kind, superclass,cacheData、data(圖中未標(biāo)出)
  • 當(dāng)前Class在內(nèi)存中所存放的屬性由 TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性 構(gòu)成,所以得出的metadata的數(shù)據(jù)結(jié)構(gòu)體如下所示
struct swift_class_t: NSObject{
    void *kind;//相當(dāng)于OC中的isa,kind的實(shí)際類型是unsigned long
    void *superClass;
    void *cacheData;
    void *data;
    uint32_t flags; //4字節(jié)
    uint32_t instanceAddressOffset;//4字節(jié)
    uint32_t instanceSize;//4字節(jié)
    uint16_t instanceAlignMask;//2字節(jié)
    uint16_t reserved;//2字節(jié)
    
    uint32_t classSize;//4字節(jié)
    uint32_t classAddressOffset;//4字節(jié)
    void *description;
    ...
}

refCounts

接著我們看看refCountsrefCountsInlineRefCounts類型,搜索InlineRefCounts??

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

InlineRefCounts是RefCounts類型,搜索??

RefCounts是class類型,確切的說,refCounts是個(gè)指針,占8字節(jié)大小。

綜上所述

  1. swift類本質(zhì)是HeapObject
  2. HeapObject默認(rèn)大小為16字節(jié): metadata(struct)8字節(jié)和refCounts(class)8字節(jié)
  3. LGPerson的age(Int)占8字節(jié),name(String)占16字節(jié),加上上面的,所以LGPersonsize為40字節(jié)

總結(jié)

本篇文章首先大致講述了Swift的編譯流程,與OC最大的不同在于,swift在編譯過程中會(huì)生成SIL中間代碼,通過對(duì)中間代碼的分析,我明知道了Swift實(shí)例對(duì)象的初始化會(huì)調(diào)用__allocating_init()方法,接著我們通過LGPerson樣例分析了Swift類的底層結(jié)構(gòu)HeapObject,其默認(rèn)包含了metadata(struct)refCounts(class),共16字節(jié)大小。

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

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

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