LLVMのphi
命令についてのメモ。
内容は、phi
命令がなぜ必要なのかという説明、使い方、phi
命令を使わない場合の条件分岐やループの例と使った場合の例。
※LLVMのバージョンは10.0.0。
目次
なぜphi
命令が必要なのか
LLVMは静的単一代入(SSA)という方式を採用している。
そのため、一度識別子に値を割り当てたら、その後別の値を割り当て直すことはできない。
例えば以下のようなことができない。
%result = call i32 func1() %result = call i32 func2()
以下のように書く必要がある。
%result1 = call i32 func1() %result2 = call i32 func2()
これで何が困るかというと、例えば以下のように条件分岐によって違う値を識別子に割り当てたい場合に困る。
コンパイルするとmultiple definition of local value named 'result'
というエラーになる。
これを避けるため、前回書いた条件分岐のコードではスタック領域を利用した。
前回書いたコードは以下の通り。
alloca
、store
、load
を使っている箇所が、スタック領域の確保や読み書きを行っている部分。
スタック領域の変数%ptr_return_value
で値を使いまわしている。
この場合、その部分を削除してphi
命令を使うと、スタック領域を使わずレジスタだけで処理が可能になる。
スタック領域よりレジスタを使うほうが実行速度の向上が見込める。また、LLVM IRのコード量も減らせる。
phi
命令とは
先述の例のようにmultiple definition of local value named 'result'
のエラーが発生する場合、”どこから分岐してきたか"によって割り当てる値を切り替えることができれば、エラーを直すことができる。
それをするための命令がphi
。
phi
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#phi-instructionにある。
構文は以下。
<result> = phi [fast-math-flags] <ty> [ <val0>, <label0>], ...
<ty>
が戻り値の型。
[ <val0>, <label0>], ...
が、分岐してくる前の位置とその場合の値のリスト。カンマ区切りで複数指定できる。
phi
命令を使った場合の分岐
6行目から13行目に遷移してきた場合、つまりラベルthen
のブロックから遷移してきた場合は%result1
の値を%result3
に割り当てる。
10行目から13行目に遷移してきた場合、つまりラベルelse
のブロックから遷移してきた場合は%result2
の値を%result3
に割り当てる。
phi
命令を使った場合のループ
値を割り当て直すことができないというのはソースコード上での話。
動作時、ループで戻ってきて値を割り当て直すことはできる。
phi
命令を使った場合のFIzzBuzz
前回と違い、配列部分以外、alloca
、store
、load
していない。
optコマンド
ちなみに、理解するために今回は自分でコードを書いたがoptコマンドを使えば自動的に最適化できるので自分でphi
命令を使わなくてもいい。
コマンドの実行例:opt -S -mem2reg -o (出力ファイル名).ll (入力ファイル名).ll
ただし、mem2reg
で最適化するためには、alloca
を関数の先頭ブロックで行わなければならない。