1、LLVM

1.1、什么是LLVM

page2image4879152.png
LLVM官网 第一句话所介绍的:The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. (LVVM项目是模块化、可重用的编译器以及工具链技术的集合。)
关于LLVM这个名称官网也做了说明:The name “LLVM” itself is not an acronym; it is the full name of the project.(LLVM这个名字本身不是首字母的缩写;它是项目的全名。)

1.2、传统编译器的架构

image.png
传统的编译器一般分为三个部分:前端、优化器和后端
前端(Frontend):词法分析、语法分析、语义分析、生成中间代码
优化器(Optimizer):中间代码优化
后端(Backend):生成机器代码

1.3、LLVM架构

LLVM支持多种开发语言,包括C/C++/ObjC、Swift、Fortran、Haskell等,通过不同的前端把源码转化成统一的中间代码,再通过不同的后端生成适合不同平台使用的机器代码。
image.png
LVVM的架构有一下特点:
1、不同的前端后端使用统一的中间代码 LLVM Intermediate Representation (LLVM IR)
2、如果需要支持一种新的编程语言,那么只需要实现一个新的前端
3、如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
4、优化阶段是一个通用的阶段,它针对的是统一的 LLVM IR,不论支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改。

2、Clang

2.1、什么是Clang

Clang 是LLVM项目的一个子项目,它基于LLVM架构的C/C++/Objective-C编译器前端。
相比于GCC,Clang具有如下优点:
1、编译速度快:在某些平台上,Clang的编译速度显出的快过GCC(Debug模式下编译OC速度比GCC快3倍)
2、占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
3、模块化设计:Clang采用基于库的模块化设计,易于IDE继承及其他用途的重用
4、诊断信息可读性强:在编译过程中,Clang创建并保留了大量详细的元数据(metadata),有利于调试和错误报告
5、设计清晰简单,容易理解,易于扩展增强

2.2、Clang与LLVM

Clang是LLVM架构的前端。广义上的LLVM指的是整个LLVM架构,狭义上的LLVM指的是LLVM架构的后端(代码优化、目标代码生成等)
image.png
LLVM把编程语言转换成机器码的过程如下:
image.png

*Pass:中间代码优化

3、OC源文件的编译过程

3.1、查看编译过程

新建一个命令行项目,查看main.m文件的编译过程,命令行输入 $ clang -ccc-print-phases main.m

  1. +- 0: input, "main.m", objective-c
  2. +- 1: preprocessor, {0}, objective-c-cpp-output
  3. +- 2: compiler, {1}, ir
  4. +- 3: backend, {2}, assembler
  5. +- 4: assembler, {3}, object
  6. +- 5: linker, {4}, image
  7. 6: bind-arch, "x86_64", {5}, image

简单分析编译过程如下: 第1步:找到源代码main.m 第2步:预处理阶段,替换#import、#include、#define 第3步:编译成中间代码 IR 第4步:后端生成目标代码(汇编等) 第5步:生成目标代码 第6步:链接静态库、动态库 第7步:生成适合某个架构的代码

3.2、查看 preprocessor(预处理)的结果

将main.m文件中的代码修改如下:

  1. #include <stdio.h>
  2. #define AGE 40
  3. int main(int argc, const char * argv[]) {
  4. int a = 10;
  5. int b = 20;
  6. int c = a + b + AGE;
  7. return 0;
  8. }

查看预处理结果:$ clang -E main.m

  1. ......
  2. 省略stdio.h头文件内容
  3. ......
  4. int main(int argc, const char * argv[]) {
  5. int a = 10;
  6. int b = 20;
  7. int c = a + b + 40;
  8. return 0;
  9. }

引入了stdio.h同文件的内容,并且替换了宏定义

3.3、词法分析

词法分析就是将代码生成多个Token,查看词法分析结果:
$ clang -fmodules -E -Xclang -dump-tokens main.m

  1. annot_module_include '#include <stdio.h>
  2. #define AGE 40
  3. int main(int argc, const char * argv[]) {
  4. int a = 10;
  5. ' Loc=<main.m:8:1>
  6. int 'int' [StartOfLine] Loc=<main.m:12:1>
  7. identifier 'main' [LeadingSpace] Loc=<main.m:12:5>
  8. ...... 省略部分打印
  9. identifier 'a' [LeadingSpace] Loc=<main.m:13:9>
  10. equal '=' [LeadingSpace] Loc=<main.m:13:11>
  11. numeric_constant '10' [LeadingSpace] Loc=<main.m:13:13>
  12. semi ';' Loc=<main.m:13:15>
  13. int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:14:5>
  14. identifier 'b' [LeadingSpace] Loc=<main.m:14:9>
  15. equal '=' [LeadingSpace] Loc=<main.m:14:11>
  16. numeric_constant '20' [LeadingSpace] Loc=<main.m:14:13>
  17. semi ';' Loc=<main.m:14:15>
  18. int 'int' [StartOfLine] [LeadingSpace] Loc=<main.m:15:5>
  19. ...... 省略部分打印

比如其中:

  1. identifier 'a' [LeadingSpace] Loc=<main.m:13:9>

就代表了 a 是在main.m 的第13行的第9个字符,这样如果代码出错的话就可以快速定位到出错位置。

3.4、语法树-AST

在main.m中添加函数:

  1. void test(int a, int b) {
  2. int c = a + b - 3;
  3. }

查看test函数的语法树部分:$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  1. `-FunctionDecl 0x7fe49b192f78 <line:20:1, line:22:1> line:20:6 test 'void (int, int)'
  2. |-ParmVarDecl 0x7fe49b192df8 <col:11, col:15> col:15 used a 'int'
  3. |-ParmVarDecl 0x7fe49b192e78 <col:18, col:22> col:22 used b 'int'
  4. `-CompoundStmt 0x7fe49b1931a8 <col:25, line:22:1>
  5. `-DeclStmt 0x7fe49b193190 <line:21:5, col:22>
  6. `-VarDecl 0x7fe49b193058 <col:5, col:21> col:9 c 'int' cinit
  7. `-BinaryOperator 0x7fe49b193170 <col:13, col:21> 'int' '-'
  8. |-BinaryOperator 0x7fe49b193130 <col:13, col:17> 'int' '+'
  9. | |-ImplicitCastExpr 0x7fe49b193100 <col:13> 'int' <LValueToRValue>
  10. | | `-DeclRefExpr 0x7fe49b1930c0 <col:13> 'int' lvalue ParmVar 0x7fe49b192df8 'a' 'int'
  11. | `-ImplicitCastExpr 0x7fe49b193118 <col:17> 'int' <LValueToRValue>
  12. | `-DeclRefExpr 0x7fe49b1930e0 <col:17> 'int' lvalue ParmVar 0x7fe49b192e78 'b' 'int'
  13. `-IntegerLiteral 0x7fe49b193150 <col:21> 'int' 3

*Decl:声明、Stmt:语句、Operator:操作符

其中FunctionDecl下面包含了ParmVarDecl、ParmVarDecl、CompoundStmt这三个节点,CompoundStmt下面又包含了其他节点,这样就形成了一个树的结构:
image.png

3.5、LVVM IR

3.5.1、IR分类

LLVM IR 有3种表示形式(但本质是等价的),其中:
text:便于阅读的文本格式,类似于汇编语言,拓展名.ll,终端命令:$ clang -S -emit-llvm main.m
memory:内存格式
bitcode:二进制格式,拓展名.bc,终端命令:$ clang -c -emit-llvm main.m

3.5.2、IR基本语法

注释以分号开头 ;
全局标识以@开头,局部标识符以%开头
alloca,在当前函数栈帧中分配内存
i32,32bit,4个字节的意思
align,内存对其
store,写入数据
load,读取数据

3.5.3、代码分析

以text形式为例,查看转换后的中间代码:

  1. define void @test(i32 %0, i32 %1) #0 {
  2. %3 = alloca i32, align 4
  3. %4 = alloca i32, align 4
  4. %5 = alloca i32, align 4
  5. store i32 %0, i32* %3, align 4
  6. store i32 %1, i32* %4, align 4
  7. %6 = load i32, i32* %3, align 4
  8. %7 = load i32, i32* %4, align 4
  9. %8 = add nsw i32 %6, %7
  10. %9 = sub nsw i32 %8, 4
  11. store i32 %9, i32* %5, align 4
  12. ret void
  13. }

简单分析:

  1. define void @test(i32 %0, i32 %1) #0 {
  2. %3 = alloca i32, align 4 ; 定义了一个局部变量,int x
  3. %4 = alloca i32, align 4 ; 定义了一个局部变量,int y
  4. %5 = alloca i32, align 4 ; 定义了一个局部变量,int z
  5. store i32 %0, i32* %3, align 4 ; 把第1个参数赋值给x, x = a
  6. store i32 %1, i32* %4, align 4 ; 把第2个参数赋值给y, y = b
  7. %6 = load i32, i32* %3, align 4 ; a
  8. %7 = load i32, i32* %4, align 4 ; b
  9. %8 = add nsw i32 %6, %7 ; a + b
  10. %9 = sub nsw i32 %8, 3 ; a + b - 3
  11. store i32 %9, i32* %5, align 4 ; 把%9赋值给zz就是源码里的c
  12. ret void
  13. }

了解更多LLVM IR语法可以查看 官方文档

4、应用与实践

4.1、libclang、libTooling

使用LLVM中的这两个插件,可以进行语法树分析和语言转换(OC转Swift)等功能。
官方参考:https://clang.llvm.org/docs/Tooling.html

4.2、Clang插件开发

可以开发Clang插件,实现代码检查(命名规范、代码规范)等。
官方参考:
https://clang.llvm.org/docs/ClangPlugins.html
https://clang.llvm.org/docs/ExternalClangExamples.html
https://clang.llvm.org/docs/RAVFrontendAction.html

4.3 Pass开发

实现代码优化、代码混淆等。
官方参考:
https://llvm.org/docs/WritingAnLLVMPass.html

4.4 开发新的编程语言

参考:
https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/