99

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 节点,以及如何使用它来实现||吗?

4

3 回答 3

90

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可以是 0 或 1 不同,在 LLVM IR 中它必须定义一次。所以我们检查是否%tobool为真,然后跳转到lor.endor lor.rhs

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

%2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
于 2012-07-14T17:41:59.570 回答
35

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

例如,如果你想这样做:

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

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

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

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

  1. 在名为 x 的堆栈上分配局部变量
  2. 加载到 temp 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 运行优化通道,第二个代码将针对第一个代码进行优化。

于 2012-07-14T21:40:47.923 回答
1

现有的答案很好。但是,我想让它更简单、更短。

block3:
    %result = phi i32 [%a, %block1], [%b, %block2]

这意味着如果前一个块是block1,选择值a。如果前一个块是block2,选择 value b

为什么我们要这样写?这是为了防止result在两个不同的块中分配,例如if块和else块。因为,我们不想违反 SSA 原则。SSA 帮助编译器应用各种优化,它是中间代码的事实标准。有关详细信息,请参阅此参考

于 2022-01-29T02:17:46.233 回答