什么是 LLVM IR
- ⽆限制的单分配寄存器机器指令集
- 强类型
- 三种常⻅表⽰形式: 1, ⼈类可读的 LLVM 程序集(.ll ⽂件) 2, 密集的 bitcode ⼆进制表⽰(.bc ⽂件) 3, C++ 类
SSA
Static Single Assignment, 我理解的是单一静态赋值,寄存器可能只分配一次.
与之对应的就是多赋值,下面就是一个变量a,被赋值2次.
int a = somefunction();
a++;
既然是2次,那下面的%a 用这样表示.
%a = call i32 @somefunction()
%a = add i32 %a, 1
用llc编译的时候,看到会报错的,
error: multiple definition of local value named ’a’
%a = add i32 %a, 1
正确的应该是,2个不同的变量,或者标识,不一定是%a
%a = call i32 @somefunction()
%a2 = add i32 %a, 1
前端必须跟踪哪个寄存器在代码的任何一位置保存的值.
上面的代码是如何跟踪到新的值, 分号是注释, 下面的%0,1,2必须是连续的,中间少了一个会提示不连续.
; int a
%a = alloca i32 , align 4
; a = somefunction
%0 = call i32 @somefunction()
store i32 %0, i32* %a
; a++
%1 = load i32 * %a
%2 = add i32 %1, 1
store i32 %2, i32* %a
寄存器的值是自动分配,上面的表达式是自动llc等翻译的,不过也可以手动写, 可以看出在llvm内存中,不是SSA.
上面看出来有2次store,实际中好像有点冗余,SROA或者寄存器会自动清除这些冗余的步骤,
SROA:scalar replacement of aggregates 如果可能的话,这个转换将聚合类型(结构或数组)的 alloca 指令分解为每个成员的单个 alloca 指令。然后,如果可能的话,它将单个 alloca 指令转换为简洁的纯标量 SSA 形式.
就是下面的样子.
%0 = call i32 @someFunction()
%1 = add i32 %0, 1
控制流
IR的控制流通过jump、branches来实现. 分支为条件和无条件分支,
条件
先来看看条件
int b = 12;
if (a)
b++;
return b;
控制流请求一个基础的块,每一次循环路径, 当循环路径指定的时候,就执行条件分支.
φ -phi, 念/fi/, φ 节点是SSA构造中使⽤的特殊指令.
IR的入口大概是下面这个样子,可以看到最后一行有2个标识符, br 是branch,lable是跳转到这个命名的块.分别为then和end
entry:
; int b = 12
%b = alloca i32
store i32 12, i32* %b
; if (a)
%0 = load i32 * %a
%cond = icmp ne i32 %0, 0
br i1 %cond , label %then , label %end
then块的内容如下,调用了end块,负责b++
then:
; b++
%1 = load i32 * %b
%2 = add i32 %1, 1
store i32 %2, i32* %b
br label %end
end块的内容如下,return b
end:
; return b
%3 = load i32 * %b
ret i32 %3
加入φ之后,就成了这样了.
简化后的CFG, 就一个select指令.
entry:
%tobool = icmp ne i32 %a, 0
%0 = select i1 %tobool , i32 13, i32 12
ret i32 %0
函数
LLVM函数包含至少一个基础块,
@hello = private constant [13 x i8] c"Hello world!\00"
define i32 @main(i32 %argc , i8 ** %argv) {
entry:
%0 = getelementptr [13 x i8 ]* @hello , i32 0 ,
i32 0
call i32 @puts(i8* %0)
ret i32 0
}
IR如何转化为本地code
Selection DAG
1, 转换(SelectionDAG) 2, 映射到指令(Machine IR) 3, 流到汇编
Instruction Selection
Register allocation
MC Streamer
LLVM重要的几个类
Module
是所有其他 LLVM 中间表示(IR)对象的顶级容器。每个模块直接包含全局变量列表、函数列表、该模块所依赖的库(或其他模块)列表、符号表以及关于目标特征的各种数据。
Function
函数基本上由一个基本块列表、一个参数列表,和一个符号表。
BasicBlock
这表示 LLVM 中的一个基本块。基本块仅仅是一个指令的容器,这些指令按顺序执行。基本块是Value,因为它们被诸如branch和switch表之类的指令引用。BasicBlock 的类型是“ Type::Label_type”,因为基本块表示分支可以跳转到的标签。
GlobalVariable
全局变量是常量指针,指向一个大空间,由 VM 或静态编译器中的链接器分配一个全局variable 可能有一个初始值,这个初始值被复制到 executables. dataarea. 需要全局常量才能有初始化器。
IRBuilder
这提供了一个统一的 API,用于创建指令并将它们插入到基本块中: 或者在 BasicBlock 的末尾,或者在块中的特定迭代器位置。
Type
Type 类的实例是不可变的: 它们一旦被创建,就不会被更改。
ConstantExpr
用表达式使用其他常量值初始化的常量值。
PassManagerBuilder
这个类用于为 c 和 c++ 等语言建立一个标准的优化序列,允许一些 api 以各种方式自定义传递序列
ExecutionEngine
用于执行 LLVM 模块的抽象接口,设计用于支持解释器和实时(JIT)编译器实现。
Read more
https://llvm.org/doxygen/classllvm_1_1Module.html
https://www.cnblogs.com/Five100Miles/p/12824942.html
https://zhuanlan.zhihu.com/p/52724656
4LLVMIRandTransformPipeline.pdf
微信公众号:cdtfug, 欢迎关注一起吹牛逼,也可以加微信号「xiaorik」朋友圈围观。