LLVMについて、バックエンドの部分はある程度勉強したけど、そういえばPassの作り方をまじめに勉強したことが無かった。
LLVMと言えばPassだろう!ということでPassの作り方について勉強することにした。これはそのうちLLVMの拡張や最適化をしたいときに役に立つはずだ。
次にpassの登録方法について。相互依存関係についてなどの話。
LLVMのPassを書く
Passの登録
Hello Worldの例ではどのようにpassの登録が動作するのかについて紹介し、これがどのように使用されどのように動作するのかを見ました。ここでは、passがどのようにして、そしてなぜ登録されるのかについて議論します。
サンプルを見たように、passはRegisterPass
テンプレートを使用して登録され案す。テンプレートパラメータはpassの名前で、これはコマンドラインでプログラム(例えば、optやbugprintなど)に追加されるべきpassの名前を指定するときに使用されます。最初の引数はpassの名前で、-help
によってプログラムから出力される時に使用され、同様に-debug-pass
オプションによって生成されるメッセージにも使用されます。
passを簡単にダンプできるようにしたい場合には、以下の仮想printメソッドを実装する必要があります。
print
メソッド
virtual void print(llvm::raw_ostream &O, const Module *M) const;
print
メソッドは、解析の結果を人間が読める形式で出力するために"analyses"によって実装されなければなりません。これは解析の結果をデバッグするために有効で、他の人にどのように解析が動作するのかを示すのに便利です。-analyze
引数によりこのメソッドが起動します。
llvm::raw_ostream
パラメータにより結果を出力するストリームを指定し、Module
パラメータは解析されるプログラムのトップレベルのモジュールへのポインタを示しています。特定の環境下においてこのポインタがNULLであった場合には(例えば、デバッガからのPass::dump()
が呼び出された場合)、このメソッドはデバッグ出力を向上させるためだけにつ合われるべきであり、それに依存されるべきではありません。
pass間での相互作用の指定
PassManager
の責任の内の重要なものの一つに、passが互いに正しく相互作用することを保証することが上げられます。PassManager
は"passの実行を最適化"しようとするため、passがどのように相互作用し既存のいくつかのpassにどのように依存するかについて知る必要があります。これを追いかけるために、各passは現在のpassを実行する前に実行されておくべき必要なpassや、現在のpassによって無欧化されるべきpassの一覧を宣言しておく必要があります。
典型的に、この機能は、あなたのpassが実行される前に解析の結果を計算しておく必要がある場合に使用されます。何らかのpassが無効化passの一覧を設定した場合に、すべての変換passが計算した解析の結果を無効化すうrことができます。もしpassがgetAnalysisUsage
メソッドを実装しない場合、このような事前に必要なpassが存在しないとされ、他のすべてのpassが無効化されます。
getAnalysisUsage
メソッド
virtual void getAnalysisUsage(AnalysisUsage &Info) const;
getAnalysisUsage
メソッドを使うことによって、あなたの変換に対して必要なpassと無効化すべきpassの一覧を指定します。この実装では、必要なpassと無効化すべきpassの一覧をAnalysisUsageオブジェクトに埋める必要があります。これにより、passはAnalysiUsage
オブジェクトに含まれるかの関数を呼び出します:
AnalysisUsage::addRequired<>
とAnalysisUsage::addRequiredTransitive<>
メソッド
もしあなたのpassが(例えば解析など)別のpassを事前に実行しておく必要がある場合、これらのメソッドを使用してpassを実行するようにアレンジすることができます。LLVMはDominatorSet
からBreakCriticalEdge
まで様々な異なるタイプの解析とpassを持っています。例えばBreakCriticalEdge
は、あなたのpassが実行された時にはクリティカルなエッジ(依存関係)が無いことを保証します。
いくつかの解析はそのジョブを実行するために他の解析に対してチェインを持っています。例えば、AliasAnaLysis<AliasAnaLysis>
実装は他のエイリアス解析パスと接続することが必要です。解析チェインが存在している場合には、addRequired
メソッドの代わりにaddRequiredTransitive
メソッドを使用する必要があります。これは、必要なパスが存在する限り、一時的に必要なパスも有効であることをPassManager
に知らせるものです。
AnalysisUsage::addPreserved<>
メソッド
PassManager
のジョブの一つにいつどのように解析が実行されるのかを最適化するというものがあります。特に、必要ないのにデータの再計算が発生するのを避ける飛鳥があり案す。このため、passは既存の解析で利用可能なものがあれば(例えば、無効化されない)それを予約することを宣言することが許されていまうs。たとえあb、単純な定数の折り畳みpassはCFGを変更しないので、dominator解析の結果に影響を与えません。デフォルトでは、すべてのpassはそれ以外のpassを無効化することを仮定しています。
AnalysisUsage
クラスはaddPreserved
に関連する特定の環境において便利ないくつかのメソッドを提供しています。特に、setPreservesAll
メソッドはLLVMのプログラムに全く影響を与えないことを宣言するためのメソッドで、setPreservesCFG
は変換いよりプログラム中の命令を変更するがCFGもしくは終了命令には変更を行わないことを示すためのものです。
addPreserved
メソッドはBreakCriticalEdge
のように特に便利なメソッドです。このpassは小さなループのセットとdominatorに関連する解析をどのようにアップデートするかを知っています。したがって実際にはCFGでハックするにもかかわらずこれを保存することができます。
getAnalysisUsage
の実装例
// この例ではプログラムを変更するが、CFGは変更しない void LICM::getAnalysisUsage(AnalysisUsage &AU) const { AU.setPreservesCFG(); AU.addRequired<LoopInfoWrapperPass>(); }
getAnalysis<>
とgetAnalysisIfAvailable<>
メソッド
Pass::getAnalysis<>
メソッドはあなたのクラスから自動的に継承され、getAnalysisUsage
メソッドによりあなたが必要だと宣言したpassに対するアクセスを提供します。このメソッドはシンプルなテンプレートの引数を取り、必要なpassを指定し、そのpassへの参照を返します。例えば:
bool LICM::runOnFunction(Function &F) { LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo(); //...
このメソッドは所望のpassへの参照を返します。getAnalysisUsage
の実装で宣言しなかttあものにアクセスしようとすると、ランタイムアサーションエラーが発生します。このメソッドはrun*
メソッドのじっそうで呼び出すことができ、run*
メソッドから呼び出される別のローカルメソッドからも呼び出すことができます。
モジュールレベルのpassは関数レベルの解析情報をインタフェースを通じて使用することができます。例えば:
bool ModuleLevelPass::runOnModule(Module &M) { //... DominatorTree &DT = getAnalysis<DominatorTree>(Func); //... }
上記の例では、DominatorTree
のrunOnFunction
は、所望のpassの参照から戻ってくる前にPass Managerによって呼び出されます。
passに解析が存在する場合に更新する機能がある場合(前述のBreakCriticalEdges
など)、getAnalysisIfAvailable
メソッドを使用することができます。このメソッドは、分析がアクティブな場合にその分析へのポインタを返します。例えば、以下のようになります。
if (DominatorSet *DS = getAnalysisIfAvailable<DominatorSet>()) { // A DominatorSet is active. This code will update it. }