Swift底層探索(一):編譯流程

Swift編譯

Swift編譯究竟是一個(gè)怎樣的過程呢?從Swift語言到cpu能夠識(shí)別的機(jī)器碼這之間究竟經(jīng)過了哪些步驟呢?
我們先寫一個(gè)簡單的HotpotCat類如下:

import Foundation

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int = 1
}

var hotpot = HotpotCat()

通過默認(rèn)的初始化器我們構(gòu)造了一個(gè)實(shí)例對(duì)象hotpot,類比OC在這個(gè)過程中alloc(內(nèi)存分配) init(初始化)。那么Swift這個(gè)默認(rèn)的初始化器到底做了什么操作?這里我們需要了解SIL(Swift intermediate language),再閱讀SIL之前,先了解下什么是SIL。從字面意思理解它是一門中間語言。
iOS開發(fā)的語言不管是OC還是Swift后端都是通過LLVM進(jìn)行編譯的,編譯過程如下:

image.png

OC通過clang編譯器,編譯成IR,然后生成可執(zhí)行文件.o(也就會(huì)機(jī)器碼);
Swift通過Swift編譯器編譯成IR,然后生成可執(zhí)行文件。

那么一個(gè)Swift文件編譯的整個(gè)過程中都經(jīng)歷了什么呢?


Swift編譯過程
  • 1.Parse :解析器是一個(gè)簡易的遞歸下降解析器(在 lib/Parse 中實(shí)現(xiàn)),并帶有完整手動(dòng)編碼的詞法分析器。通過parse進(jìn)行詞法分析;
  • 2.Semantic Analysis: 語義分析階段(在 lib/Sema 中實(shí)現(xiàn))負(fù)責(zé)獲取已解析的 AST(抽象語法樹)并將其轉(zhuǎn)換為格式正確且類型檢查完備的 AST,以及在源代碼中提示出現(xiàn)語義問題的警告或錯(cuò)誤。語義分析包含類型推斷,如果可以成功推導(dǎo)出類型,則表明此時(shí)從已經(jīng)經(jīng)過類型檢查的最終 AST 生成代碼是安全的;
  • 3.Clang Importer:Clang 導(dǎo)入器(Clang Importer):Clang 導(dǎo)入器(在 lib/ClangImporter 中實(shí)現(xiàn))負(fù)責(zé)導(dǎo)入 Clang 模塊,并將導(dǎo)出的 C 或 Objective-C API 映射到相應(yīng)的 Swift API 中。最終導(dǎo)入的 AST 可以被語義分析引用。
  • 4.SIL 生成(SIL Generation):Swift 中間語言(Swift Intermediate Language,SIL)是一門高級(jí)且專用于 Swift 的中間語言,適用于對(duì) Swift 代碼的進(jìn)一步分析和優(yōu)化。SIL 生成階段(在 lib/SILGen 中實(shí)現(xiàn))將經(jīng)過類型檢查的 AST 弱化為所謂的「原始」SIL。SIL 的設(shè)計(jì)在 docs/SIL.rst 有所描述。這個(gè)過程生成RAW SIL(原生SIL,代碼量很大,不會(huì)進(jìn)行類型檢查,代碼優(yōu)化)
  • 5.SIL 保證轉(zhuǎn)換(SIL Guaranteed Transformations):SIL 保證轉(zhuǎn)換階段(在 lib/SILOptimizer/Mandatory中實(shí)現(xiàn))負(fù)責(zé)執(zhí)行額外且影響程序正確性的數(shù)據(jù)流診斷(比如使用未初始化的變量)。這些轉(zhuǎn)換的最終結(jié)果是「規(guī)范」SIL。
  • 6.SIL 優(yōu)化(SIL Optimizations):SIL 優(yōu)化階段(在 lib/Analysis、lib/ARC、lib/LoopTransforms 以及 lib/Transforms 中實(shí)現(xiàn))負(fù)責(zé)對(duì)程序執(zhí)行額外的高級(jí)且專用于 Swift 的優(yōu)化,包括(例如)自動(dòng)引用計(jì)數(shù)優(yōu)化、去虛擬化、以及通用的專業(yè)化
    通過-emit-sil命令生成優(yōu)化過后的 SIL Opt Canonical SIL。這個(gè)也是我們一般閱讀的SIL代碼;
  • 7.LLVM IR 生成(LLVM IR Generation):IR 生成階段(在 lib/IRGen 中實(shí)現(xiàn))將 SIL 弱化為 LLVM LR,此時(shí) LLVM 可以繼續(xù)優(yōu)化并生成機(jī)器碼。
    通過IRGen生成 IR;
  • 8.然后生成機(jī)器碼交給機(jī)器進(jìn)行識(shí)別。

Swift和OC的區(qū)別也就是中間SIL生成的這一部分。我們一般閱讀經(jīng)過優(yōu)化后的SIL文件。

swift在編譯的過程中使用的前段編譯器是swiftc,和OC中使用的Clang是有區(qū)別的??梢酝ㄟ^swiftc -h查看swiftc都能哪些功能:

USAGE: swiftc
MODES:
  -dump-ast              Parse and type-check input file(s) and dump AST(s)
  -dump-parse            Parse input file(s) and dump AST(s)
  -dump-pcm              Dump debugging information about a precompiled Clang module
  -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               Emit LLVM BC file(s)
  -emit-executable       Emit a linked executable
  -emit-imported-modules Emit a list of the imported modules
  -emit-ir               Emit LLVM IR file(s)
  -emit-library          Emit a linked library
  -emit-object           Emit object file(s) (-c)
  -emit-pcm              Emit a precompiled Clang module from a module map
  -emit-sibgen           Emit serialized AST + raw SIL file(s)
  -emit-sib              Emit serialized AST + canonical SIL file(s)
  -emit-silgen           Emit raw SIL file(s)
  -emit-sil              Emit canonical SIL file(s)
  -index-file            Produce index data for a source file
  -parse                 Parse input file(s)
  -print-ast             Parse and type-check input file(s) and pretty print AST(s)
  -resolve-imports       Parse and resolve imports in input file(s)
  -typecheck             Parse and type-check input file(s)

簡單分析下
-dump-parse
Parse input file(s) and dump AST(s)、分析輸出AST

swiftc -dump-parse main.swift >> ./main.parse

-dump-ast
Parse and type-check input file(s) and dump AST(s)。分析并且檢查類型輸出AST。

swiftc -dump-ast main.swift >> ./main.ast

從以上可以看出這兩個(gè)都生成了抽象語法樹,-dump-ast多了類型檢查。我們摘錄一段對(duì)比就可以看出一個(gè)bind Swift String,一個(gè)為none。

//-dump-parse
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed
        (pattern_named 'name')
        (type_ident
          (component id='String' bind=none)))

//-dump-ast
    (pattern_binding_decl range=[main.swift:11:5 - line:11:24]
      (pattern_typed type='String'
        (pattern_named type='String' 'name')
        (type_ident
          (component id='String' bind=Swift.(file).String)))

-emit-silgen
Emit raw SIL file(s),簡單理解為生成未加工的SIL文件。

swiftc -emit-silgen main.swift >> ./main.silgen
image.png

-emit-sil
Emit canonical SIL file(s),經(jīng)過優(yōu)化處理的SIL文件

swiftc -emit-sil main.swift >> ./main.sil
image.png

對(duì)比兩者可以看到經(jīng)過優(yōu)化的SIL多了引用計(jì)數(shù)相關(guān)的操作,那么猜測引用計(jì)數(shù)是在SIL優(yōu)化這一步進(jìn)行處理的。

-emit-ir
Emit LLVM IR file(s), 生成LLVM的IR中間表示層文件

swiftc -emit-ir main.swift >> ./main.ir

-emit-assembly
Emit assembly file(s) (-S),生成匯編語言

swiftc -emit-assembly  main.swift >> ./main.assembly

-emit-bc
Emit LLVM BC file(s),生成字節(jié)碼二進(jìn)制

swiftc -emit-bc  main.swift >> ./main.bc

-o
生成可執(zhí)行的二進(jìn)制文件

swiftc -o  main.o  main.swift

這里main.swift文件是一個(gè)只依賴了Foundation庫的文件,直接使用-emit就可以了,這么直接編譯iOS項(xiàng)目中的Swift源文件會(huì)報(bào)錯(cuò),因?yàn)橐蕾嚵薝IKit或其它庫,需要額外指定-target和-sdk。

  • sdk 直接指定Xcode對(duì)應(yīng)的SDK.
//模擬器
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk`
//真機(jī)
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk`
//mac
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk

可以通過命令代替真實(shí)路徑,當(dāng)然真實(shí)路徑也沒問題,以下分別對(duì)應(yīng)模擬器、真機(jī)、mac 路徑:
xcrun --show-sdk-path --sdk iphonesimulator/iphoneos/macosx

  • target (指令集-apple-ios版本/指令集-apple-ios版本-simulator
    這里在操作時(shí)用mac/iphone代替apple也是可以的,分別指代maciphone平臺(tái)。當(dāng)然TVwatch也同理。
    對(duì)應(yīng)目標(biāo)平臺(tái)i386、x86_64armv7、armv7sarm64、arm64e等,這里iOS版本最低iOS7。
    如:arm64e-apple-ios14.0、x86_64-apple-ios14-simulator、armv7-iphone-ios12.0、arm64-mac-macosx11.0
//模擬器
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphonesimulator)  -target x86_64-apple-ios14.0-simulator ViewController.swift >> ./ViewController.sil
//真機(jī)
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphoneos)  -target arm64e-apple-ios14.0 ViewController.swift >> ./ViewController.sil
//mac
swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk macosx) -target arm64-apple-macosx11.0 ViewController.swift >> ./ViewController.sil

更詳細(xì)的介紹可以觀看官方講解視頻

SIL分析

接著上面的HotpotCat例子,我們直接在終端生成SIL文件并保存為main.sil查看。(當(dāng)然也可以嘗試其它命令-emit-silgen,-dump-ast)。

swiftc -emit-sil main.swift >> ./main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}

可以看到HotpotCat有兩個(gè)存儲(chǔ)屬性,有一個(gè)init方法,有一個(gè)objc標(biāo)識(shí)的deinit方法。

main分析

繼續(xù)看sil文件會(huì)找到一個(gè)main函數(shù),這個(gè)main函數(shù)其實(shí)就是swift隱藏的main函數(shù),swift不過進(jìn)行了省略而已。

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main6hotpotAA9HotpotCatCvp     // id: %2
  %3 = global_addr @$s4main6hotpotAA9HotpotCatCvp : $*HotpotCat // user: %7
  %4 = metatype $@thick HotpotCat.Type            // user: %6
  // function_ref HotpotCat.__allocating_init()
  %5 = function_ref @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %7
  store %6 to %3 : $*HotpotCat                    // 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'

在SIL中以@作為標(biāo)識(shí)符名稱前綴

  • @mian 標(biāo)識(shí)這是swift入口函數(shù);
  • @convention(c)標(biāo)識(shí)這是一個(gè)C函數(shù),有兩個(gè)參數(shù)Int32和UnsafeMutablePointer的指針,返回Int32;
  • alloc_global分配一個(gè)全局變量s4main6hotpotAA9HotpotCatCvp,這個(gè)變量也就是變量hotpot,名字是經(jīng)過混寫之后的(name manager)??梢酝ㄟ^xcrun去還原查看。
 xcrun  swift-demangle s4main6hotpotAA9HotpotCatCvp
$s4main6hotpotAA9HotpotCatCvp ---> main.hotpot : main.HotpotCat
  • %3~%9表示當(dāng)前寄存器,這里的寄存器和register read讀取的寄存器不是一個(gè)東西,這里寄存器是虛擬的會(huì)一直遞增,可以理解為常量,經(jīng)過賦值之后不會(huì)再改變。在運(yùn)行的時(shí)候會(huì)直接對(duì)應(yīng)到真實(shí)的寄存器。
  • global_addr 拿到全局變量hotpot的地址賦值給%3。
  • metatype 拿到HotpotCat的 Metadata 賦值給%4。也就是元類型。
  • function_ref 拿到函數(shù)s4main9HotpotCatCACycfC的地址給到%5。這個(gè)函數(shù)我們xcrun看一下其實(shí)是$s4main9HotpotCatCACycfC ---> main.HotpotCat.__allocating_init() -> main.HotpotCat。當(dāng)然SIL里面也有注釋。
  • apply %5(%4)就是調(diào)用__allocating_init,參數(shù)是我們的元類型。返回值為我們要的實(shí)例變量。
  • store 將得到的實(shí)例變量%6給到%3,也就是將實(shí)例變量地址放到全局變量中。
  • 最后也就是構(gòu)建一個(gè)0返回,相當(dāng)于main函數(shù)的 retern 0。

Swift實(shí)例對(duì)象

__allocating_init()分析

那么s4main9HotpotCatCACycfC(__allocating_init)究竟干了什么呢?

// HotpotCat.__allocating_init()
sil hidden [exact_self_class] @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat {
// %0 "$metatype"
bb0(%0 : $@thick HotpotCat.Type):
  %1 = alloc_ref $HotpotCat                       // user: %3
  // function_ref HotpotCat.init()
  %2 = function_ref @$s4main9HotpotCatCACycfc : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %4
  return %3 : $HotpotCat                          // id: %4
} // end sil function '$s4main9HotpotCatCACycfC'
  • alloc_ref 創(chuàng)建HotpotCat實(shí)例對(duì)象(在堆上分配內(nèi)存空間),引用計(jì)數(shù)為1。alloc_ref
    官方原文:
Allocates an object of reference type T. The object will be initialized with retain count 1;
  • function_ref 獲取init方法,這里注意s4main9HotpotCatCACycfcs4main9HotpotCatCACycfC最后一個(gè)字母不同,xcrun驗(yàn)證一下確實(shí)是init()方法。
  • apply 調(diào)用init方法,初始化實(shí)例變量,參數(shù)是實(shí)例變量地址,最后返回。

xcode斷點(diǎn)驗(yàn)證

我們可以設(shè)置一個(gè)符號(hào)斷點(diǎn)(__allocating_init)驗(yàn)證一下:


image.png
SwiftBasic`HotpotCat.__allocating_init():
->  0x100003be0 <+0>:  push   rbp
    0x100003be1 <+1>:  mov    rbp, rsp
    0x100003be4 <+4>:  push   r13
    0x100003be6 <+6>:  push   rax
    0x100003be7 <+7>:  mov    esi, 0x28
    0x100003bec <+12>: mov    edx, 0x7
    0x100003bf1 <+17>: mov    rdi, r13
    0x100003bf4 <+20>: call   0x100003d5e               ; symbol stub for: swift_allocObject
    0x100003bf9 <+25>: mov    r13, rax
    0x100003bfc <+28>: call   0x100003c40               ; SwiftBasic.HotpotCat.init() -> SwiftBasic.HotpotCat at main.swift:10
    0x100003c01 <+33>: add    rsp, 0x8
    0x100003c05 <+37>: pop    r13
    0x100003c07 <+39>: pop    rbp
    0x100003c08 <+40>: ret    

Swift源碼驗(yàn)證

同樣可以通過VSCode調(diào)試源碼,在swift的REPL(命令交互行)


image.png

swift_allocObject

在定義變量hotpot敲回車前,我們給_swift_allocObject_加個(gè)斷點(diǎn):
這個(gè)方法最終是創(chuàng)建我們當(dāng)前的實(shí)例對(duì)象

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
image.png

敲回車后看一下本地變量:大小需要40字節(jié),8字節(jié)對(duì)齊。

這里對(duì)齊的目的是對(duì)于64位cpu而言,一次能讀取8個(gè)字節(jié)(連續(xù))。在這個(gè)過程當(dāng)中連續(xù)讀8個(gè)字節(jié)最快,對(duì)于創(chuàng)建出來的實(shí)例對(duì)象應(yīng)該是8的倍數(shù),偶數(shù)。8的倍數(shù)是為了在整個(gè)內(nèi)存尋址周期的過程當(dāng)中更加的具有效率,不足8的倍數(shù)會(huì)補(bǔ)足。目的是以空間換時(shí)間來提高訪問存儲(chǔ)效率。這里掩碼為7代表的就是8字節(jié)對(duì)齊。

image.png

swift_slowAlloc

接著我們看swift_slowAlloc這個(gè)函數(shù):

void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}

p這里可以看到在堆中創(chuàng)建size大小的空間。這個(gè)size存儲(chǔ)我們當(dāng)前的實(shí)例變量

#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);

swift_allocObject函數(shù)的返回值是HeapObject,意味著我們創(chuàng)建出來的實(shí)例變量在內(nèi)存當(dāng)中是HeapObject。

  new (object) HeapObject(metadata);

創(chuàng)建HeapObject的參數(shù)是metadata,也就是通過元數(shù)據(jù)初始化HeapObject這個(gè)結(jié)構(gòu)體。

HeapObject

HeapObject內(nèi)容如下(c++代碼):

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;
  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

可以看到這函數(shù)需要metadata(元數(shù)據(jù))和refCounts(引用計(jì)數(shù))??梢钥吹絤etadata是一個(gè)指針類型(8字節(jié)),refCounts可以看到是一個(gè)InlineRefCounts。

InlineRefCounts、RefCounts

InlineRefCounts定義如下,RefCounts點(diǎn)進(jìn)去可以看到是一個(gè)class。

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

所以refCounts也是一個(gè)指針類型(8字節(jié))。上面我們?cè)赩SCode調(diào)試的時(shí)候看到hotpot對(duì)象requiredSize占40字節(jié),那么這里其實(shí)Int占8字節(jié),String占16字節(jié)。
我們也可以通過代碼直接驗(yàn)證

print(MemoryLayout<String>.size)
print(MemoryLayout<Int>.stride)
print(class_getInstanceSize(HotpotCat.self))
16
8
40

那么對(duì)我們的hotpot實(shí)例對(duì)象本質(zhì)是一個(gè)HeapObject結(jié)構(gòu)體(默認(rèn)16字節(jié)大小)。相比于OC實(shí)例對(duì)象本質(zhì)是結(jié)構(gòu)體objc_object,他有一個(gè)isa指針默認(rèn)8字節(jié)。swift相比oc多了一個(gè)refCounted。

  • swift 內(nèi)存分配:__allcoating_init—>swift_allocObject—>swift_allocObject—>swift_slowAlloc—>Malloc;
  • Swift對(duì)象的內(nèi)存結(jié)構(gòu)為HeapObject,有兩個(gè)屬性:Metadata、RefCount默認(rèn)占用16字節(jié);
  • init初始化變量,和OC中是一致的。

類對(duì)象

類結(jié)構(gòu)是HeapMetadata結(jié)構(gòu),OC中類結(jié)構(gòu)是objc_class。

HeapMetadata

using HeapMetadata = TargetHeapMetadata<InProcess>;

從源碼中可以看到HeapMetadata是一個(gè)別名,實(shí)際是TargetHeapMetadata。

TargetHeapMetadata

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
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

TargetHeapMetadata是一個(gè)模板類型,接收一個(gè)參數(shù)InProcess, InProcess定義了一些需要的數(shù)據(jù)結(jié)構(gòu)??碩argetHeapMetadata源碼發(fā)現(xiàn)其中并沒有屬性,只有初始化方法,初始化方法是MetadataKind kind,那么我們看看他的父結(jié)構(gòu)體TargetMetadata。

TargetMetadata

private:
  /// The kind. Only valid for non-class metadata; getKind() must be used to get
  /// the kind value.
  StoredPointer Kind;
public:
  /// Get the metadata kind.
  MetadataKind getKind() const {
    return getEnumeratedMetadataKind(Kind);
  }
  
  /// Set the metadata kind.
  void setKind(MetadataKind kind) {
    Kind = static_cast<StoredPointer>(kind);
  }

可以看到有一個(gè)StoredPointer Kind

  using StoredPointer = typename Runtime::StoredPointer;

再后頭看下InProcess,可以找到

  using StoredPointer = uintptr_t;

繼續(xù)看uintptr_t,可以看到是unsigned long類型,那么kind就是unsigned long類型。kind其實(shí)是來區(qū)分是哪種類型的元數(shù)據(jù)。

typedef unsigned long           uintptr_t;

TargetHeapMetadata中點(diǎn)開MetadataKind可以看到一個(gè)MetadataKind.def文件,點(diǎn)開.

MetadataKind.def

/// A class type.
NOMINALTYPEMETADATAKIND(Class, 0)

/// A struct type.
NOMINALTYPEMETADATAKIND(Struct, 0 | MetadataKindIsNonHeap)

/// An enum type.
/// If we add reference enums, that needs to go here.
NOMINALTYPEMETADATAKIND(Enum, 1 | MetadataKindIsNonHeap)

/// An optional type.
NOMINALTYPEMETADATAKIND(Optional, 2 | MetadataKindIsNonHeap)

/// A foreign class, such as a Core Foundation class.
METADATAKIND(ForeignClass, 3 | MetadataKindIsNonHeap)

/// A type whose value is not exposed in the metadata system.
METADATAKIND(Opaque, 0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A tuple.
METADATAKIND(Tuple, 1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A monomorphic function.
METADATAKIND(Function, 2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential type.
METADATAKIND(Existential, 3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A metatype.
METADATAKIND(Metatype, 4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An ObjC class wrapper.
METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// An existential metatype.
METADATAKIND(ExistentialMetatype, 6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)

/// A heap-allocated local variable using statically-generated metadata.
METADATAKIND(HeapLocalVariable, 0 | MetadataKindIsNonType)

/// A heap-allocated local variable using runtime-instantiated metadata.
METADATAKIND(HeapGenericLocalVariable,
             0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

/// A native error object.
METADATAKIND(ErrorObject,
             1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)

這里面記錄了我們當(dāng)前所有元數(shù)據(jù)類型。
kind種類:

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

回到TargetMetadata,只有一個(gè)kind屬性,在這個(gè)文件中我們往下找看看,706行可以看到有一個(gè)getClassObject方法,返回類型為TargetClassMetadata

  const TargetClassMetadata<Runtime> *getClassObject() const;

實(shí)現(xiàn)為:

  template<> inline const ClassMetadata *
  Metadata::getClassObject() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      // Native Swift class metadata is also the class object.
      return static_cast<const ClassMetadata *>(this);
    }
    case MetadataKind::ObjCClassWrapper: {
      // Objective-C class objects are referenced by their Swift metadata wrapper.
      auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
      return wrapper->Class;
    }
    // Other kinds of types don't have class objects.
    default:
      return nullptr;
    }
  }

  void *allocateMetadata(size_t size, size_t align);

本身是匹配kind返回對(duì)應(yīng)的值,就是上表對(duì)應(yīng)kind的值。如果是class那么把this也就是當(dāng)前指針強(qiáng)轉(zhuǎn)為ClassMetadata.
通過lldb po metadata 驗(yàn)證為class

Stop reason: exec
po metadata->getKind()
Class
po metadata->getClassObject()
0x0000000110effc70
x/8g 0x0000000110effc70
//這里就是元數(shù)據(jù)里面記錄的數(shù)據(jù)了
0x110effc70: 0x0000000110effc38 0x000000011976e420
0x110effc80: 0x00007fff201d3af0 0x0000803000000000
0x110effc90: 0x0000000110f880c2 0x0000000000000002
0x110effca0: 0x0000000700000028 0x00000010000000a8

那么意味著TargetMetadata也就是TargetClassMetadata,那么我們認(rèn)為的內(nèi)存中的結(jié)構(gòu)體也就是TargetClassMetadata。

TargetClassMetadata

那么在TargetClassMetadata中我們可以看到以下數(shù)據(jù)

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;
private:
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

那么它還有一個(gè)父類TargetAnyClassMetadata

TargetAnyClassMetadata

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  using StoredPointer = typename Runtime::StoredPointer;
  using StoredSize = typename Runtime::StoredSize;

TargetAnyClassMetadata 繼承了TargetHeapMetadata

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
};
using HeapMetadata = TargetHeapMetadata<InProcess>;

TargetHeapMetadata又繼承了TargetMetadata,TargetMetadata有一個(gè)屬性kind。

經(jīng)過源碼的閱讀我們可以得到

Metadata::Class
TargetClassMetadata(所有的屬性)->TargetAnyClassMetadata(kind,superclass,cacheData)->TargetHeapMetadata->TargetMetadata(kind)
  • 如果Metadata是一個(gè)class,那么kind這個(gè)枚舉值返回TargetClassMetadata。
  • 而TargetClassMetadata繼承自TargetAnyClassMetadata,anyclass有3個(gè)屬性,繼承過來的kind以及superclass和cacheData
  • TargetAnyClassMetadata 繼承了TargetHeapMetadata
  • TargetHeapMetadata 繼承了TargetMetadata,他有一個(gè)屬性kind

那么所有的這些就構(gòu)成了我們Class內(nèi)存結(jié)構(gòu)(ClassMetadata + AnyClassMetadata + TargetMetadata),metadata數(shù)據(jù)結(jié)構(gòu)體為:

struct swift_class_t{
        void *kind; //isa, kind(unsigned long)//如果和OC交互了就是isa指針
        void *superClass;
        void *cacheData
        void *data
        uint32_t flags; //4
        uint32_t instanceAddressOffset; //4
        uint32_t instanceSize;//4
        uint16_t instanceAlignMask; //2
        uint16_t reserved; //2

        uint32_t classSize; //4
        uint32_t classAddressOffset; //4
        void *description;
 // ...
 };

Swift屬性

存儲(chǔ)屬性

要么是常量(let修飾),要么是變量(var修飾)

class HotpotCat {
    var name: String = "Hotpot"
    let age: Int = 1
}

var hotpot = HotpotCat()

對(duì)于HotpotCat中age、name來說都是我們的變量存儲(chǔ)屬性,在SIL中可以看到

class HotpotCat {
  @_hasStorage @_hasInitialValue var name: String { get set }
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}
image.png

0x0000000100008188 存儲(chǔ)的是metadata,0x0000000400000003存儲(chǔ)的是refcount,0x0000746f70746f48 0xe600000000000000 存儲(chǔ)的是String,0x0000000000000001存儲(chǔ)的是Int。

計(jì)算屬性

不占存儲(chǔ)空間,本質(zhì)是get/set方法
如果我們給計(jì)算屬性賦值會(huì)發(fā)生什么呢?

class HotpotCat {
    var name: String = "Hotpot"
    var age: Int {
        get{
           3
        }
        set{
            age = newValue
        }
    }
}

var hotpot = HotpotCat()
hotpot.age = 6
print(hotpot.age)
image.png

可以看到發(fā)生了遞歸調(diào)用,自己調(diào)用自己。
那么寫一個(gè)正確的計(jì)算屬性

class HotpotCat {
    var width: Double = 10;//8字節(jié)
    var area: Double{//不占存儲(chǔ)空間
        get{
            pow(width, 2)
        }
        set{
            width = sqrt(newValue)
        }
    }
}

var hotpot = HotpotCat()
print(class_getInstanceSize(HotpotCat.self))

輸出

24

這也就證明計(jì)算屬性確實(shí)不占內(nèi)存空間。
我們生成sil文件看看計(jì)算屬性到底是什么?

swiftc -emit-sil main.swift >> ./main.sil && open main.sil
class HotpotCat {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}
  • 可以看到存儲(chǔ)屬性使用_hasStorage修飾的,計(jì)算屬性沒有。
// HotpotCat.area.getter
sil hidden @$s4main9HotpotCatC4areaSdvg : $@convention(method) (@guaranteed HotpotCat) -> Double {
// %0 "self"                                      // users: %3, %2, %1
bb0(%0 : $HotpotCat):
  debug_value %0 : $HotpotCat, let, name "self", argno 1 // id: %1
  %2 = class_method %0 : $HotpotCat, #HotpotCat.width!getter : (HotpotCat) -> () -> Double, $@convention(method) (@guaranteed HotpotCat) -> Double // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed HotpotCat) -> Double // user: %7
  %4 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %5
  %5 = struct $Double (%4 : $Builtin.FPIEEE64)    // user: %7
  // function_ref pow
  %6 = function_ref @pow : $@convention(c) (Double, Double) -> Double // user: %7
  %7 = apply %6(%3, %5) : $@convention(c) (Double, Double) -> Double // user: %8
  return %7 : $Double                             // id: %8
} // end sil function '$s4main9HotpotCatC4areaSdvg'

// HotpotCat.area.setter
sil hidden @$s4main9HotpotCatC4areaSdvs : $@convention(method) (Double, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %5, %2
// %1 "self"                                      // users: %7, %6, %3
bb0(%0 : $Double, %1 : $HotpotCat):
  debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  // function_ref sqrt
  %4 = function_ref @sqrt : $@convention(c) (Double) -> Double // user: %5
  %5 = apply %4(%0) : $@convention(c) (Double) -> Double // user: %7
  %6 = class_method %1 : $HotpotCat, #HotpotCat.width!setter : (HotpotCat) -> (Double) -> (), $@convention(method) (Double, @guaranteed HotpotCat) -> () // user: %7
  %7 = apply %6(%5, %1) : $@convention(method) (Double, @guaranteed HotpotCat) -> ()
  %8 = tuple ()                                   // user: %9
  return %8 : $()                                 // id: %9
} // end sil function '$s4main9HotpotCatC4areaSdvs'
  • 可以看到就是get和set方法。
  • oc中的方法存儲(chǔ)在objc_class:Method_List中,swift方法存儲(chǔ)在metadata中。

屬性觀察者

willSet會(huì)在新值賦值之前調(diào)用,didSet會(huì)在新值賦值之后調(diào)用。

class HotpotCat {
    var name: String = "hotpot" {
        //新值存儲(chǔ)前調(diào)用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存儲(chǔ)后調(diào)用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
}

var hotpot = HotpotCat()
hotpot.name = "cat"
willSet newValue: cat oldValue: hotpot
didSet oldValue: hotpot newValue: cat

查看一下SIL文件,我們?cè)谠O(shè)置name的時(shí)候首先調(diào)用set方法,我們直接看SIL文件的name setter方法

屬性觀察者實(shí)現(xiàn)

這也就解釋了為什么willSet能訪問newValue和self,didSet能訪問oldValue和self。
再繼續(xù)看下willset(s4main9HotpotCatC4nameSSvw)方法的實(shí)現(xiàn)

sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "newValue"                                  // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "newValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1 

可以看到newValue是編譯器自己幫我們?nèi)〉?,let類型。didSet同理。
那我們自己指定變量名呢?

sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "myNewValue"                                // users: %31, %2
// %1 "self"                                      // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
  debug_value %0 : $String, let, name "myNewValue", argno 1 // id: %2
  debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
  %4 = integer_literal $Builtin.Word, 1           // user: %6

可以看到SIL里面已經(jīng)變成我們自己起名的變量名。

那么如果我們?cè)賗nit方法里面調(diào)用self.name,屬性觀察者會(huì)被調(diào)用么?

class HotpotCat {
    var name: String = "hotpot" {
        //新值存儲(chǔ)前調(diào)用
        willSet{
            print("willSet newValue: \(newValue) oldValue: \(name)")
        }
        //新值存儲(chǔ)后調(diào)用
        didSet{
            print("didSet oldValue: \(oldValue) newValue: \(name)")
        }
    }
    init() {
        //不會(huì)調(diào)用屬性觀察者
        self.name = "cat"
    }
}

var hotpot = HotpotCat()

很明顯控制臺(tái)沒有輸出,為什么呢?
init方法是做初始化用的。在這個(gè)過程中訪問oldValue或者其它屬性會(huì)獲取到未知狀態(tài)的值,所以swift禁止操作,這也就是swift安全的體現(xiàn)。


image.png

我們?cè)倏匆欢斡幸馑嫉拇a

class HotpotCat {
    var name: String = "hotpot" {
        //新值存儲(chǔ)前調(diào)用
        willSet {
            print("HotpotCat name willSet")
        }
        //新值存儲(chǔ)后調(diào)用
        didSet {
            print("HotpotCat name didSet")
        }
    }
    var width: Double = 10
    var area: Double {
        get {
            print("HotpotCat area get")
            return pow(width, 2)
        }
        set {
            print("HotpotCat area set")
            width = sqrt(newValue)
        }
    }
}

class MyHotpotCat: HotpotCat {
    override var name: String {
        willSet {
            print("MyHotpotCat name willset")
        }
        didSet {
            print("MyHotpotCat name didSet")
        }
    }
    override var area: Double {
        willSet {
            print("MyHotpotCat area willset")
        }
        didSet {
            print("MyHotpotCat area didSet")
        }
    }
    override init() {
        super.init()
        self.name = "cat"
        self.area = 100
    }
}

var myHotpot = MyHotpotCat()

控制臺(tái)打印

MyHotpotCat name willset
HotpotCat name willSet
HotpotCat name didSet
MyHotpotCat name didSet
MyHotpotCat area willset
HotpotCat area set
MyHotpotCat area didSet

這里要注意下name的調(diào)用順序,可以簡單理解為一個(gè)棧(先進(jìn)后出)。


image.png
  • 定義的存儲(chǔ)屬性,可以添加屬性觀察者。
  • 繼承的存儲(chǔ)屬性,可以添加屬性觀察者。
  • 繼承的計(jì)算屬性,可以添加屬性觀察者。
  • init方法中自己本身的屬性不會(huì)調(diào)用屬性觀察者,繼承的屬性會(huì)調(diào)用自己和父類的屬性觀察者。(self.init已經(jīng)對(duì)屬性做了初始化操作)
  • 計(jì)算屬性本身不能添加屬性觀察者,因?yàn)樽约罕旧硪呀?jīng)實(shí)現(xiàn)了set/get。

配置腳本生成SIL文件

每次跑命令生成SIL文件都比較麻煩,我們可以直接添加一個(gè)Target直接將命令放在腳本中執(zhí)行:
TARGETS -> Other -> Aggregate

image.png

Build Phases -> + -> New Run Script Phase
image.png

#這里需要注意路徑問題,在icloud里面的路徑會(huì)報(bào)錯(cuò),文件和路徑替換為自己的工程文件和路徑。
rm -rf ${SRCROOT}/main.sil
swiftc -emit-sil ${SRCROOT}/SwiftEnum/main.swift | xcrun swift-demangle  >> ./main.sil && open main.sil

附錄

SIL參考文檔
SIL手冊(cè)
OC轉(zhuǎn)Swift
參考閱讀
https://swift.org/swift-compiler/#compiler-architecture
https://www.imooc.com/article/273543
https://blog.csdn.net/qq_41790914/article/details/106457729
http://www.itdecent.cn/p/fb6923e3a7be
https://zhuanlan.zhihu.com/p/101898915
http://www.itdecent.cn/p/440d760a7392?from=singlemessage
https://zhuanlan.zhihu.com/p/112465903
https://blog.csdn.net/wjnhub/article/details/107818429
枚舉解析
LLVM相關(guān)內(nèi)容:
https://zhuanlan.zhihu.com/p/102028114
https://zhuanlan.zhihu.com/p/102250532
https://zhuanlan.zhihu.com/p/102270840
https://zhuanlan.zhihu.com/p/102716482
https://zhuanlan.zhihu.com/p/103674744

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 引言 維基百科: 編譯語言(英語:Compiled language)是一種以編譯器來實(shí)現(xiàn)的編程語言。它不像解釋型...
    素還真人閱讀 2,438評(píng)論 0 7
  • # iOS的編譯、鏈接工具 — Clang/LLVM 官網(wǎng)定義:[https://llvm.org/] The L...
    Tenloy閱讀 4,588評(píng)論 1 13
  • iOS 底層探索: 學(xué)習(xí)大綱 OC篇[/p/9d73ee7aae64] 前言 今天來學(xué)習(xí)一下牛逼的架構(gòu)編譯器LLV...
    歐德爾丶胡閱讀 1,938評(píng)論 3 5
  • 前言 語言類型 我們有很多維度可以將計(jì)算機(jī)語言進(jìn)行分類,其中以編譯/執(zhí)行方式為維度,可以將計(jì)算機(jī)語言分為: 編譯型...
    AiLearn閱讀 2,612評(píng)論 1 6
  • 先來看幾個(gè)定義: 編譯型語言: 需要編輯器將源代碼編譯成機(jī)器碼之后才能執(zhí)行的語言。一般分兩個(gè)步驟 編譯(compi...
    暴躁的小豆子閱讀 1,467評(píng)論 0 2

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