PHI命令の正確な機能とLLVMでの使用方法


87

LLVMには非常に奇妙な説明のあるファイ命令があります:

'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ノードとは何か、そしてそれを||使って実装する方法を説明できますか?


1
phiノードは、「静的単一代入」形にIRを変換するコンパイラで問題の解です。解決策をよりよく理解するには、問題をよりよく理解することをお勧めします。だから私はあなたに「なぜphiノードなのか」を1つ挙げます。
Vraj Pandya 2018

回答:


76

ファイノードは、現在のブロックの先行ノードに応じて値を選択するために使用される命令です(ここを見てください)完全な階層を確認するには、を参照してください。これは、継承元のクラスの1つである値としても使用されます)。

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
}

では、ここで何が起こるのでしょうか?変数bool lが0または1のいずれかであるC ++コードとは異なり、LLVMIRでは一度定義する必要があります。したがって%tobool、trueかどうかを確認してから、lor.endまたはにジャンプしlor.rhsます。

ではlor.end、私たちは最終的にの価値を持っている|| オペレーター。エントリーブロックから到着した場合、それは本当です。それ以外の場合は、%tobool2-の値に等しくなります。これは、次のIRラインから得られるものです。

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

6
TL;DRφノードは3項式です。条件が含まれていないと主張する人もいるかもしれませんが、実際には、最終的なコードに変換すると、どちらの引数が有効であるかを判断できないため、φにも条件が必要です。
ハイエンジェル

31

ファイを使用する必要はまったくありません。一時変数の束を作成するだけです。LLVM最適化パスは、一時変数の最適化を処理し、そのためにphiノードを自動的に使用します。

たとえば、これを実行する場合:

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

そのためにファイノードを使用できます(擬似コードで):

  1. x1に4を割り当てます
  2. if(!something)branch to 4
  3. 2を加算してx1からx2を計算します
  4. x1とx2からx3phiを割り当てます
  5. x3でprintを呼び出す

しかし、ファイノードなしで(擬似コードで)行うことができます:

  1. xと呼ばれるスタックにローカル変数を割り当てます
  2. 一時x1値4にロード
  3. x1をxに格納
  4. if(!something)branch to 8
  5. xをtempx2にロードします
  6. x2と4をtempx3に追加します
  7. x3をxに保存
  8. xをtempx4にロードします
  9. x4でprintを呼び出す

llvmで最適化パスを実行することにより、この2番目のコードは最初のコードに最適化されます。


4
私が読んだことから、ここで覚えておくべきいくつかの制限があるように思えます。 mem2regは問題の最適化パスであり、Kaleidoscopeの例で指摘されているいくつかの制限があります。ただし、これが問題を処理するための推奨される方法であり、Clangによって使用されているようです。
マシューサンダース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.