LLVM概述
LLVM
是以C++
编写的架构编译器(compiler
)的框架系统- 从以下方面优化任意语言编写的的程序
- 编译时间
compile time
- 链接时间
link time
- 运行时间
run time
- 空闲时间
idle time
- 编译时间
传统编译器设计

编译器前端 Frontend
编译器前端的任务是 解析源代码
- 检查代码正确性
- 词法分析
- 语法分析
- 语义分析
- 构建抽象语法树
LLVM
的前端还会生成 中间代码(intermediate representation
,IR)
优化器 Optimizer
优化器端的任务是改善代码的运行时间,例如消除冗余运算等。
编译器后端 Backend
/ 代码生成器CodeGenerator
- 将代码映射到目标指令集
- 生成机器语言
- 机器相关的代码优化
iOS的编译器架构
OC
、c
、c++
使用的编译器前端是clang
;Swift
使用的是Swift
;后端都是LLVM
。

LLVM的设计
传统的编译器设计把前端、优化器、后端集成在一起,需要支持新语言或新硬件结构时就必须重新开发一套编译器。LLVM
的设计模式很好的解决了这种不便。
LLVM
的编译器前端会生成通用的中间代码(intermediate respresentation
IR)- 优化器对中间代码处理不需要改变
- 需要支持新语言时只需要单独开发前端
- 需要支持新架构时只需要单独编写后端

Clang
LLVM
中的一个子项目- 基于
LLVM
的轻量级编译器 - 替代
GCC
,提供更快的编译速度 - 负责编译
OC
、C
、C++
LLVM
架构的编译器前端
编译流程
打印编译流程
clang -ccc-print-phases main.m

- 0、输入文件:找到源文件
- 1、预处理:包括替换宏和导入头文件
- 2、编译阶段:词法分析、语法分析、语义分析,最终生成IR
- 3、后端:通过一个个Pass去优化,每个Pass完成一部分功能,最终生成汇编代码
- 4、生成目标文件
- 5、链接:链接需要的动态库和静态库,生成目标文件
- 6、通过不同架构,生成对应的可执行文件
预处理
clang -E main.m
执行后可以看到头文件导入以及宏被替换。
词法分析
预处理后会进行词法分析,词法分析会把代码切片成一个个token
。
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
执行后可以看到每个词(token
)所在的位置

语法分析
语法分析会验证语法是否正确,在词法分析的基础上把单词组合成各种语法短语,然后把节点组成抽象的语法树(Abstract Syntax Tree,AST)
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
如果找不到头文件,可以手动导入SDK
clang -isysroot SDK路径 -fmodules -fsyntax-only -Xclang -ast-dump main.m

生成中间代码IR(intermediate representation)
处理完上述步骤,代码生成器(Core Generator
)会根据语法树自顶向下生成LLVM IR
。OC会在这里进行runtime
的桥接:property
的合成、ARC
处理等。
clang -S -fobjc-arc -emit-llvm main.m
LLVM
的优化级别有 -O0
-O1
-O2
-O3
-Os
clang -Os -S -fobjc-arc -emit-llvm main.m
执行指令可以得到.ll
文件
- IR基本语法
@ 全局标示
% 局部标示
alloca 开辟空间
align 字节对齐
i32 32位,4字节
store 写入内存
load 读取数据
call 调用函数
ret return
查看未优化的main.ll

优化后的main.ll

bitCode
xcode7
以后开启了bitCode
,苹果对代码做了进一步优化并生成.bc
文件。通过.ll
文件生成.bc
文件。
clang -emit-llvm -c main.ll -o main.bc
生成汇编代码
可以通过.bc
或.ll
文件生成汇编代码
clang -S -fobjc-arc main.bc -o main.s
clang -S -fobjc-arc main.ll -o main.s
生成汇编代码的时候也可以进行优化
clang -Os -S -fobjc-arc main.bc -o main.s
生成目标文件
输入汇编代码,转化成机器代码,最后输出目标文件。
clang -fmodules -c main.s -o main.o
查看main.o中的符号
xcrun nm -nm main.o
可以看到print
函数、test
函数、main
函数

_printf
可以看到undefined extranal
标示undefined
代表它在当前文件找不到extranal
代表它可以外部访问
生成可以执行文件
把编译的.o
文件和dylib.a
文件连接,生成mach-o
文件
clang main.o -o main
执行可执行文件,可以看见输出了正确结果
