什么是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节点(伪代码):
- 分配4给x1
- 如果(!东西)分支到4
- 通过加2来从x1计算x2
- 从x1和x2分配x3 phi
- 用x3打电话打印
但是你可以不用phi节点(伪代码):
- 在名为x的堆栈上分配局部variables
- 加载到温度x1值4
- 将x1存储到x
- 如果(!东西)分支到8
- 将x加载到温度x2
- 添加x2与4到临时x3
- 将x3存储到x
- 将x加载到温度x4
- 打电话与x4打印
通过使用llvm运行优化传递,第二个代码将优化为第一个代码。