LLVM介绍和Clang编译流程分析

1,395 阅读5分钟

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文件.