Modern Compiler Implementation in C 19章後半

Modern Compiler Implementation in C
19章後半(451ページから)
早稲田大学 理工学部 情報学科
上田研究室 高木祐介
1999年7月12日
19章後半 概要
•
•
•
•
•
19.3 SSAを利用する最適化アルゴリズム
19.4 配列、ポインタ、メモリ
19.5 制御依存グラフ
19.6 SSAからの逆変換
19.7 関数型中間表現
19.3節「SSAを利用する最適化
アルゴリズム」
•
•
•
•
死亡コード除去
単純な定数伝播
条件付き定数伝播
支配性を保つ
SSAグラフを表現するオブジェクト
(451ページ)
• 文
– ブロック、直前・直後の文、定義・使用する変
数の5属性を持つ。
– 代入、φ関数、フェッチ、ストア、分岐の5種類。
• 変数
– 定義された場所、使用される場所のリスト
• ブロック
– 文のリスト、直前ブロックの順序付きリスト、直
後ブロック
死亡コード除去(451-452ペー
ジ)
• SSAを使うと死亡コード解析が簡単。
– 変数の定義が死亡しているのは、使用リスト
が空のときだけ。
– 変数の定義は、その変数の全使用を支配す
る。
• 非SSAでは、使用リストが空でなくても死亡
の場合があった。(同じ変数の、別の定義
を使用する場合)
アルゴリズム19.12(452ページ)
SSAを利用した死亡コード除去
W ← SSAプログラム中の全変数のリスト
while Wが空でない
Wから変数vを除去する
if vの使用リストが空である
Sは、vを定義する文であるとする
if Sは、vに代入する以外の副作用がない
プログラムからSを除去する
for Sで使用されていた各変数x_i
x_iの使用リストからSを除去する
W ← W ∪ {x_i}
アルゴリズム19.12の時間コスト
(451ページ)
• (プログラムの大きさ + 除去された変数の
個数)に比例した時間
• 変数の個数がプログラムの大きさを超える
ことはない。 →一般に線形時間
• x_iの使用リスト(長いかも)からSを除去す
る時間は? →使用リストを二重リンクリストで
管理し、各使用(文?)に自分のノードを指すポイ
ンタを持たせれば、定数時間で済む。
図19.3b(435ページ)での
死亡コード除去(452ページ)
• a1,a2,a3,b0,b2,c0,c1,c2は使用されている。
• b1の使用リストは空なので、ブロック2の文
S: b1←φ(b0,b2) を除去する。
• b0,b2の使用リストからSを除去する。
• b0の使用リストが空だが、定義文がない。
• b2はS以外でも使用されている。
単純な定数伝播(452-453ペー
ジ)
• v←c (cは定数)という文があったら、vの使
用は、cの使用に置換できる。
• v←φ(c1,c2,…,c_n) という文があって、
c1=c2=…=c_n なら、この文を v←c に置換
できる。
単純定数伝播のアルゴリズム
(452ページ)
W ← SSAプログラム中の全文のリスト
while Wが空でない
Wから文Sを除去する
if Sは v←φ(c,c,...,c) ただしcは定数
Sを v←c に置換する
if Sは v←c ただしcは定数
プログラムからSを除去する
for vを使用する各文T
T中のvをcに置換する
W ← W ∪ {T}
図19.4g(437ページ)での
単純定数伝播(453ページ)
•
•
•
•
全引数が等価なφ関数はない。
i1←1 を除去して、 j3←i1 を j3←1 に置換。
j1←1 を除去して、 j2←φ( j4,1)
k1←0 を除去して、 k2←φ(k4,0)
• j3←1 を除去して、 j4←φ(1, j5)
他の作業リストアルゴリズム
(453ページ)
• コピー伝播
– x←φ(y) や x←y を除去、xの使用をyに置換。
• 定数畳み込み
– x←a・b (a,bは定数)という文は、翻訳時に
c←a・b を評価して、 x←c に置換。
• 定数条件
• 到達不可能コード
定数条件(453ページ)
– ブロックL中に if a<b goto L1 else L2 という文
があり、aとbが定数なら、翻訳時に a<bを評価
し、その値によって goto L1 か goto L2 に置換
する。
– LからL2(またはL1)への制御フロー枝を除去
する。
– L2(L1)の直前ブロックのリストと、L2(L1)中
のφ関数の引数と から、Lを除去する。
到達不可能コード(453ページ)
– 定数条件で直前ブロックを除去した結果、ブ
ロックL2が到達不可能になった場合、L2中の
全文を除去する。
– L2で使用されていた全変数の使用リストを直
す。
– ブロックL2自体を除去し、直後ブロックの持つ
直前ブロックリストからL2を除去する。
条件付き定数伝播
(453-456ページ)
• 単純定数伝播が実行可能と見なすブロッ
クの集合は、不動点だが最小ではない。
• 条件付き定数伝播は最小不動点を見つけ
る。
– 証拠がない限り、
• ブロックは実行不可能と見なす。
• 変数は定数値を取ると見なす。
変数vの値(454ページ)
• V[v]=⊥ (初期値)
– 実行可能な代入が見つかってない。
• V[v]=4
– v←4 以外に実行可能な代入が見つかってない。
• V[v]=T
– 複数の実行可能な代入が見つかった。
– 翻訳時に値の分からない実行可能な代入が
見つかった。
ブロックBの実行可能性
(454ページ)
• E[B]=false (初期値)
– ブロックBが実行可能である証拠が見つかっ
てない。
• E[B]=true
– ブロックBが実行可能である証拠が見つかっ
た。
初期設定と、ブロックに対する
条件(454ページ)
1. SSA中で定義されない変数vは、プログラ
ムに対する入力か、手続きの形式引数か、
初期化し忘れの変数なので、V[v]←T とす
る。
2. 最初のブロックB1は実行可能。
E[B1]←true
3. 実行可能なブロックBの直後ブロックがC
のみなら、E[C]←true
実行可能な代入と分岐に対する
条件(455ページ)
• 実行可能な代入 v←x・y について、
4. V[x]=c1 かつ V[y]=c2 なら、V[v]←c1・c2(定数畳み込み)
5. V[x]=T または V[y]=T なら、V[v]←T
• その他の代入
7. v←MEM( ) または v←CALL( ) なら、V[v]←T
• 実行可能な分岐 if x<y goto L1 else L2 について、
10. V[x]=T または V[y]=T なら、E[L1]←true かつ E[L2]←true
11. V[x]=c1 かつ V[y]=c2 なら、c1<c2 の値に応じて E[L1]←true
または E[L2]←true(定数条件)
実行可能なφ関数に対する
条件(455ページ)
• v←φ(x1,...,x_n) について、
6. V[x_i]=c1,V[x_j]=c2,c1≠c2,i番目とj番目の直前ブ
ロックが実行可能なら、V[v]←T
8. V[x_i]=T かつ i番目の直前ブロックが実行可能な
ら、V[v]←T
9. V[x_i]=c1 かつ i番目の直前ブロックが実行可能な
とき、全てのjについて (V[x_j]=⊥ または V[x_j]=c1
または j番目の直前ブロックが実行不可能)なら、
V[v]←c1
変数とブロックの解析
(455ページ)
• 変数用の作業リストWvから変数xを選び、xを使
用する各文について条件4-9に従う。
• ブロック用の作業リストWbからブロックBを選ん
で条件3を考え、B中の各文について条件4-9に
従う。
• ブロックBが新しく実行可能と見なされたら、Bと、
実行可能な直後ブロックをWbに加える。
• 変数vが、⊥からc、cからTへ格上げされたら、vを
Wvに加える。
• WvとWbが空になったら終わり。
条件付き定数伝播の
アルゴリズム(455ページ)
• 解析は速く終わる。なぜなら、
– 変数は2回しか格上げされない。
– ブロックは1回しか実行可能にならない。
• 解析が終わったら、
– E[B]=false であるようなブロックBを除去する。
– V[x]=c であるような変数xをcに置換し、xへの
代入を除去する。
図19.4gでの条件付き定数伝播
(456ページ図19.13)
•
•
•
•
•
•
•
•
•
•
ブロック1、2は実行可能。(条件2、3)
V[i1] ←1、V[ j1]←1、V[k1]←0。
V[ j2]←1、V[k2]←0。(条件9)
ブロック3、5、7(、2)は実行可能。(条件11、3)
V[ j3]←1。
V[k3]←1。(条件4)
V[ j4]←1、V[k4]←1。(条件9)
V[ j2]←1、V[k2]←T。(条件9、6)
ブロック(3、)4は実行可能。(条件10)
V[k3]←T、V[k4]←T。(条件5、8)
支配性を保つ(456-457ページ)
• 支配性とは、変数の定義が、その変数の
使用を支配すること。
• 支配性を保たない最適化は避けるべき。
– SSAから干渉グラフを作るアルゴリズム19.17
など、支配性に依存する最適化アルゴリズム
がある。
– SSAの定義も支配性に依存している。
19.4節「配列、ポインタ、メモリ」
• 4種の依存関係
• メモリ上での依存
4種の依存関係(457ページ)
• 書いてから読む。(SSAに明示される)
– 文Aが変数vを定義し、文Bがvを使用する。
• 書いてから書く。(SSAにはない)
– Aがvを定義した後、Bがvを定義する。
• 読んでから書く。(SSAにはない)
– Aがvを使用した後、Bがvを定義する。
• 制御する。
– Bが実行されるかどうかをAが制御する。
メモリ上での依存(458ページ)
• これまでは、ロード・ストアを無視してきた。
• メモリに対するSSAを得るには、各メモリ
ワードに一度しか書き込まなければよい。
• これは、純関数型言語がしていることと同
じ(15章)。
– 実際には、裏でごみ集めによって物理メモリを
使い回す。
• 手続き型言語では難しい。
単純だが現実的な解決
(459ページ)
• ストア命令は生存していると見なす。
– ストアに対して死亡コード除去を行わない。
– ロードとストアや、ストア2つを入れ替えない。
– 到達不可能なストアは除去してよい。
• 19章にある最適化アルゴリズムは、この単
純なロード・ストアモデルを使っている。
– 命令を並べ替えない。
– メモリを通じてデータフロー情報を伝播しない。
19.5節「制御依存グラフ」
• 制御依存グラフを作る
• 強力な死亡コード除去
– 制御依存グラフを使うアルゴリズムである。
用語(459-460ページ)
• 制御フローグラフ(CFG)には出口ノード1つ。
– 複数のreturn文を持つ関数のCFGは、各returnから、
CFGの本当の出口ノードへの枝があると見なす。
• ノードyがxに制御依存するとは、
– xからuとvへの分岐があり、uから出口へyを通らない
パスがあり、vから出口へyを通らないパスがないこと。
• 制御依存グラフ(CDG)は、
– yがxに制御依存するとき、xからyへの枝を持つ。
• yがvを逆支配するとは、
– vから出口へyを通らないパスがないこと。
G(CFG)の CDGを作る
(460ページ)
1. Gに入口ノードrを加え、
– rから、Gの最初のノードへ枝を付ける。(外のプログラ
ムからGに入れる)
– rから、Gの出口ノードへ枝を付ける。(外のプログラム
はGを実行できない)
2. Gの枝の向きを逆にしてG’(逆CFG)を作る。
3. G’の支配木を作る。根はGの出口ノード。
4. G’の支配前線DF_G’を計算する。
5. CDGは、x∈DF_G’[y]のとき、xからyへの枝を持
つ。
強力な死亡コード除去
(461ページ)
• 強力でない死亡コード除去では、実質的な
計算結果に役立っていない文も生存する
と見なしていた。
• 強力な死亡コード除去では、プログラムの
実質的な結果に役立つという証拠がなけ
れば、文は死亡していると見なす。
強力な死亡コード除去の
アルゴリズム(461ページ)
• 以下の文は生存と印付ける。
1. 入出力、ストア、関数からのreturn、副作用の
あるかもしれない関数呼び出し。
2. 他の生存する文で使われる変数vの定義。
3. 他の生存する文が制御依存する条件分岐。
• その後、印付いてない文を除去する。
図19.13dでの強力な死亡コード
除去(462ページ図19.16)
• ブロック4は、return文なので生存。(条件1)
• ブロック4が使用する変数はない。(条件2)
• ブロック4が制御依存する条件分岐はない。
(条件3)
• 生存しているのはブロック4のみ。
強力な死亡コード除去で
注意すること(461ページ)
• 強力な死亡コード除去は、出力のない無
限ループを除去する。
– 何もしない(止まらない)はずのプログラムが、
出力して止まるプログラムになるかも。
• 並列化コンパイラではよく使われる。
– 無限ループと、ループ後の文を並列化すれば、
無限ループを除去するのと同様の結果になる
だろう。
19.6節「SSAからの逆変換」
• SSAにおける生存解析
SSAからの逆変換
(462-463ページ)
• y←φ(x_1,…,x_n)を含むブロックの、i番目
の直前ブロックの最後に y←x_i を加える。
– 枝分割SSAでないと、無駄な代入が入る。
• 逆変換の後、レジスタ割り当てを行う。
– 元のプログラムで同一の変数xから派生した
x1とx2は、最適化(定数伝播、コピー伝播)に
よって干渉しているかもしれない。
– 派生元は無視。合体(コピー伝播)に任せる。
SSAにおける生存解析
(463-464ページ)
• φ関数を代入命令に変換する前に、SSAか
ら干渉グラフを作れる。
• アルゴリズム19.17は、各変数の生存範囲
を計算し、干渉グラフを作る。
– 変数が生存しているブロックをたどって、その
範囲で定義される他の変数との干渉を計算。
– 時間コストは、干渉グラフの大きさに比例。
– 末尾再帰で効率的。
アルゴリズム19.17(463ページ)
LivenessAnalysis() =
for 各変数v
M←{} %すでにたどったブロックを覚えておく
for vの各使用場所s
if sは、vをi番目の引数に持つφ関数である
LiveOutAtBlock(sを含むブロックのi番目の直前ブロック, v)
else LiveInAtStatement(s, v)
LiveOutAtBlock(n, v) = %変数vは、ブロックnから生存して出ているか
if n∈M then すでにたどったブロックにたどり着いたので終わり
M ← M∪{n}
LiveOutAtStatement(nの最後の文, v)
アルゴリズム19.17(463ページ)
LiveInAtStatement(s, v) = %変数vは、文sに生存して入っているか
if sは、ブロックnの最初の文である then
for nの各直前ブロックp
LiveOutAtBlock(p, v)
else LiveOutAtStatement(s’, v)
LiveOutAtStatement(s, v) = %変数vは、文sから生存して出ているか
for vを除く、文sで定義される各変数w
干渉グラフに (v, w) を加える
if vがsで定義されている then vの定義にたどり着いたので終わり
LiveInAtStatement(s, v)
19.7節「関数型中間表現」
• スコープ
• SSAから関数型中間表現への変換
• 関数型プログラムから関数型中間表現へ
の変換
関数型中間表現の特徴
(464ページ)
• 関数型言語の関数型中間表現と、手続き型言語
のSSAは、関係が近い。
• 関数型中間表現(465ページ図19.18)では、
–
–
–
–
–
–
式の評価順序は決まっている。
中間結果は名前付きの一時変数に収められる。
組み込み演算や関数の実引数は、変数か定数。
変数への束縛は一度だけ。等式による推論ができる。
変数の使用は、その変数の束縛スコープ中にある。
スコープは文法上の表記。支配ノードの計算は不要。
関数型中間表現の式
(465ページ図19.18)
• atom → c
• atom → s
• atom → v
整定数
文字列定数
変数
•
•
•
•
•
•
•
•
•
関数宣言
コピー
算術演算
メモリからフェッチ
メモリにストア
条件分岐
末尾呼び出し
非末尾呼び出し
リターン
exp
exp
exp
exp
exp
exp
exp
exp
exp
→
→
→
→
→
→
→
→
→
let fundefs in exp
let v = atom in exp
let v = binop(atom,atom) in exp
let v = M[atom] in exp
M[atom] := atom; exp
if atom relop atom then exp else exp
atom(args)
let v = atom(args) in exp
return atom
関数型中間表現の構文(式以
外)
(465ページ図19.18)
• args →
• args → atom args
•
•
•
•
fundefs
fundefs
formals
formals
→
→ fundefs function v(formals) = exp
→
→ v formals
• binop → plus | minus | mul | …
• relop → eq | ne | lt | ...
スコープ(465-466ページ)
• let v = … in exp
– 束縛変数vのスコープは、exp。
• let function f1(…) = exp1 …
function f_k(…) = exp_k
in exp
– 束縛変数f_iのスコープは、expとexp_j全て。(再帰を
許す。)
• function f(…, v, ...) = exp
– 束縛変数vのスコープは、exp(関数本体)。
関数型中間表現の利点
(466ページ)
• インライン展開ができる。
– 中間表現木(7章)では、関数の出入口が機種
依存なので、関数を扱うのが難しい。
• インライン展開が簡単。
– (15章328ページ、アルゴリズム15.8)
– 実引数は変数か定数のみ。
– その他の式の場合を考えなくてよい。
SSAから関数型中間表現へ
の変換(466-467ページ)
• アルゴリズム19.20を使って、SSAから関数
型中間表現に変換できる。
• 複数の直前ブロックを持つCFGノードは、
関数に変換。
– 引数は、ノード中のφ関数で定義される変数。
– ノードへの枝は、jumpではなく、関数呼び出し。
– ノードfがgを支配しているなら、関数gを、fにネ
ストする。
アルゴリズム19.20(467ページ)
ノードの変換
Translate(node) =
nodeが支配するノードのうち、複数の直前ブロックを持
つp_iは “function Fi()=Translate(p_i)” に変換。引数
は、p_i中のφ関数で定義される変数
Fi を並べて F とする
return Statements(node, 1, F)
アルゴリズム19.20(467ページ)
文の変換
Statements(node, j, F) = %node中のj番目以降の文を変換
if node中の文を変換し終わった
if 直後ブロックsの直前ブロックが1個だけ
then Statements(s, 1, F)
else 複数の直前ブロックを持つsへの枝は関数呼び出しに変換
else if nodeのj番目の文がφ関数 then Statements(node, j+1, F)
else if j番目の文が “return a” then “let F in return a”
else if move命令の場合 then “let move命令 in Statements(node,
j+1, F)”
else if “if a<b goto s1 else s2”の場合 then “let F in if a<b then
Translate(s1) else Translate(s2)”
図19.4g(437ページ)を関数型中間表現
に変換(466ページ、プログラム19.19)
• Translate(ノード1)
– F = “function f2(j2,k2) = Translate(ノード2)”
– “let i1=1…k1=0 in let F in f2(j1,k1)”
• Translate(ノード2)
– “(let in) if k2<100 then Translate(3) else Translate(4)”
• Translate(3)
– F = “function f7(j4,k4) = Translate(7)”
– “let F in if j2<20 then Translate(5) else Translate(6)”
• Translate(7)
– “(let in) f2(j4,k4)”
関数型プログラムから関数型
中間表現への変換(468ページ)
• (純)関数型プログラムは、
– すでにスコープルールに従っている。
– 実引数は原子式(変数や定数)でないかも。
– 同名の変数があるかも。(?? variables are not
unique)
• 関数型中間表現への変換
– 式の中間結果を一時変数に収めるのは簡単。
– 支配ノードとSSAの計算は不要。
関数型中間表現の最適化
(468ページ)
• SSAでの最適化は、関数型中間表現でも
使える。
• 関数型プログラムでの最適化(15章)は、
関数型中間表現でも使える。
• 明示多相型と型検査と型再構成(16章)に
対応させることができる。
– 関数型中間表現は、お勧め。