- 上一节,我们完成了源码编译。本节,我们
探索Swift
的编译流程
和类结构
- swift编译流程
- 类结构
强烈建议
先阅读LLVM入门,再开始本节的阅读
1. swift编译流程
在了解swift编译
流程前,我们需要知道LLVM
是什么(👉 LLVM入门)。
LLVM
是架构编译器
。可以为任何编程语言
独立编写前端
,也可以为任何硬件架构
独立编写后端
。
比如: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)
创建
测试代码
,实例化
处加断点
:
顶部
Debug
->Debug workflow
->Always Show Disassembly
,勾选汇编模式
:
运行
代码,可以看到是调用了__allocating_init()
进行的实例化。
添加
__allocating_init
符号断点,继续运行,发现内部调用了swift_allocObject
:
添加
swift_allocObject
符号断点,继续运行,发现内部调用了_swift_allocObject_
后,继续调用swift_slowAlloc
:
添加
swift_slowAlloc
符号断点,继续运行,发现内部调用了malloc_zone_malloc
进行内存申请:
- swift对象
创建流程
:
__allocating_init
->swift_allocObject
->_swift_allocObject_
->swift_slowAlloc
->malloc_zone_malloc
VSCode调试
验证
:
- VSCode打开
Swift
源码,搜索_swift_allocObject_
,加入断点
。
run
起来后,在底部编辑区
,逐行
输入:
(ps:编辑器
会判断语法
是否结束
(花括号对称),来判断
你输入
是否完成
)class HTPerson { var age: Int = 18 var name: String = "ht" }
再输入
var p = HTPerson()
创建变量,按回车,会进入断点
:
- 进入
swift_slowAlloc
内部,可以看到是申请
了size
大小的堆空间
,并返回了空间的指针地址
这就是
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
打印大小)
【age】:是
struct类型
,64位系统
下与Int64
大小一样。占8字节
【name】: 是
struct类型
,打印发现,占16字节
- 其次,通过
VSCode
编译,查看大小
:
Q:
OC类
的大小
,就是isa指针
大小,8字节
,为什么Swift类
是16字节
?
- 最后,探索
Swift类
的组成(16字节
):
VSCode
点击进入HeapObject
结构:
【metadata】:是
HeapMetedata
类型,进入探究:TargetHeapMetadata
->TargetMetadata
,是struct
结构体。
结构体
的大小
,由内部属性
决定,当前TargetMetadata
结构体只有Kind
一个属性,类型为StoredPointer
(本质是unsigned long
类型,8字节
)
【refCounts】:是
InlineRefCounts
类型,进入探究:InlineRefCounts
->RefCounts
,是class
类型,占8字节
。swift
也使用ARC
进行内存管理
。
HeapObject结构:
struct HeapObject { let metadata: UnsafeRawPointer // 8字节 let strongRef: UInt32 // 4字节 let weakRef: UInt32 // 4字节 【... 自定义属性 ...】 // ... }
- 总结:
swift类
本质是HeapObject
HeapObject
默认大小为16字节
:metadata
(struct)8字节
和refCounts
(class)8字节
HTPerson
的age
(Int)占8字节
,name
(String)占16字节
所以
HTPerson
的size
为40字节
2.3 对象的初始化(init)
- alloc
申请
内存空间并拿到
空间地址
后,通过metadata
(元数据)初始化HeapObject
对象。
-
简版流程图
:
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字节 描述
}
- 附上
完整流程图
。(大图,放大观看)
本节,熟悉了swift编译流程
和swift类结构
,关于swift的探索
,才刚刚起步
💪