web-dev-qa-db-ja.com

PHI命令の正確な動作とLLVMでの使用方法

LLVMには phi 命令があり、非常に奇妙な説明があります。

「phi」命令は、関数を表すSSAグラフのφノードを実装するために使用されます。

通常、分岐の実装に使用されます。正しく理解できたら、依存関係分析を可能にする必要があり、場合によっては不必要なロードを回避するのに役立つ可能性があります。しかし、それが何をするのかを正確に理解することはまだ困難です。

Kaleidoscope は、ifの場合にかなりうまく説明します。ただし、&&||などの論理演算を実装する方法は明確ではありません。 online 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ノードとは何か、それを使って||を実装する方法を説明できますか?

74
user730816

Phiノードは、現在のブロックの前身に応じて値を選択するために使用される命令です( here を参照して完全な階層を確認します。これは、値としても使用され、クラスの1つです)から継承)。

Phiノードは、LLVMコードのSSA(静的単一割り当て)スタイルの構造のために必要です-たとえば、次の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++コードとは異なり、LLVM IRで定義する必要がありますonce。したがって、%toboolが真であるかどうかを確認し、lor.endまたはlor.rhsにジャンプします。

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

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

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

たとえば、これを行いたい場合:

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

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

  1. x1に4を割り当てます
  2. if(!something)branch to 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)branch to 8
  5. xをtemp x2にロード
  6. 一時的なx3に4のx2を追加
  7. x3をxに保存
  8. xをtemp x4にロード
  9. x4でprintを呼び出す

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

28