- 上一节,我们完成了源码编译。本节,我们
探索Swift的编译流程和类结构
- swift编译流程
- 类结构
强烈建议先阅读LLVM入门,再开始本节的阅读
1. swift编译流程
在了解swift编译流程前,我们需要知道LLVM是什么(👉 LLVM入门)。
LLVM是架构编译器。可以为任何编程语言独立编写前端,也可以为任何硬件架构独立编写后端。
image.png
比如:swift语言使用的前端编译器是swiftc,而oc语言使用的前端编译器是Clang,但它们都会将源代码编译为IR中间代码,交给LLVM,而LLVM会输出指定硬件架构(如手机arm64、电脑x86_64)的.O 机器可执行文件。
oc的clang编译流程,我们在LLVM入门中分析得十分清楚。
- 现在,我们开始
swift的编译全流程分析。
1.1 语法命令
- 打开
终端,输入swiftc -h,查看语法命令:
-dump-ast 语法和类型检查,打印AST语法树
-dump-parse 语法检查,打印AST语法树
-dump-pcm 转储有关预编译Clang模块的调试信息
-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 输出一个LLVM的BC文件
-emit-executable 输出一个可执行文件
-emit-imported-modules 展示导入的模块列表
-emit-ir 展示IR中间代码
-emit-library 输出一个dylib动态库
-emit-object 输出一个.o机器文件
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen 输出一个.sib的原始SIL文件
-emit-sib 输出一个.sib的标准SIL文件
-emit-silgen 展示原始SIL文件
-emit-sil 展示标准的SIL文件
-index-file 为源文件生成索引数据
-parse 解析文件
-print-ast 解析文件并打印(漂亮/简洁的)语法树
-resolve-imports 解析import导入的文件
-typecheck 检查文件类型
- 新建一个
HTDemo的swift项目,新建HTPerson.swift文件,测试代码:
class HTPerson {
var age: Int = 18
var name: String = "ht"
}
let t = HTPerson()
拓展命令:
- 将
打印结果,输出为文档的命令:命令语句后 + `>> ./XXX && open XXX` // 命令语句: swiftc -emit-sil HTPerson.swift // 输出文件: >> ./HTPerson.sil // 打开文件: open HTPerson.sil 例如: swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil && open HTPerson.sil
- 另一个相同
命令:命令语句后 + ` | col -b > XXX` // 命令语句为:`swiftc -print-ast HTPerson.swift` // 输出文档为`ast.swift` 例如: swiftc -dump-ast HTPerson.swift | col -b > ast.swift
1.2 Swift编译流程

Swift编译流程
- 将
swift源码编译为AST语法树
swiftc -dump-ast HTPerson.swift >> ast.swift- 生成
SIL源码
swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil- 生成
IR中间代码
swiftc -emit-ir HTPerson.swift >> ir.swift- 输出
.o机器文件
swiftc -emit-object HTPerson.swift(ps:以上是各环节
关键命令,其他命令可自行尝试和查看)
1.3 SIL分析
SIL(
Swift intermediate language):swift中间语言
调用swiftc -emit-sil HTPerson.swift >> ./HTPerson.sil命令,生成HTPerson.sil文件。用VSCode打开SIL文件
1.3.1 main函数分析
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// 创建全局变量HTPerson
alloc_global @$s8HTPerson1tA2ACvp // id: %2
// 读取全局变量HTPerson地址,赋值给%3
%3 = global_addr @$s8HTPerson1tA2ACvp : $*HTPerson // user: %7
// metatype读取HTPerson的Type(Metadata),赋值给%4
%4 = metatype $@thick HTPerson.Type // user: %6
// 将HTPerson.__allocating_init() 函数地址给%5
%5 = function_ref @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %6
// 调用%5函数,将返回值给%6
%6 = apply %5(%4) : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson // user: %7
// 将%6存储到%3 (%3是HTPerson类型)
store %6 to %3 : $*HTPerson // id: %7
// 构建Int并return
%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'
@main标识当前HTPerson.swift文件的入口函数,SIL标识符号名称以@作为前缀%0,%1...在SIL中也叫寄存器,类似代码中的常量,一旦赋值后不可修改。如果SIL中还要继续使用,就需要使用新的寄存器
(此寄存器是虚拟的,只作为临时标识,最终运行到机器,会使用真的寄存器)
1.3.2 实例化分析
-
HTPerson()实际调用如下:
// HTPerson.__allocating_init()
sil hidden [exact_self_class] @$s8HTPersonAACABycfC : $@convention(method) (@thick HTPerson.Type) -> @owned HTPerson {
// %0 "$metatype"
bb0(%0 : $@thick HTPerson.Type):
// 读取HTPerson的`alloc_ref`方法地址,给%1
%1 = alloc_ref $HTPerson // user: %3
// 读取HTPerson.init()函数地址,给%2
%2 = function_ref @$s8HTPersonAACABycfc : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %3
// 调用`alloc_ref`创建一个HTPerson实例对象,给%3
%3 = apply %2(%1) : $@convention(method) (@owned HTPerson) -> @owned HTPerson // user: %4
// 返回%3(HTPerosn类型)
return %3 : $HTPerson // id: %4
} // end sil function '$s8HTPersonAACABycfC'
- 类似的
SIL分析,可以多看几个实例,慢慢找感觉。
2. 类结构
对象初始化
- OC:
[[HTPerosn alloc] init]
一般alloc申请内存空间并创建对象,init对象进行统一初始化处理。- Swift:
HTPerson()
直接()就完成了对象的创建。
我们一起去探索Swift的对象创建流程:
2.1 对象的创建(alloc)
创建
测试代码,实例化处加断点:
image.png顶部
Debug->Debug workflow->Always Show Disassembly,勾选汇编模式:
image.png
运行代码,可以看到是调用了__allocating_init()进行的实例化。
image.png添加
__allocating_init符号断点,继续运行,发现内部调用了swift_allocObject:
image.png添加
swift_allocObject符号断点,继续运行,发现内部调用了_swift_allocObject_后,继续调用swift_slowAlloc:
image.png添加
swift_slowAlloc符号断点,继续运行,发现内部调用了malloc_zone_malloc进行内存申请:
image.png
- swift对象
创建流程:
__allocating_init->swift_allocObject->_swift_allocObject_->swift_slowAlloc->malloc_zone_malloc
VSCode调试
验证:
- VSCode打开
Swift源码,搜索_swift_allocObject_,加入断点。
image.png
run起来后,在底部编辑区,逐行输入:
(ps:编辑器会判断语法是否结束(花括号对称),来判断你输入是否完成)class HTPerson { var age: Int = 18 var name: String = "ht" }再输入
var p = HTPerson()创建变量,按回车,会进入断点:
image.png
- 进入
swift_slowAlloc内部,可以看到是申请了size大小的堆空间,并返回了空间的指针地址
image.png
这就是
swift的alloc,申请内存空间并返回一个object指针。返回后,使用reinterpret_cast强转为HeapObject类型。
// 堆中申请内存空间,地址返回给object
auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));
reinterpret_cast:强制类型转换符【用法】
new_type a = reinterpret_cast <new_type> (value)
- 将
value的值转成new_type类型的值,a和value的值一模一样,比特位不变。reinterpret_cast用在任意指针(或引用)类型之间的转换,以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
注意: 此时object是强转的HeapObject类型,实际是一个指向内存空间的对象指针,而HeapObject需要使用metadata进行初始化
// object对象类型为HeapObject,通过metadata(元数据)初始化
new (object) HeapObject(metadata);

2.2 类的大小
在探索init的初始化操作之前,我们先了解一下类的大小。
- 首先,通过
Xcode工程打印类的大小:
【总大小】
40字节(分别使用MemoryLayout和class_getInstanceSize打印大小)
image.png【age】:是
struct类型,64位系统下与Int64大小一样。占8字节
image.png【name】: 是
struct类型,打印发现,占16字节
- 其次,通过
VSCode编译,查看大小:
image.png
Q:
OC类的大小,就是isa指针大小,8字节,为什么Swift类是16字节?
- 最后,探索
Swift类的组成(16字节):
VSCode点击进入HeapObject结构:
image.png【metadata】:是
HeapMetedata类型,进入探究:TargetHeapMetadata->TargetMetadata,是struct结构体。
结构体的大小,由内部属性决定,当前TargetMetadata结构体只有Kind一个属性,类型为StoredPointer(本质是unsigned long类型,8字节)
image.png【refCounts】:是
InlineRefCounts类型,进入探究:InlineRefCounts->RefCounts,是class类型,占8字节。swift也使用ARC进行内存管理。
image.png
HeapObject结构:
struct HeapObject { let metadata: UnsafeRawPointer // 8字节 let strongRef: UInt32 // 4字节 let weakRef: UInt32 // 4字节 【... 自定义属性 ...】 // ... }
- 总结:
swift类本质是HeapObjectHeapObject默认大小为16字节:metadata(struct)8字节和refCounts(class)8字节HTPerson的age(Int)占8字节,name(String)占16字节所以
HTPerson的size为40字节
2.3 对象的初始化(init)
- alloc
申请内存空间并拿到空间地址后,通过metadata(元数据)初始化HeapObject对象。

-
简版流程图:
image.png
swift类结构:
struct swift_class_t {
unsigned long Kind; // 8字节 (swift中是Kind,OC中是isa)
void * Superclass; // 8字节 父类
void * CacheData[2]; // 16字节 缓存数据(2个元素)
void * Data ; // 8字节 自定义数据
uint32_t Flags; // 4字节
uint32_t InstanceAddressPoint; // 4字节
uint32_t InstanceSize; // 4字节
uint16_t InstanceAlignMask; // 2字节
uint16_t Reserved; // 2字节
uint32_t ClassSize; // 4字节
uint32_t ClassAddressPoint; // 4字节
void * Description; // 8字节 描述
}
- 附上
完整流程图。(大图,放大观看)
image.png
本节,熟悉了swift编译流程和swift类结构,关于swift的探索,才刚刚起步 💪


















