Swift编译简介
我们先来看一段简单代码
class LYPerson {
var age : Int = 19
var name: String = "LY"
}
let p = LYPerson()
接下来,我们想要研究的是,这个默认初始化器到底做了一个什么样的操作,在这里我们引入 SIL(Swift intermediate language),在来阅读SIL的代码之前,我们先来了解一下什么是SIL.
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的。如图所示:
可以看到:
OC是通过clang编译器,编译成IR,然后在生成可执行文件.o
Swift则是通过Swfit编译器编译成IR,然后在生成可执行文件。
swift在编译过程中使用的前端编译器是 swiftc,我们可以通过swiftc -h命令查看它能做什么样的事情。
Desktop % swiftc -help
OVERVIEW: Swift compiler
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)
接下来,我们使用 swiftc命令,将以上代码编译为SIL
// 将 main.swift 文件 使用swiftc编译后 覆盖写入 ./main.sil文件中,并打开。
swiftc -emit-sil main.swift > ./main.sil && open main.sil
我们使用VSCode打开SIL文件
class LYPerson {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
我们可以看到,这里生成了
- 两个存储属性
deinit方法- 初始化
init方法
接下来我们来分析整个SIL文件
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA8LYPersonCvp // id: %2
%3 = global_addr @$s4main1pAA8LYPersonCvp : $*LYPerson // user: %7
%4 = metatype $@thick LYPerson.Type // user: %6
// function_ref LYPerson.__allocating_init()
%5 = function_ref @$s4main8LYPersonCACycfC : $@convention(method) (@thick LYPerson.Type) -> @owned LYPerson // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LYPerson.Type) -> @owned LYPerson // user: %7
store %6 to %3 : $*LYPerson // id: %7
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} /
main函数是swift里面默认的入口函数,在写代码的时候,把它给省略了。alloc_global:我们可以使用
xcrun swift-demangle s4main1pAA8LYPersonCvp
对字符串进行反混淆
$s4main1pAA8LYPersonCvp ---> main.p : main.LYPerson
我们可以看出 alloc_global在这里全局初始化了一个 LYPerson对象,就是 p变量。
function_ref @$s4main8LYPersonCACycfC: 是__allocating_init方法的方法引用。- 紧接着使用
apply调用__allocating_init方法,参数为metatype。 store: 将 生成的实例变量 赋值给p变量。
// variable initialization expression of LYPerson.age
sil hidden [transparent] @$s4main8LYPersonC3ageSivpfi : $@convention(thin) () -> Int {
bb0:
%0 = integer_literal $Builtin.Int64, 19 // user: %1
%1 = struct $Int (%0 : $Builtin.Int64) // user: %2
return %1 : $Int // id: %2
} // end sil function '$s4main8LYPersonC3ageSivpfi'
- 主要功能为 构建一个整型变量
integer_literal:生成整形立即数 19.struct $Int: 在 swift 中整型变量实际上是一个结构体
实例对象的结构
__allocating_init()
通过对sil文件的分析,我们知道了swift对象在初始化的时候,是通过__allocating_init方法创建的,我们通过Xcode来探索__allocating_init。
首先,我们设置一个符号断点
然后运行工程
LYSwift`LYPerson.__allocating_init():
0x100003bf0 <+0>: pushq %rbp
0x100003bf1 <+1>: movq %rsp, %rbp
0x100003bf4 <+4>: pushq %r13
0x100003bf6 <+6>: pushq %rax
0x100003bf7 <+7>: movl $0x28, %esi
0x100003bfc <+12>: movl $0x7, %edx
-> 0x100003c01 <+17>: movq %r13, %rdi
0x100003c04 <+20>: callq 0x100003d6e ; symbol stub for: swift_allocObject
0x100003c09 <+25>: movq %rax, %r13
0x100003c0c <+28>: callq 0x100003c50 ; LYSwift.LYPerson.init() -> LYSwift.LYPerson at main.swift:11
0x100003c11 <+33>: addq $0x8, %rsp
0x100003c15 <+37>: popq %r13
0x100003c17 <+39>: popq %rbp
0x100003c18 <+40>: retq
在这里调用了 swift_allocObject方法
我们在swift源码中查看该函数
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;
}
该方法最终创建了一个实例对象,生成 LYPerson对象需要 40字节空间大小,内存为 8字节对齐.
对象的就是一个HeapObject结构体,通过metadata元数据来创建metadata。
通过 swift_slowAlloc在内存中申请 size大小的空间。
HeapObject
HeapObject的初始化方法为
HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{}
HeapObject:里面有两个参数,一个是 metadata,一个是 refCounts。metadata就是元类型数据,refCounts是对象的引用计数,在swift中,内存管理也是使用引用计数做的。HeapMetadata是一个指针类型的数据,其空间大小为8字节。refCounts: Refcounts其大小为8字节,所以 HeapObject 默认占用
16字节空间。
在OC中,实例对象的本质是 objc_object(Class isa),其默认大小为 8 字节
类对象的结构
HeapMetadata
HeapMetadata就是swift中的类结构,里面记录的信息就是类的信息。HeapMetadata是 TargetHeapMetadata 的别名,TargetHeapMetadata是一个结构体,继承至 TargetMetadata ,TargetMetadata也是一个结构体
struct TargetMetadata {
private:
StoredPointer Kind;
}
Kind是一个 unsigned const long类型的数据,主要是用来区分是哪种类型的元数据,在 MetadataKind.def里面,我们可以看到所有的元数据类型。
kind的种类为:
| name | Value | name | Value |
|---|---|---|---|
| Class | 0x0 | ObjCClassWrapper | 0x305 |
| Struct | 0x200 | ExistentialMetatype | 0x306 |
| Enum | 0x201 | HeapLocalVariable | 0x400 |
| Optional | 0x202 | HeapGenericLocalVariable | 0x500 |
| ForeignClass | 0x203 | ErrorObject | 0x501 |
| Opaque | 0x300 | LastEnumerated | 0x7FF |
| Tuple | 0x301 | ||
| Function | 0x302 | ||
| Existential | 0x303 | ||
| Metatype | 0x304 |
如果我们通过 kind 获取的类型为 Class,其返回的类型为 TargetClassMetadata。通过阅读源码我们可得如下继承关系:
TargetClassMetadata(所有class类属性)->TargetAnyClassMetadata(kind,superClass, cacheData)->TargetHeapMetadata -> TargetMetadata(kind)
这样我们就总结出metadata的数据结构了
struct swift_class_t {
void *kind; // kind (unsigned long)
void *superClass;
void *cacheData;
uint32_t flags; //4
uint32_t instanceAddressOffset; // 4
uint32_t instanceSize; // 4
uint16_t instanceAlignMask; // 2
//....
}
实例对象和类对象绑定
前面我们对Swift对象和Swift类对象的结构进行了探索,下面我们通过内容绑定的形式来验证能否进行内存映射。
// swift对象
struct HeapObject {
var metadata: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
// swift-class对象
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
// 实例
class Person {
var age: Int = 18
var name: String = "GG"
}
var t = Person()
// 将实例 t 绑定到 HeapObject 结构体上面
let objcRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
// 绑定到swift-class结构体上
let metadata = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
输出结果如下
HeapObject(metadata: 0x00000001000081a8, refcounted1: 3, refcounted2: 0)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (140733732518560, 140943646785536), data: 4301294722, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40, instanceAlignmentMask: 7, reserved: 0, classSize: 168, classAddressPoint: 16, typeDescriptor: 0x0000000100003c68, iVarDestroyer: 0x0000000000000000)
可以看出,能够完美的映射到该内存处。
总结
在这里我们探索了swift实例对象的初始化过程和swift类对象的结构。实例对象的本质是一个HeapObject结构体,默认里面包含了两个属性 metadata和 refCount,其默认大小为16字节, metadata代表其为哪种类型的数据,refCount为该对象的引用计数。swift类对象本质是 TargetClassMetaData的结构体,里面包含了类的一些基本信息(kind, superClass)等。
本文用的命令汇总
// 将 main.swift 使用 swiftc 编译成 sil文件,并打开
swiftc -emit-sil main.swift > ./main.sil && open main.sil
// 对字符串进行 swift 反混淆
xcrun swift-demangle s4main1pAA8LYPersonCvp