LLVM介绍
LLVM的设计
Clang
iOS编绎器架构
Object-C/C/C++使用的编辑器前端是Clang,swift是Swift,后端都是LLVM。
编绎流程
创建一个工程
终端cd到main.m所在的目录,输入如下指令:
clang -ccc-print-phases main.m
效果如下:
- 0:输入文件,找到源文件;
- 1:预处理阶段:包括宏的替换和头文件的导入等
- 2:编绎阶段:进行词法分析、语法分析和语义分析,最终生成中间代码(IR)
- 3:后端代码生成器:生成机器语言,并且进行机器相关的代码优化;LLVM会对一个个的pass进行优化,每个pass做一些事情,最终生成汇编代码;
- 4:生成目标文件;
- 5:链接:链接动态库和静态库,生成可执行文件;
- 6:通过不同的架构,生成对应的可执行文件。
预处理阶段
首先看看main.m的内容
使用如下命令对main.m进行预处理:
clang -E main.m >> main2.m
得到的结果如下:
- 可以看到原来main.m中短短的几行代码经过预编绎处理后变成了1113行代码;
#import <stdio.h>将stdio.h里面的内容copy到了main2.m文件中,增加的这么代码主要是它里面的代码;#define C 30在预编绎时会把代码中用到的C替换成30;typedef int JS_INT ;不会在预编绎时把JS_INT替换成int;可以利用这个特性,将我们代码中的一些重要方法替换成别的方法名称。
编绎阶段
词法分析
通过如下指令对main.m进行词法分析处理,查看编绎阶段做了什么
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
可以看到,词法分析会把我们所写的代码按关键字、方法名、变量名、标点符号等等进行切割来进行分析.
语法分析
通过如下指令对main.m进行语法分析
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
语法分析的任务是验证语法是否正确,在词法分析的基础上将单词序列组合成各类语法短语,如程序、语句、表达式等等;然后中将所有结点组合成
抽象语法树.
在进行词法分析和语法分析时,如果头文件找不到可以指定头文件
clang -isysroot 自已的SDK路经 -fmodules -fsyntax-only -Xclang -ast-dump main.m
中间代码
中间代码生成
完成以上步骤就可以开始生成中间代码(IR)了,代码生成器会将语法树自顶向下遍历翻译成LLVM的IR,通过以下命令可以生成.ll的文本文件
clang -S -fobjc-arc -emit-llvm main.m
得到main.ll文件
使用Xcode打开main.ll文件(也可以用sublime Text打开)
下面介绍一些IR的基本语法
- @全局标识;
- %局部标识;
- alloca开辟空间;
- align内存对及;
- i32 32个bit,4个字节;
- store 写入内存;
- load读取内存;
- call调用函数;
- ret调用函数。
有兴趣的同学可以根据这些基本语法去阅读一下IR代码,这里就不多介绍了.
中间代码的优化
在Xcode项目中,可以通过Apple Clang - Code generation的Optimization Level来对debug或release下的代码进行优化。
优化等级分为以下几种:
IR的优化
我们的LLVM优化等级分为-O0-O1-O2-O3-Os(第一个字母为大写的O)
通过以下代码可以生成优化后的IR代码:
clang -Os -S -fobjc-arc -emit-llvm main.m -o main1.ll
优化后的代码比上面没有优化的要少了很多代码。
bitCode的优化
Xcode 7以后开启bitcode苹果会做进一步的优化,生成.bc的中间代码,我们可以通过以下指令将IR优化后的代码生成.bc代码:
clang -emit-llvm -c main1.ll -o main1.bc
得到一个main1.bc文件,这个文件是经过bitcode优化以后的。
汇编代码
通过上面的操作我们得到了main.m、mian1.ll和main1.bc文件,这些通过这些文件可以生成汇编代码.
- clang -S -fobjc-arc main1.bc -o mainBC.s
- clang -S -fobjc-arc main1.ll -o mainLL.s
- clang -Os -S -fobjc-arc main.m -o main.s
通过以上指令分别生成心以下代码:
分别打开这三个文件我们发现mainBC.s和mainLL.s文件总共有38行代码,main.s只有30行代码,说明第三个指令在生成汇编代码时进行了优化.
生成目标文件
目标文件的生成是汇编以汇编作为输入,将汇编代码转换为机器代码,最后输出目标文件 通过以下指令生成目标文件:
clang -fmodules -c main.s -o main.o
再通过以下指令查看main.o目标不文件:
xcrun nm -nm main.o
main函数中的print是调用的其他库的,所以是undefined external的
- undefined表示在当前文件暂时找不到符号;
- external表示这个符号是外部可以访问的。
生成可执行文件
通过以下指令,连接器把编绎产生的.o文件和.dylib、.a文件生成一个mach-o文件
clang main.o -o main
查看main文件
xcrun nm -nm main
可以看到print函数指明了去libSystem中查找.
总结
LLVM就是苹果开发的一套将前后端分离的编绎器,Clang就是LLVM的一个子项目,以及知道了平常我们写的代码是通过预编绎-> 编绎(词法分析和语法分析)->中间代码生成和优化-> 生成汇编代码-> 生成目标文件-> 生成可执行文件,这一系列操作,最后得到了机器可以识别的mach-o文件.