重學Swift第一篇:類結構探索

SofunNiu發表於2020-12-09

前言

內容主要是通過對Swift原始碼和SIL程式碼來學習Swift中底層的實現。

一、Swift編譯過程

一個swift檔案的編譯過程如下:
在這裡插入圖片描述
Swift在編譯過程中使用的LLVM前端編譯器是swiftc,可通過swiftc -h檢視了解各命令的作用。

二、SIL分析

在main.swift中定義Test類

class Test {}
var test = Test()

通過swiftc -emit-sil main.swift >> main.sil && open -a Xcode main.sil把swift檔案編譯成sil檔案並開啟

class Test {  //Test類
  @objc deinit
  init()
}

@_hasStorage @_hasInitialValue var test: Test { get set }

// test  
//可通過xcrun swift-demangle s4main4testAA4TestCvp還原函式符號
sil_global hidden @$s4main4testAA4TestCvp : $Test

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

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

// Test.init()
sil hidden @$s4main4TestCACycfc : $@convention(method) (@owned Test) -> @owned Test {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Test):
  debug_value %0 : $Test, let, name "self", argno 1 // id: %1
  return %0 : $Test                               // id: %2
} // end sil function '$s4main4TestCACycfc'

可以看到在main函式中呼叫了Test.__allocating_init(),在__allocating_init()中通過alloc_ref生成了Test的例項,接著呼叫init方法返回了自身。
可以在VSCode或Xcode中打一個__allocating_init符號斷點,我這裡原始碼是用VSCode開啟的,所以在VSCode中打上斷點執行後,單步除錯進入了swift_allocObject函式

HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
                                     size_t requiredSize,
                                     size_t requiredAlignmentMask) {
  CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}

CALL_IMPL是一個巨集,接著單步除錯,進入_swift_allocObject_函式

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask)); //分配記憶體,
  new (object) HeapObject(metadata); //建立HeapObject物件
  ......
  return object;
}

通過swift_slowAlloc方法分配了記憶體,然後通過new (object) HeapObject(metadata)在object的記憶體上就地建立HeapObject物件。

struct HeapObject {
  /// This is always a valid pointer to a metadata object.這始終是指向後設資料物件的有效指標
  HeapMetadata const *metadata;   //指標8bytes
  InlineRefCounts refCounts;     //RefCounts類,類中有一個refCounts成員變數,型別為RefCountBitsT,類RefCountBitsT中只有一個uint64_t的bits成員變數,所以最後也佔用8bytes
  ......
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
};

可以看出物件是HeapObject *型別,它有兩個屬性,一個是metadata,一個是refCounts,預設佔用16bytes。

三、類結構探索

從上面SIL分析中瞭解到類的結構體為HeapObject,HeapObject中metadata包含了後設資料資訊,refCounts用來表示引用計數。下面主要來看metadata,它的型別HeapMetadata原始碼如下:

using HeapMetadata = TargetHeapMetadata<InProcess>;

template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> { ... }

template <typename Runtime>
struct TargetMetadata {
	using StoredPointer = typename Runtime::StoredPointer;
	constexpr TargetMetadata() : Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
	......
	StoredPointer Kind; //InProcess::StoredPointer就是unsinged long long
	......
	const TargetClassMetadata<Runtime> *getClassObject() const;
}

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);
    }
   	......
  }
}

可以看出HeapMetadata中沒有屬性,而它繼承自TargetMetadata,而TargetMetadata有一個Kind屬性。這個Kind屬性可以標識元類資料的型別,具體如下
在這裡插入圖片描述
而TargetMetadata還有一個getClassObject()函式,可以根據kind拿到對應的metadata,接著來看ClassMetadata。

using ClassMetadata = TargetClassMetadata<InProcess>;

struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
  ClassFlags Flags; //swift類標記,這些標誌僅在isTypeMetadata()時有效。
  uint32_t InstanceAddressPoint;  //例項地址點
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize; //例項所需大小
  uint16_t InstanceAlignMask; //例項對齊掩碼
  uint16_t Reserved;  //保留
  uint32_t ClassSize; //類物件的總大小,包括字首和字尾範圍。
  uint32_t ClassAddressPoint; //類物件內地址點的偏移量。
private:
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
public:
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
}

template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
  ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;  //超類的後設資料。根類為null。ObjC類沒有後設資料頭
  TargetPointer<Runtime, void> CacheData[2]; //用於動態查詢,它歸執行時所有,通常需要與Objective-C的使用進行互操作。
  StoredSize Data; //unsigned long
}

綜上可知ClassMetadata結構體可大致表示為

struct ClassMetadata {
  void* Kind;      //表示型別,相當於isa的功能
  void* Superclass;  //超類
  void* CacheData;  //快取資料
  void* Data;  //外聯後設資料, 用低位標誌為Swift metatype,存在型別後設資料頭
  uint32_t Flags; //swift類標記,這些標誌僅在isTypeMetadata()時有效。
  uint32_t InstanceAddressPoint;  //例項地址點
  uint32_t InstanceSize; //例項所需大小
  uint16_t InstanceAlignMask; //例項對齊掩碼
  uint16_t Reserved;  //保留
  uint32_t ClassSize; //類物件的總大小,包括字首和字尾範圍。
  uint32_t ClassAddressPoint; //類物件內地址點的偏移量。
  void* Description;
  void* IVarDestroyer;
  ......
}

類比objc中原始碼

struct swift_class_t : objc_class {
	//isa_t isa;
	//Class superclass;
	//cache_t cache;
	//class_data_bits_t bits;
    uint32_t flags;
    uint32_t instanceAddressOffset;
    uint32_t instanceSize;
    uint16_t instanceAlignMask;
    uint16_t reserved;

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

    void *baseAddress() {
        return (void *)((uint8_t *)this - classAddressOffset);
    }
};

總結

  • 通過swiftc -emit-sil main.swift >> main.sil可以把swift原始檔轉為sil檔案。
  • 通過xcrun swift-demangle可把符號還原
  • HeapObjec有一個HeapMetadata型別的metadata用來提供類的資料,通過metadata->getKind()可知後設資料型別,通過metadata->getClassObject()可以拿到後設資料。還有一個refCounts用來管理引用計數。

相關文章