LLVMについて、バックエンドの部分はある程度勉強したけど、そういえばPassの作り方をまじめに勉強したことが無かった。
LLVMと言えばPassだろう!ということでPassの作り方について勉強することにした。これはそのうちLLVMの拡張や最適化をしたいときに役に立つはずだ。
いくつかのPassについて。CallGraphSCCPass
クラス、FunctionPass
クラス、LoopPass
クラス。
LLVMのPassを書く
CallGraphSCCPass
クラス
CallGraphSCCPass
はpassがプログラムの呼び出しグラフを下から上にする必要があるときに使用する(呼び出し先から呼び出し元へ)。
CallGraphSCCPass
はCallGraph
を作成するメカニズムを提供するが、システムがCallGraphSCCPass
の最適化実行も許す。
あなたのpassが以下のアウトラインに示す要件に合致し、FunctionPass
の要件に合致しないならば、CallGraphSCCPass
から派生させるべきです。
TODO: SCCとは何か、Tarjanのアルゴリズム、B-U meanについて説明する。
CallGraphSCCPass
サブクラスについてその役割を明確にすると:
- 現在のSCCおよびSCCの直接呼び出し元と直接呼び出し先以外の関数を検査したり変更したりすることはできません。
- 現在の
CallGraph
オブジェクトを保存し、プログラムに加えられた変更を反映して更新することが求められる。 - SCC の内容を変更することはできても、現在のモジュールに SCC を追加したり削除したりすることはできない。
- 現在のモジュールにグローバル変数を追加・削除することができる。
runOnSCC
の起動に関わらず、状態を維持することができる(グローバルデータを含む)。
Note: SCCって何? --> "Strongly Connected Component"
SCCを1つ以上のノードで管理しなければならないため、CallGraphSCCPass
を実装するのは、いくつかの場合において非常にトリッキーです。
以下で説明するすべてのバーチャルメソッドでは、プログラムを変更した場合にはtrue
を、そうでない場合はfalse
を返す必要があります。
doInitialization(CallGraph &)
メソッド
virtual bool doInitialization(CallGraph &CG);
doInitiazilation
メソッドはCallGraphSCCPass
が許可していないことのほとんどを許可するメソッドである。関数の追加や削除、関数のポインタの取得などが行えます。doInitializatoin
メソッドは処理されるSCCに依存しない単純な初期化などに使用されます。doInitilization
メソッドは他のpassの実行とオーバラップしないようにスケジュールされます(従ってこのメソッドは非常に高速であるべきです)。
runOnSCC
メソッド
virtual bool runOnSCC(CallGraphSCC &SCC) = 0;
runOnSCC
メソッドはpassの最も興味のある動作を行う場所です。モジュールが変換により変換された場合はtrue
を返し、そうでない場合はfalse
を返すべきです。
doFinalization(CallGraph &)
メソッド
virtual bool doFinalization(CallGraph &CG);
doFinalization
メソッドは、passのフレームワークがコンパイルされるプログラムのすべてのSCCに対してrunOnSCC
を呼び終わった後に呼び出されます。
FunctionPass
クラス
ModulePass
サブクラスと対比して、FunctionPass
サブクラスは予測可能な、システムによって予測可能な動作を持っている。すべてのFunctionPass
はプログラム中の関数を独立に実行する。FunctionPass
は特定の順序で動作する必要はなく、FunctionPass
は外部の関数を変更しない。
明確化するために、FunctionPass
サブクラスは以下を許可しない:
- 現在処理されている
Function
以外の関数を検査や変更すること - 現在の
Module
から関数を追加もしくは削除すること - 現在の
Module
からグローバル変数を追加または削除すること runOnFunction
の起動の状態を操作すること(グローバルデータを含む)
FunctionPass
の実装は通常は単純です("Hello World"passの例を参照のこと)。FunctionPass
は3つのバーチャルメソッドをオーバロードする。すべてのメソッドは、プログラムを変更した場合にはtrue
を返す必要があり、そうでない場合はfalse
を返す。
doInitialization(Module &)
メソッド
virtual bool doInitialization(Module &M);
このdoInitialization
メソッドはFunctionPass
が許可しない殆どの操作を行うことができます。関数の追加削除や、関数のポインタの取得などが行えます。このdoInitialization
メソッドは処理する関数に依存しないタイプの様々なことを簡単に初期化するために設計されています。
doInitialization
メソッドは他のpassとオーバラップして呼び出されるようにはスケジュールされていません(従って非常に高速であるべきです)。
このメソッドの使い方の良い例としてLowerAllocation
passがあります。
このpassはmalloc
とfree
命令をプラットフォームに依存するmalloc()
とfree()
関数呼び出しに置き換えます。
doInitialization
メソッドではmalloc
とfree
関数への参照を取得し、必要ならばモジュールにプロトタイプを追加します。
runOnFunction
メソッド
virtual bool runOnFunction(Function &F) = 0;
runOnFunction
メソッドはpassによる変換や解析処理を行うために実装する必要があります。
通常は、関数が変更されたらtrue
を返します。
doFinalization(Module &)
メソッド
virtual bool doFinalization(Module &M);
doFinalization
メソッドはあまり使われませんが、passのフレームワークが、コンパイルされるプログラムに対してすべてのrunOnFunction
の呼び終えた後に呼び出されます。
LoopPass
クラス
LoopPass
クラスは関数内の独立したループに対して適用されます。LoopPass
はネストされた順番に実行され、つまり最も上位のループが最後に実行されます。
LoopPass
サブクラスはLPPassManager
インタフェースを通じてループネストをアップデートすることが許可されています。loop passを実装することは通常は単純です。LoopPass
は3つバーチャルメソッドをオーバロードする必要があります。すべてのこれらのメソッドは、プログラムを変更した場合はtrue
を返し、そうでない場合はfalse
を返すべきです。
LoopPass
サブクラスは、同じ関数の解析を実行するメインのループパスパイプラインの一部として実行するように意図されています。これを簡単にするために、LoopUtils.h
にはgeTLoopAnalysisUsage
関数が提供されています。これはサブクラスのgeTAnalysisUsage
のオーバライドの中で呼び出すことが出来、一貫性と正しい動作を行うために必要です。同様に、INITIALIZE_PASS_DEPENDENCY(LoopPass)
はいくつかの関数の解析を初期化します。
doInitialization(Loop *, LPPassManager &)
メソッド
virtual bool doInitialization(Loop *, LPPassManager &LPM);
doInitialization
メソッドは処理する関数に依存しない簡単な初期化を実行します。doInitialization
メソッドは他のpassと実行がオーバラップしないようにスケジュールされます(従って、この関数は素早く処理を終える必要があります)。LPPassManager
インタフェースはFunciton
とModule
レベルの解析情報にアクセスするために使用します。
runOnLoop
メソッド
virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;
runOnLoop
メソッドは、サブクラス内、変換及び解析を行うために実装しなければならないメソッドです。通常、関数が変更された場合にはtrue
を返す必要があります。LPPassManager
インタフェースはループネストをアップデートするために必要です。
doFinalization
メソッド
virtual bool doFinalization();
doFinalization
メソッドはあまり使われることのないメソッドですが、passフレームワークが、コンパイルされるプログラム内のすべてのループに対してrunOnLoop
の呼び出しを終了すると呼び出されます。
RegionPass
クラス
RegionPass
はLoopPass
と似ていますが、関数内のエントリから終了までを実行します。RegionPass
はその領域をネスト順に実行し、もっとも外側の領域が最後に処理されます。
RegionPass
サブクラスはRGPassManager
インタフェースを使って領域ツリーを更新することが許されています。RegionPass
内の3つのバーチャルメソッドをオーバロードして独自のリージョンpassを実装することができます。これらのメソッドは、プログラムを変更した場合にはtrue
を返す必要があります。
doInitialization(Region *, RGPassManager &)
メソッド
virtual bool doInitialization(Region *, RGPassManager &RGM);
doInitialization
メソッドは処理される関数に依存しない簡単な初期化などを行うためのものです。doInitialization
メソッドは他のpassの実行とオーバラップしないようにスケジュールされます(したがって高速に動作する必要があります)。RPPassManager
インタフェースはFunction
もしくはModule
レベルの解析情報にアクセスするために必要となります。
runOnRegion
メソッド
virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;
runOnRegion
メソッドは、変換や解析などの実行するために必要な関数です。通常、その領域を変更した場合にはtrue
を返します。RGPassManager
は領域ツリーをアップデートするために必要です。
doFinalization()
メソッド
virtual bool doFinalization();
doFinalization
メソッドはあまり使われることのないメソッドで、コンパイルされるプログラムのすべての領域に対してrunOnRegion
が実行された後に呼び出されます。
MachineFunctionPass
クラス
MachineFunctionPass
はLLVMコードジェネレータの一部で、プログラム中のマシンに依存する表現に対して実行されるものです。
コードジェネレータpassはTargetMachine::addPassToEmitFile
により特別に登録され初期化されます。従ってこれらは一般的にoptやbugpointコマンドによって実行することはできません。
MachineFunctionPass
はFunctionPass
でもあります。したがってFunctionPass
のすべての制約はMachineFunctionPass
にも当てはまります。MachineFunctionPass
は以下のことが許可されていません:
LLVM IR
のInstruction
,BasciBlock
,Argument
,Function
,GlobalVariables
,GlobalAlias
,Module
の作成または変更- 現在処理している
MachineFunction
以外のMachineFunction
の変更 runOnMachineFunction
の起動を促すステートの調整 (グローバルデータも含む)
runOnMachineFunction(MachineFunction &MF)
メソッド
virtual bool runOnMachineFunction(MachineFunction &MF) = 0;
runOnMachineFunction
はMachineFunctionPass
のメインのエントリポイントと考えることができます; つまり、このメソッドをオーバライドしてMachineFunctionPass
を実装することができます。