LLVM概述
LLVM是架构编译器的框架系统,以C++编写而成,用于优化任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time)。
LLVM的设计
LLVM的一大特色就是,有着独立的、完善的、严格约束的中间代码表示。这种中间代码,就是LLVM的字节码,是LLVM抽象的精髓,前端生成这种中间代码,后端自动进行各类优化分析,让用LLVM开发的编译器,都能用上最先见的后端优化技术。
-
Frontend
编译器前端的任务是解析源代码(编译阶段),它会进行 词法分析、语法分析、语义分析、检查源代码是否存在错误,然后构建抽象语法树(Abstract Syntax Tree AST),LLVM的前端还会生成中间代码(intermediate representation,简称IR),可以理解为llvm是编译器 + 优化器, 接收的是IR中间代码,输出的还是IR,给后端,经过后端翻译成目标指令集
-
优化器 Optimizer
优化器负责进行各种优化,改善代码的运行时间,例如消除冗余计算等
-
后端 Backend(代码生成器 Code Generator)
将代码映射到目标指令集,生成机器代码,并且进行机器代码相关的代码优化。
LLVM将中间代码优化这个流程做到了极致,LLVM工具链,不但可以生成所支持的各个后端平台的代码,更可以方便的将各语言的前端编译后的模块链接到一起,你可以方便的在你的语言中调用C函数。
iOS的编译器架构
OC 、C、C++ 使用的编译器前端是 Clang ,后端都是LLVM,如下图所示
Clang概述
Clang 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C/Objective C++ 编译器。
Clang 编译流程
通过命令可以打印源码的编译阶段
clang -ccc-print-phases 源文件路径
得到结果如下:
输入文件:找到源文件
input, "main.m", objective-c
预处理阶段:这个过程处理包括宏的替换,头文件的导入
preprocessor, {0}, objective-c-cpp-output
编译阶段:进行词法分析、语法分析、检测语法是否正确,最终生成IR
compiler, {1}, ir
后端:这里LLVM会通过一个一个的pass去优化,每个pass做一些事情,最终生成汇编代码
backend, {2}, assembler
汇编代码生成目标文件
assembler, {3}, object
链接:链接需要的动态库和静态库,生成可执行文件
linker, {4}, image(镜像文件)
绑定:通过不同的架构,生成对应的可执行文件
bind-arch, "x86_64", {5}, image
预处理阶段
这个阶段主要是处理包括宏的替换,头文件的导入,可以执行命令 clang -E 源文件路径
,执行完毕可以看到头文件的导入和宏的替换。define则在预处理阶段会被替换,所以经常被是用来进行代码混淆,目的是为了 app 安全,实现逻辑是:将 app 中核心类、核心方法等用系统相似的名称进行取别名,然后在预处理阶段就被替换,来达到代码混淆的目的。
clang -E main.m
编译阶段
词法分析
预处理完成后就会进行词法分析,这里会把代码切成一个个 token,比如大小括号、等于号、还有字符串等,
clang -fmodules -fsyntax-only -Xclang -dump-tokens 源文件路径
语法分析
语法分析,它的任务是验证语法是否正确,在词法分析的基础上将单词序列组合成各类此法短语,如程序、语句、表达式 等等,然后将所有节点组成抽象语法树(Abstract Syntax Tree AST),语法分析程序判断程序在结构上是否正确。
clang -fmodules -fsyntax-only -Xclang -ast-dump 源文件路径
-
FunctionDecl
函数 -
ParmVarDecl
参数 -
CallExpr
调用一个函数 -
BinaryOperator
运算符
生成中间代码IR
完成以上步骤后,就开始生成中间代码 IR 了,代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成 LLVM IR,可以通过下面命令可以生成 .ll
的文本文件,查看 IR 代码。
clang -S -fobjc-arc -emit-llvm 源文件路径 -o 输出文件路径
Objective-C 代码在这一步会进行 runtime
桥接,property
合成、ARC 处理等。
IR 基本语法
-
@
全局标识 -
%
局部标识 -
alloca
开辟空间 -
align
内存对齐 -
i32
32bit,4个字节 -
store
写入内存 -
load
读取数据 -
call
调用函数 -
ret
返回
Optimization Level
当然,IR 文件在 Objective-C 中是可以进行优化的,一般设置是在 target - Build Setting - Optimization Level(优化器等级)中设置。
LLVM 的优化级别分别是
-O0
、 -O1
-O2
-O3
-Os
,下面是带优化的生成中间代码 IR 的命令
clang -Os -S -fobjc-arc -emit-llvm 源文件路径 -o 输出文件路径
BitCode
xcode7 以后开启 bitcode
,苹果会做进一步优化,生成 .bc
的中间代码,我们通过优化后的 IR 代码生成 .bc
代码
clang -emit-llvm -c .ll源文件 -o .bc文件
后端
LLVM 在后端主要是会通过一个个的 Pass
去优化,每个 Pass
做一些事情,最终生成汇编代码。我们通过最终的 .bc
或者 .ll
代码生成汇编代码:
clang -S -fobjc-arc .bc 文件 -o .s 文件
clang -S -fobjc-arc .ll 文件 -o .s 文件
生成汇编代码也可以进行优化
clang -Os -S -fobjc-arc .m 文件 -o .s 文件
生成目标文件
目标文件的生成,是汇编器以汇编代码作为插入,将汇编代码转换为机器代码,最后输出目标文件(object file)
clang -fmodules -c .s 文件 -o .o 文件
可以通过 nm
命令,查看下 main.o
中的符号
xcrun nm -nm .o 文件
undefined
表示在当前文件暂时找不到符号external
表示这个符号是外部可以访问的
链接
链接主要是链接需要的动态库和静态库,生成可执行文件,其中静态库会和可执行文件合并,动态库是独立的。连接器把编译生成的 .o
文件和 .dyld
、.a
文件链接,生成一个 mach-o
文件
clang .o 文件 -o 输出文件
LLVM 工程编译
下载源码
git clone https://github.com/llvm/llvm-project.git
安装 cmake
brew install cmake
通过 xcode 编译 LLVM
-
cmake
编译成 Xcode 项目cd llvm-project mkdir build_xcode cd build_xcode cmake -G Xcode ../llvm
打开
build_xcode/LLVM.xcodeproj
文件选择
Automatically Create Schemes
(目前 xcode version 13.2.1)选择
ALL_BUILD Secheme
进行编译
通过 ninja 编译 LLVM
-
安装
ninja
brew install ninja
-
在 llvm-project 目录下创建文件夹 build_ninja 、 llvm-release
cd llvm-project mkdir build_ninja mkdir llvm_release
-
cmake
编译cd build_ninja cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=../llvm_release
-
ninja
运行ninja install