前言
内容主要是通过对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* CacheData1; //TargetPointer<Runtime, void> CacheData[2]是个数组,有两个元素,所以这里用CacheData1、CacheData2来表示
void* CacheData2;
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;
......
}
类比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 | xcrun swift-demangle > main.sil && open -a Xcode main.sil
可以把swift源文件转为还原符号后的sil文件并用Xcode打开。 - HeapObjec有一个HeapMetadata类型的metadata用来提供类的数据,通过
metadata->getKind()
可知元数据类型,通过metadata->getClassObject()
可以拿到元数据。还有一个refCounts用来管理引用计数。