PHI指令的确切作用以及如何在LLVM中使用它


87

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

“ phi”指令用于在表示功能的SSA图中实现φ节点。

通常,它用于实现分支。如果我理解正确,则需要使依赖关系分析成为可能,并且在某些情况下,可以帮助避免不必要的加载。但是,仍然很难理解它的确切作用。

万花筒的例子很好地解释了这种if情况。但是,还不清楚如何实现类似&&和的逻辑操作||。如果我将以下内容输入到在线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节点只是定义了来自哪个路径的值。

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


1
phi节点是编译器将IR转换为“静态单分配”形式的问题的解决方案。为了更好地理解解决方案,我建议更好地理解问题。所以我将为您“为什么是phi节点”。
Vraj Pandya '18

Answers:


76

phi节点是一条指令,用于根据当前块的前身选择值(请看此处以了解完整的层次结构-它也用作值,这是它继承的类之一)。

由于LLVM代码的SSA(静态单分配)样式的结构,因此必须使用Phi节点-例如,以下C ++函数

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

转换为以下IR :(通过clang -c -emit-llvm file.c -o out.bc-创建,然后通过查看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 ++代码不同bool l,在LLVM IR中变量可以为0或1,因此必须定义一次。因此,我们检查是否%tobool为true,然后跳至lor.endlor.rhs

lor.end我们终于拥有的价值|| 操作员。如果我们是从入口处到达的-那是真的。否则,它等于-的值,%tobool2而这正是我们从以下IR行获得的结果:

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

6
TL; DRφ节点是一个三元表达式。可能有人争辩说它不包含条件,但是实际上,在转换为最终代码后,您无法确定其中哪个参数是有效的,因此φ也必须具有条件。
Hi-Angel

31

您根本不需要使用phi。只需创建一堆临时变量即可。LLVM优化过程将负责优化临时变量,并将自动使用phi节点。

例如,如果要执行此操作:

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

您可以为此使用phi节点(以伪代码):

  1. 将4分配给x1
  2. 如果(!something)分支到4
  3. 通过加2从x1计算x2
  4. 从x1和x2分配x3 phi
  5. 用x3调用打印

但是,您可以不使用phi节点(以伪代码):

  1. 在名为x的堆栈上分配局部变量
  2. 加载到温度x1值4中
  3. 将x1存储到x
  4. if(!something)分支到8
  5. 将x加载到温度x2
  6. 将x2加4到临时x3
  7. 将x3存储到x
  8. 将x加载到温度x4
  9. 用x4调用打印

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


4
根据我的阅读,听起来这里有一些限制要记住。 mem2reg是有问题的优化过程,在万花筒示例中指出了一些限制。听起来这是解决此问题的首选方法,并且由Clang使用。
马修·桑德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.