はじめに
この記事は,まだmem2regのことをふんわりとしか理解していない人が書いています.間違いの指摘などお待ちしております.
また,この記事内では解説にLLVM-IRの前提知識を必要とします.(alloca
, load
, store
, use-def chain などが何なのかわかっていればOK.)
mem2reg とは
mem2regはLLVMのPassの一つで,可能な限りメモリアクセスをレジスタアクセスへと変換します.
LLVMのPassとは言いますが,単にメモリアクセスをレジスタアクセスへと変換する機構のことだと思っていただければOKです.
mem2reg は大まかに何をするのか
ここからの解説は,主にこのあたりのコードに沿って進めていきます.
-
レジスタへと昇格できる alloca を集める.
- どんなalloca? →
load
,store
からのみアクセスされていて,アドレスの取得などに利用されていない. - 以降の
store
,load
はこれらのallocaを対象としたものとする
- どんなalloca? →
-
簡単な場合を先に処理しておく
- 一回しか
store
されていない場合- その
store src, dst
以降のload dst
をすべてsrc
で置き換えるだけ
- その
- 一つのBasic Block内でのみ
store
,load
されている場合- そのBasic Block内の
store
,load
を実行順に歩いていって,load dst
ならその一つ前のstore src, dst
のsrc
に置き換えるだけ
- そのBasic Block内の
- 一回しか
-
それぞれの
alloca
について,store
されるBasic Block (以降 def block),load
されるBasic Block (以降 using block)を求め,これらを使い入口生存(live in)しているBasic Block(以降 livein block)を計算する -
def blockの支配辺境に
phi
を挿入する.この時もlivein blockを使う. -
プログラムの入口からBasic Blockを歩いていって,後続ブロックをたどりながらプログラムの出口にたどりつくまでに次のことを繰り返す.同じブロックをたどらないように注意.
-
store src, dst
にたどり着いたら,src
を記録 -
load dst
にたどり着いたら,その参照(use)を記録された値と置き換え -
phi
にたどり着いたら,記録された値をincoming nodeとして追加する. そしてphi
を記録 (これらphi
への操作は同じブロックをたどって行うことがある)
-
-
完成 (多分)
ちょっと詳しく解説
上の章の 3. と 4. と 5. について詳しく見ていきます.
3. について
入口生存
あるブロックの入口で変数が生存していること.
def blockとusing blockを求めてあるので,using blockから前任ブロックをたどっていってdef blockにたどり着くまでのすべてのブロックがlivein blockとなる.
4. について
支配・支配木
プログラムの入口からブロックB2に達するどの路も必ずブロックB1を通るとき,ブロックB1はブロックB2を**支配する(dominate)という.ブロックBは自分自身を支配することになっている.
また,支配関係は反射的かつ推移的であり,木の形で書くことができることが知られている.この木では,親のブロックが子孫のブロックを支配する.この木を支配木(dominator tree)**という.1
画像で表すと次のようになる.
支配辺境
ブロックiの支配辺境(dominance frontier)とは,制御フローグラフ上で,初めてiの支配関係から「はずれた」ブロックjのことである.1
画像で表すと次のようになる.ブロックの右下に示したのが支配辺境.先ほどの画像の制御フローグラフを参考にしてください.
支配辺境を求めるアルゴリズムにはいくつかありますが,LLVMは線形時間で解けるものを使っているようです2.
phi の挿入位置
phi を挿入するブロックは,あるdef blockの支配辺境であることが知られています1.
とりあえず支配辺境の先頭に incoming node を持たない phi を挿入しておきましょう.
5. について
次のようなプログラムを考えます.
4.
までの処理でphiが挿入されているので次のようになります.
プログラムの先頭からBasic Blockを歩いていきます.以下の点に注意しながらGIFを見てみてください.
store src, dst
にたどり着いたら,src
を記録
-
load dst
にたどり着いたら,その参照(use)を記録された値と置き換え -
phi
にたどり着いたらincoming nodeとして記録された値を追加する.phi
を記録
(TODO: GIF内で, loadの参照を置き換えるのではなく, load自体を置き換えてしまっている. 近いうちに直します...)
ここまで来たら,あとは alloca
, store
を削除してやれば完成です.(まあ途中で削除してもいいんですけどね)
最後に
この記事で紹介した手法で生成されるSSAはpruned SSAと呼ばれているようです.ほかにもSemi-pruned SSAなどがあり,まだあまりよく理解できていないので今後調べていきたい.
-
静的単一代入形式を用いた最適化(導入編) とってもわかりやすい. ↩ ↩2 ↩3
-
A linear time algorithm for placing φ-nodes. 実際にコードを読むとわかりやすい. ↩