什么是LLVM?官网:https://llvm.org
翻译:LLVM项目是模块化和可重用的编译器和工具链技术的集合。尽管名为LLVM,但它与传统虚拟机几乎没有什么关系。“LLVM”这个名字本身不是一个缩写词;它是项目的全称。
创始人:Chris Lattner,也是Swift之父。
一、LLVM架构
1.1. 传统的编译器架构
Frontend: 前端
- 词法分析、语法分析、语义分析、生成中间代码
Optimizer:优化器
- 中间代码优化
Backend:后端
- 生成机器码
1.2. LLVM架构
不管是什么语言,只要遵守LLVM架构规范,最终生成的中间代码格式(LLVM IR)是一样的。
- 不同的前端和后端使用统一的中间代码LLVM Intermedicate Representation(简称:LLVM IR)。
- 如果需要支持一种新的编程语言,那么只需要实现一个新的前端。
- 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端。
- 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改。
相比之下,GCC的前端和后端没分的太开,前端后端耦合在了一起,所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就变得特别困难(排列组合形式)。
LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)。
二、Clang
Clang是LLVM项目的一个子项目(官网:http://clang.llvm.org/),是基于LLVM架构的C/C++/Objective-C编译器前端。
相比于GCC、Clang具有如下优点:
- 编译速度快: 在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GCC快3倍)。
- 占用内存小: Clang生成的AST所占用的内存是GCC的五分之一左右。
- 模块化设计: Clang采用基于库的模块化设计,易于IDE集成及其他用途的重用。
- 诊断信息可读性强: 在编译过程中,Clang创建并保留了大量详细的元数据(metadata),有利于调试和错误定位。
- 设计清晰简单,容易理解,易于扩展增强。
2.1. Clang与LLVM
广义的LLVM: 整个LLVM架构(前端和后端)。
狭义的LLVM: LLVM后端(代码优化、目标代码生成等)。
如果想要自己优化中间代码,只需要插入一段Pass就可以。
2.2. OC源文件的编译过程
- 尝试编译
main.m
- 命令行查看编译的过程:
1 | clang -ccc-pring-phases main.m |
- input: 输入源文件
- preprocessor: 预处理器(把import、include、define等替换掉)
- compiler: 编译成中间代码(IR)
- backend: 生成汇编代码
- assembler: 生成目标代码
- linker: 链接动态库和静态库
- bind-arch: 生成适合对应架构的机器码
查看preprocessor(预处理)的结果,为了方边查看,我们把main文件改造下
控制台输入命令后就可以看到,已经把宏定义的AGE自动替换了。
1 | clang -E main.m |
2.3. 词法分析
词法分析,其实就是把代码生成一个个token。
1 | clang -fmodules -E -Xclang -dump-tokens main.m |
2.4. 语法树
语法分析,生成语法树(Abstract Syntax Tree,缩写:AST),其实就是把词法分析的结果拼接起来。
1 | clang -fmodules -fsyntax-only -Xclang -ast-dump main.m |
- FunctionDecl: 函数声明
- ParmVarDecl: 参数声明
- CompoundStmt/DeclStmt:: 具体语句
- VarDecl: 变量声明
- BinaryOperator: 操作符
2.5. LLVM IR
LLVM IR有3中表示形式(但本质是等价的,就好比水可以有气体、液体、固体3种形态):
text:便于阅读的文本格式,类似于汇编语言,扩展名
.ll
1
clang -S -emit-llvm main.m
memory:内存格式
bitcode:二进制格式,扩展名
.bc
1
clang -c -emit-llvm main.m
编写
main.m
源代码输入命令,生成
main.ll
文件
1 | clang -S -emit-llvm main.m |
IR基本语法:
- 注释以分号
;
开头 - 全局标识符以
@
开头,局部标识符以%
开头 alloca
:在当前函数栈帧中分配内存(创建局部变量)i32
,32bit
:4个字节align
:内存对齐store
:写入数据load
:读取数据
官方语法参考:https://llvm.org/docs/LangRef.html
三、源码
3.1. 源码下载
下载LLVM(大概795MB)
1
git clone https://git.llvm.org/git/llvm.git/
下载Clang(一定要等到LLVM下载完成后再操作,因为Clang要在LLVM项目下面的tools文件夹中下载。大概272.4MB)
1
2cd llvm/tools
git clone https://git.llvm.org/git/clang.git/
Clang没有下载的时候,为什么也能使用?是因为Xcode内置了该工具。之所以再次下载,是为了能够编译出自己的编译器(定制化)。
3.2. 源码编译
安装cmake和ninja(先安装brew,https://brew.sh/),这个工具是为了快速编译LLVM。
1
2brew install cmake
brew install ninja如果ninja安装失败,可以直接从Github获取release版本,然后放入
/usr/local/bin
。生成模板:在LLVM源码同级目录下新建一个
llvm_build
目录(最终会在llvm_build
目录下生成build.ninja
)。1
2cd llvm_build
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=最终LLVM的安装路径(比如:../llvm_release)更多cmake相关选项,可以参考:https://llvm.org/docs/CMake.html。
执行编译指令(编译完毕后,
llvm_build
目录大概21.05GB)。1
ninja
执行安装指令(生成的文件在
llvm_release
目录,安装完毕后,安装目录大概11.92GB)。1
ninja install
完成之后在可以看到clang执行文件(
llvm_release/bin/clang
)。
另外一种编译方式(使用Xcode):
- 生成Xcode项目(速度很慢很慢很慢……)。
1 | cd llvm_xcode |
- 生成之后,可以看到执行文件,用Xcode打开之后选择Scheme名称为
ALL_BUILD
的项目,开始编译,之后在llvm_xcode/Debug/bin
可以看到编译后的文件。
四、应用与实践
LLVM相关工具:libclang、libTooling
- 官方参考:https://clang.llvm.org/docs/Tooling.html
- 应用场景:语法树分析、语言转换等
Clang插件开发:
- 官方参考【如何开发一个Clang插件】:
- 应用场景:代码检查(命名规范,代码规范)等
Pass开发: