什么是PHI指令,以及如何在LLVM中使用它

LLVM有phi指令,相当怪异的解释:

'phi'指令用于实现表示函数的SSA图中的φ节点。

通常用于实现分支。 如果我理解正确,就需要进行依赖关系分析,在某些情况下可以避免不必要的加载。 然而,它仍然很难理解它究竟做了什么。

万花筒的例子很好地解释, if情况。 但是,如何实现逻辑运算(如&&||还不是很清楚 。 如果我input以下命令联机llvm编译器:

 void main1(bool r, bool y) { bool l = y || r; } 

最后几行完全混淆了我:

 ; <label>:10 ; preds = %7, %0 %11 = phi i1 [ true, %0 ], [ %9, %7 ] %12 = zext i1 %11 to i8 

看起来像phi节点产生可以使用的结果。 我还有一个印象,就是phi节点只是定义了来自哪个path的值。

有人可以解释什么是Phi节点,以及如何实现|| 用它?

一个phi节点是一个指令,用于根据当前块的前导来select一个值(请看这里查看完整的层次结构 – 它也被用作一个值,它是inheritance的类之一)。

由于LLVM代码的SSA(静态单一赋值)风格的结构,Phi节点是必需的 – 例如,下面的C ++函数

 void m(bool r, bool y){ bool l = y || r ; } 

被翻译成以下IR:(通过clang -c -emit-llvm file.c -o out.bc创build – 然后通过llvm-dis查看)

 define void @_Z1mbb(i1 zeroext %r, i1 zeroext %y) nounwind { entry: %r.addr = alloca i8, align 1 %y.addr = alloca i8, align 1 %l = alloca i8, align 1 %frombool = zext i1 %r to i8 store i8 %frombool, i8* %r.addr, align 1 %frombool1 = zext i1 %y to i8 store i8 %frombool1, i8* %y.addr, align 1 %0 = load i8* %y.addr, align 1 %tobool = trunc i8 %0 to i1 br i1 %tobool, label %lor.end, label %lor.rhs lor.rhs: ; preds = %entry %1 = load i8* %r.addr, align 1 %tobool2 = trunc i8 %1 to i1 br label %lor.end lor.end: ; preds = %lor.rhs, %entry %2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ] %frombool3 = zext i1 %2 to i8 store i8 %frombool3, i8* %l, align 1 ret void } 

那么这里发生了什么? 与C ++代码不同,variablesl可以是0或1,在LLVM IR中,它必须定义一次 。 所以我们检查%tobool是否为true,然后跳转到lor.end或lor.rhs。

最后,我们终于有了||的价值 运营商。 如果我们从入门到达,那么这是真的。 否则,它等于%tobool2的值 – 这正是我们从以下IR线获得的结果:

 %2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ] 

你根本不需要使用phi。 只需创build一堆临时variables。 LLVM优化过程将负责优化临时variables,并自动使用phi节点。

例如,如果你想这样做:

 x = 4; if (something) x = x + 2; print(x); 

你可以使用phi节点(伪代码):

  1. 分配4给x1
  2. 如果(!东西)分支到4
  3. 通过加2来从x1计算x2
  4. 从x1和x2分配x3 phi
  5. 用x3打电话打印

但是你可以不用phi节点(伪代码):

  1. 在名为x的堆栈上分配局部variables
  2. 加载到温度x1值4
  3. 将x1存储到x
  4. 如果(!东西)分支到8
  5. 将x加载到温度x2
  6. 添加x2与4到临时x3
  7. 将x3存储到x
  8. 将x加载到温度x4
  9. 打电话与x4打印

通过使用llvm运行优化传递,第二个代码将优化为第一个代码。