背景
抖音上线 Swift 后,编译时偶现Segmentation fault: 11
和Illegal instruction: 4
的错误,CI/CD 和本地均有出现,且重新编译后均可恢复正常。
由于属于编译器层抛出的 Crash,加之提示的错误代码不固定且非必现,一时较为棘手。网上类似错误较多,但Segmentation fault
属于访问了错误内存的通用报错,参考意义较小。和公司内外的团队交流过,也有遇到类似错误,但原因各不相同,难以借鉴。
虽然 Swift 库二进制化后,相关代码不会参与编译,本地出现的概率大大减少,但在 CI/CD/仓库二进制化任务中依旧使用源码,出现问题需要手动重试,影响效率且繁琐,故深入编译器寻求解决方案。
Crash 堆栈

结论
简而言之,是 Swift 代码中将在 OC 中声明为类属性的NSDictionary
变量,当成 Swift 的Dictionary
使用。即一个 immutable 变量当作 mutable 变量使用了。编译器在校验SILInstruction
时出错,主动调用abort()
结束进程或出现EXC_BAD_ACCESS
的 Crash。
准备工作
编译 Swift
由于本地重现过错误,故拉取和本地一致的 swift-5.3.2-RELEASE 版本,同时推荐使用 VSCode 进行调试,Ninja 进行构建。
Ninja 是专注于速度的小型构建系统。
注意事项
提前预留 50G 磁盘空间
首次编译时长在一小时左右,CPU 基本打满
下载&编译源码
brew install cmake ninja
mkdir swift-source
cd swift-source
git clone git@github.com:apple/swift.git
cd swift/utils
./update-checkout --tag swift-5.3.2-RELEASE --clone
./build-script
主要目录

提取编译参数
笔者将相关代码抽离抖音工程, 本地复现编译报错问题后,从 Xcode 中提取编译参数:

VSCode 调试
选择合适的 LLDB 插件,以 CodeLLDB 为例配置如下的 launch.json。
其中args
内容为获取前一步提取的编译参数,批量将其中每个参数用双引号包裹,再用逗号隔开所得。
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/build/Ninja-DebugAssert/swift-macosx-x86_64/bin/swift",
"args": ["-frontend","-c","-primary-file"/*and other params*/],
"cwd": "${workspaceFolder}",
}
]
}
SIL
LLVM
在深入 SIL 之前,先简单介绍 LLVM,经典的 LLVM 三段式架构如下图所示,分为前端(Frontend),优化器(Optimizer)和后端(Backend)。当需要支持新语言时只需实现前端部分,需要支持新的架构只需实现后端部分,而前后端的连接枢纽就是 IR(Intermediate Representation),IR 独立于编程语言和机器架构,故 IR 阶段的优化可以做到抽象而通用。