LLVM的编译流程
在介绍编译流程之前,首先回顾一下LLVM:
LLVM是一个模块化的、可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,它的编译速度比GCC快3倍,其中clang static analyzer主要进行语法分析、语义分析以及生成中间代码,在这个过程中会对代码进行检查,对于出错以及警告的代码会给与标记。此外,LLVM核心库还提供一个优化器,对流行的CPU做生成代码的支持(如x86等机器)。lld是Clang/LLVM的内置链接器,clang必须调用链接器来产生可执行文件。
LLVM相对于其它编译器的特殊性在于它提供一种代码编写良好的中间表示IR,这意味着它可以作为多种语言的后端,这样它就可以在提供语言无关的优化的同时还可以针对不同的硬件设备(CPU)生成对应的机器代码。
在介绍LLVM的编译流程之前,对于编译器的编译链接过程需要有一定的了解,可以参阅这些文章
快速开始——介绍LLVM编译流程
我们从一个例子开始,观察一次编译过程是如何完成的。首先我们编写如下的代码:
#import <Foundation/Foundation.h>
#define DEFINEEight 8
int main(){
@autoreleasepool {
int eight = DEFINEEight;
int six = 6;
NSString* site = [[NSString alloc] initWithString:@"starming"];
int rank = eight + six;
NSLog(@"%@ rank %d", site, rank);
}
return 0;
}
可以看到这段代码输出一个字符串和相加后的数字。将此代码保存为main.m
文件。然后使用clang对其进行编译:
clang -ccc-print-phases main.m
可以看到编译过程输出如下:
- 0步骤获得源代码
main.m
,是OC语言。 - 1步骤是预处理阶段,做相应处理(后面会提到)
- 2步骤是编译阶段
- 3步骤通过后端进行汇编前的处理
- 4步骤汇编阶段
- 5步骤链接阶段,做相应处理
- 6步骤绑定相应变量(或常量等)到硬件机器上(如寄存器)
通过如上步骤可以了解到整个过程以及过程中的一些信息。例如首先进行的预处理操作可以使用如下命令查看具体信息:
clang -E main.m
执行完后可以看到预处理后的样子:
这个过程包括宏的替换,头文件的导入等等。在预处理完成后会进行词法分析,在此步骤会把代码切成一个个Token,比如大小括号,等于号还有字符串等。通过如下命令可以看到词法分析后的结果:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
词法分析结果如下:
接下来是语法分析,验证程序的语法是否正确,然后将所有的节点组成抽象语法树AST:
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
这些步骤完成之后就要开始进行IR中间代码的生成了,代码生成器CodeGen会负责将语法树自顶向下遍历逐步翻译成LLVM IR,IR就是编译过程的前端的输出以及后端的输入:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
此步骤LLVM会去做些优化工作,在Xcode的编译设置里也可以设置优化的级别-01,-03,-0s等,还可以通过编写自己的pass自定制优化解决方案,编写自己的Pass请参阅[这篇文章]
clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
什么是pass呢,可以将pass理解为LLVM优化工作的一个节点,一个节点做一些事,把这些节点加起来就构成了LLVM完整的优化器了。而且当你在Xcode里开启了bitcode的话还会做进一步的优化工作:对于新的后端架构可以使用这份优化后的bitcode去生成。这也是LLVM灵活性的一个体现吧。
开启bitcode优化:
clang -emit-llvm -c main.m -o main.bc
生成汇编的命令:
clang -S -fobjc-arc main.m -o main.s
再生成目标文件:
clang -fmodules -c main.m -o main.o
最后生成可执行文件:
clang main.o -o main
这样通过执行./main
就可以看到程序的执行结果啦!starming rank 14
那么以上就是LLVM的整个编译流程了。对于具体的Clang编译过程中的细节问题,将在后续的文章中进行介绍和讨论。欢迎关注!