LLVM (4) 11.3 LLVM的代码表示:LLVM IR

来源:互联网 发布:好听的淘宝客服昵称 编辑:程序博客网 时间:2024/05/07 00:57
基于历史背景和上下文,让我们深入分析LLVM。设计中最重要的是LLVM中间表示(IR),在编译器中IR被用来表示代码。LLVM IR被设计为支持前文中优化器的中级(mid-level)分析和转换。它被设计考虑多种专用目标,包括:支持轻权运行时优化、跨函数/进程优化、全程序分析和激进的重组转换等。作为一种顶级语言的重要原因是良好定义的语义。为了具体化,这里是一个.ll文件的例子:

<span style="font-size:14px;">define i32 @add1(i32 %a, i32 %b) {entry:    %tmp1 = add i32 %a, %b    ret i32 %tmp1}define i32 @add2(i32 %a, i32 %b) {entry:    %tmp1 = icmp eq i32 %a, 0    br i1 %tmp1, label %done, label %recurserecurse:    %tmp2 = sub i32 %a, 1    %tmp3 = add i32 %b, 1    %tmp4 = call i32 @add2(i32 %tmp2, i32 %tmp3)    ret i32 %tmp4done:    ret i32 %b}</span>

对应的C代码,提供两种不同方式的整数加法:

<span style="font-size:14px;">unsigned add1(unsigned a, unsigned b) {  return a+b;}// Perhaps not the most efficient way to add two numbers.unsigned add2(unsigned a, unsigned b) {  if (a == 0) return b;  return add2(a-1, b+1);}</span>

从这个例子看来LLVM IR是一种低级的类RISC虚拟指令集。像一个真正的RISC指令集一样,LLVM IR支持简单指令(像add、substrate、compare和branch)的线性序列。这些指令以三地址形式表示,这意味着采用一些数字输入和产生存储在不同寄存器的结果。LLVM IR支持标记(label)并且一般看起来像汇编语言的一种奇怪形式。

不像大部分的RISC指令集,LLVM采用一种简单的类型系统(例如,i32是32位整数,i32**是指向32位整数的指针),并且机器的细节被高度抽象。例如,调用会话通过call和ret指令以及显示参数进行抽象。另一个与机器码的重大不同是LLVM IR不使用固定的命名寄存器而是采用通过%标识的、无限的临时变量。

除了被实现作为一种语言,LLVM IR实际上被定义在三种同态形式:文本格式、编译器检测和修改的内存数据结构和高效的、密集的磁盘二进制“bitcode”格式。LLVM项目也提供工具区将磁盘格式从文本转化为二进制:llvm-as命令将文本.ll文件汇编为包含bitcode的.bc文件和llvm-dis命令将.bc文件转化为.ll文件。

编译器的中间表示是有趣的,因为对于编译器的优化器来说它是“完全世界(perfect world)”:不像编译器的前端和后端,优化器不会被特殊的源语言或目标机器所限制。另一方面,它不得不很好被设计去简单地支持前端生成以及足够高效地允许重要优化形成真实目标。

11.3.1 写一个LLVM IR优化
为了给出优化如何工作的直观感觉,一些例子很有必要。很多编译器存在不同的优化,所以很难提供如何解决任意问题的一个统一方法。一般来说大多数优化是一种简单的三部分结构:
  • 寻找一个模式去转化
  • 验证转化对于这种匹配的实例是否安全和正确
  • 进行转化,更新代码
最琐碎的优化是算术标识的模型匹配,例如,对于整数X、X-X简化为0,(X*2)-X简化为X。第一个问题是这些在LLVM IR中是什么样子。例子如下:

<span style="font-size:14px;">%example1 = sub i32 %a, %a%example2 = sub i32 %b, 0%tmp = mul i32 %c, 2%example3 = sub i32 %tmp, %c</span>

对于这些“观测实例(peephole)”,LLVM提供一个指令简化接口,被用来作为很多其它高级转化的实用工具。这些特别的转化在函数SimplifySubInst,如下所示:

<span style="font-size:14px;">//X - 0 -> Xif (match(Op1, m_Zero()))  return Op0;// X -X -> 0if (Op0 == Op1)  return Constant::getNullValue(Op0->getType());// (X*2) - X -> Xif (match(Op0, m_Mul(m_Specific(Op1), m_ConstantInt<2>())))  return Op1;...return 0; // Nothing matched, return null to indicate no transformation.</span>

在这段代码中,Op0和Op1是整数减指令的左右操作数(重要的是这些表示没必要保持IEEE浮点)。LLVM通过C++实现,C++相对于函数语言Objective Caml模型匹配能力不著名,但是C++提供一个相当通用的模板系统去实现相似功能。match函数和m_函数允许我们去形成描述式模式匹配LLVM IR的操作。例如,m_Specific预测仅仅匹配乘法的右手边,与Op1相同。

这三种例子都是模型匹配,如果可以的话函数返回这种替换,或者替换不可能的话将返回空指针。这个函数(SimplifyInstruction)调用是一种分发器,做指令opcode的转换、分发给per-opcode helper函数。这个函数被不同的优化调用。简单的驱动如下:

<span style="font-size:14px;">for (BasicBlock::iterator I = BB->begin(), E = BB->end(); I != E; ++I)  if (Value *V = SimplifyInstruction(I))    I->replaceAllUsesWith(V)</span>

这段代码仅仅循环基本块中每个指令,检查是否它们的任何一个能够简化。如果有的话(SimplifyInstruction返回非空指针),使用replaceAllUsesWith函数去更新代码在一个更简单的形式。


0 0