LLVM パスの作成 (レガシー PM バージョン)

はじめに — パスとは?

警告

このドキュメントでは、レガシーパスマネージャーについて説明します。LLVM は、最適化パイプライン(コード生成パイプラインは引き続きレガシーパスマネージャーを使用)に新しいパスマネージャーを使用しており、独自のパス定義方法があります。詳細については、LLVM パスの作成 および 新しいパスマネージャーの使用 を参照してください。

LLVM パスフレームワークは LLVM システムの重要な部分です。なぜなら、コンパイラの興味深い部分のほとんどは LLVM パスに存在するためです。パスは、コンパイラを構成する変換と最適化を実行し、これらの変換で使用される解析結果を構築し、何よりも、コンパイラコードの構造化手法です。

すべての LLVM パスは Pass クラスのサブクラスであり、Pass から継承された仮想メソッドをオーバーライドすることによって機能を実装します。パスの動作に応じて、ModulePassCallGraphSCCPassFunctionPass、または LoopPass、または RegionPass クラスを継承する必要があります。これにより、システムはパスの動作と他のパスとの組み合わせ方に関する詳細な情報を取得できます。LLVM パスフレームワークの主な機能の 1 つは、パスが満たす制約(どのクラスから派生しているかによって示される)に基づいて、効率的な方法でパスの実行をスケジュールすることです。

パスクラスと要件

新しいパスを設計するときに最初に行うことの 1 つは、パスのサブクラスとしてどのクラスを使用するかを決定することです。ここでは、最も一般的なものから最も具体的なものまで、利用可能なクラスについて説明します。

Pass のスーパークラスを選択する際には、リストされている要件を満たしつつ、可能な限り **最も具体的な** クラスを選択する必要があります。これにより、LLVM パスインフラストラクチャはパスの実行方法を最適化するために必要な情報を取得し、結果のコンパイラが不必要に遅くならないようにします。

ImmutablePass クラス

最も単純で退屈なパスのタイプは、「ImmutablePass」クラスです。このパスタイプは、実行する必要がなく、状態を変更せず、更新する必要がないパスに使用されます。これは通常の変換または解析のタイプではありませんが、現在のコンパイラ構成に関する情報を提供できます。

このパスクラスは非常にまれにしか使用されませんが、コンパイル対象の現在のターゲットマシンや、さまざまな変換に影響を与える可能性のあるその他の静的情報に関する情報を提供する上で重要です。

ImmutablePass は他の変換を無効にしたり、無効にされたり、実行されることはありません。

ModulePass クラス

ModulePass クラスは、使用できるすべてのスーパークラスの中で最も一般的です。ModulePass から派生することは、パスがプログラム全体を単位として使用し、予測できない順序で関数本体を参照したり、関数を追加および削除したりすることを示します。 ModulePass サブクラスの動作については何もわかっていないため、それらの実行を最適化することはできません。

モジュールパスは、関数レベルパス(例:ドミネーター)を getAnalysis インターフェイス getAnalysis<DominatorTree>(llvm::Function *) を使用して使用し、関数パスがモジュールパスまたはイミュータブルパスを必要としない場合、解析結果を取得する関数を提供できます。これは、解析が実行された関数(例:ドミネーターの場合、宣言ではなく関数定義の DominatorTree のみを要求する必要があります)に対してのみ実行できます。

正しい ModulePass サブクラスを作成するには、ModulePass から派生させ、次のシグネチャを持つ runOnModule メソッドをオーバーライドします

runOnModule メソッド

virtual bool runOnModule(Module &M) = 0;

runOnModule メソッドは、パスの興味深い処理を実行します。変換によってモジュールが変更された場合は true を返し、それ以外の場合は false を返す必要があります。

CallGraphSCCPass クラス

CallGraphSCCPass は、コールグラフ上でプログラムをボトムアップ(呼び出し元より前に呼び出し先)でトラバースする必要があるパスで使用されます。CallGraphSCCPass から派生すると、CallGraph を構築およびトラバースするためのメカニズムが提供されますが、CallGraphSCCPass の実行を最適化することもできます。パスが以下に示す要件を満たし、FunctionPass の要件を満たさない場合は、CallGraphSCCPass から派生する必要があります。

TODO: SCC、Tarjan のアルゴリズム、および B-U の意味を簡単に説明します。

明示的に言うと、CallGraphSCCPass サブクラスは

  1. … 現在の SCC 内および SCC の直接の呼び出し元と直接の呼び出し先以外の Function を検査または変更することは*許可されていません*。

  2. … プログラムに加えられた変更を反映するように更新し、現在の CallGraph オブジェクトを保持する*必要があります*。

  3. … 現在のモジュールから SCC を追加または削除することは*許可されていません*が、SCC の内容を変更することはできます。

  4. … 現在のモジュールからグローバル変数を追加または削除することは*許可されています*。

  5. runOnSCC の呼び出しをまたいで状態を維持すること(グローバルデータを含む)が*許可されています*。

CallGraphSCCPass を実装するのは、場合によっては少しトリッキーです。なぜなら、複数のノードを持つ SCC を処理する必要があるからです。以下に説明するすべての仮想メソッドは、プログラムを変更した場合は true を、変更しなかった場合は false を返す必要があります。

doInitialization(CallGraph &) メソッド

virtual bool doInitialization(CallGraph &CG);

doInitialization メソッドは、CallGraphSCCPass が許可されていないほとんどの操作を実行できます。関数を追加および削除したり、関数へのポインタを取得したりできます。doInitialization メソッドは、処理される SCC に依存しない単純な初期化タイプの処理を行うように設計されています。doInitialization メソッドの呼び出しは、他のパスの実行と重複しないようにスケジュールされているため、非常に高速である必要があります。

runOnSCC メソッド

virtual bool runOnSCC(CallGraphSCC &SCC) = 0;

runOnSCC メソッドはパスの重要な処理を実行し、モジュールが変換によって変更された場合は true を、それ以外の場合は false を返す必要があります。

doFinalization(CallGraph &) メソッド

virtual bool doFinalization(CallGraph &CG);

doFinalization メソッドは、パスフレームワークがコンパイル対象プログラム内のすべての SCC に対して runOnSCC の呼び出しを完了したときに呼び出される、まれにしか使用されないメソッドです。

FunctionPass クラス

ModulePass のサブクラスとは対照的に、FunctionPass のサブクラスは、システムが期待できる予測可能でローカルな動作をします。すべての FunctionPass は、プログラム内の他のすべての関数とは独立して、プログラム内の各関数で実行されます。FunctionPass は特定の順序で実行される必要はなく、FunctionPass は外部関数を変更しません。

明示的に言うと、FunctionPass のサブクラスは、以下を実行することは許可されていません。

  1. 現在処理中の関数以外の Function を検査または変更する。

  2. 現在の Module から Function を追加または削除する。

  3. 現在の Module からグローバル変数を追加または削除する。

  4. runOnFunction の呼び出し間で状態を維持する(グローバルデータを含む)。

FunctionPass を実装するのは通常簡単です。FunctionPass は、処理を実行するために 3 つの仮想メソッドをオーバーライドできます。これらのメソッドはすべて、プログラムを変更した場合は true を、変更しなかった場合は false を返す必要があります。

doInitialization(Module &) メソッド

virtual bool doInitialization(Module &M);

doInitialization メソッドは、FunctionPass が許可されていないほとんどの操作を実行できます。関数を追加および削除したり、関数へのポインタを取得したりできます。doInitialization メソッドは、処理される関数に依存しない単純な初期化タイプの処理を行うように設計されています。doInitialization メソッドの呼び出しは、他のパスの実行と重複しないようにスケジュールされているため、非常に高速である必要があります。

このメソッドをどのように使用すべきかの良い例は、LowerAllocations パスです。このパスは、malloc および free 命令をプラットフォーム依存の malloc() および free() 関数呼び出しに変換します。必要な malloc および free 関数への参照を取得するために、doInitialization メソッドを使用し、必要に応じてモジュールにプロトタイプを追加します。

runOnFunction メソッド

virtual bool runOnFunction(Function &F) = 0;

runOnFunction メソッドは、パスの変換または分析作業を行うために、サブクラスによって実装される必要があります。通常どおり、関数が変更された場合は true 値を返す必要があります。

doFinalization(Module &) メソッド

virtual bool doFinalization(Module &M);

doFinalization メソッドは、パスフレームワークがコンパイル対象プログラム内のすべての関数に対して runOnFunction の呼び出しを完了したときに呼び出される、まれにしか使用されないメソッドです。

LoopPass クラス

すべての LoopPass は、関数内の他のすべてのループとは独立して、関数内の各 ループ で実行されます。LoopPass は、最も外側のループが最後に処理されるように、ループネスト順にループを処理します。

LoopPass のサブクラスは、LPPassManager インターフェイスを使用してループネストを更新できます。ループパスの実装は通常簡単です。LoopPass は、処理を実行するために 3 つの仮想メソッドをオーバーライドできます。これらのメソッドはすべて、プログラムを変更した場合は true を、変更しなかった場合は false を返す必要があります。

メインのループパスパイプラインの一部として実行される予定の LoopPass サブクラスは、パイプライン内の他のループパスが必要とするものと同じ *関数* 分析をすべて保持する必要があります。これを簡単にするために、LoopUtils.h によって getLoopAnalysisUsage 関数が提供されています。サブクラスの getAnalysisUsage オーバーライド内で呼び出して、一貫性のある正しい動作を得ることができます。同様に、INITIALIZE_PASS_DEPENDENCY(LoopPass) は、この関数分析のセットを初期化します。

doInitialization(Loop *, LPPassManager &) メソッド

virtual bool doInitialization(Loop *, LPPassManager &LPM);

doInitialization メソッドは、処理される関数に依存しない単純な初期化タイプの処理を行うように設計されています。doInitialization メソッドの呼び出しは、他のパスの実行と重複しないようにスケジュールされているため、非常に高速である必要があります。LPPassManager インターフェイスは、Function または Module レベルの分析情報にアクセスするために使用する必要があります。

runOnLoop メソッド

virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;

runOnLoop メソッドは、パスの変換または分析作業を行うために、サブクラスによって実装される必要があります。通常どおり、関数が変更された場合は true 値を返す必要があります。LPPassManager インターフェイスは、ループネストを更新するために使用する必要があります。

doFinalization() メソッド

virtual bool doFinalization();

doFinalization メソッドは、パスフレームワークがコンパイル対象プログラム内のすべてのループに対して runOnLoop の呼び出しを完了したときに呼び出される、まれにしか使用されないメソッドです。

RegionPass クラス

RegionPassLoopPass に似ていますが、関数内の単一入口単一出口リージョンごとに実行されます。RegionPass は、最も外側のリージョンが最後に処理されるように、ネストされた順序でリージョンを処理します。

RegionPass のサブクラスは、RGPassManager インターフェイスを使用してリージョンツリーを更新できます。独自のリージョンパスを実装するために、RegionPass の 3 つの仮想メソッドをオーバーライドできます。これらのメソッドはすべて、プログラムを変更した場合は true を、変更しなかった場合は false を返す必要があります。

doInitialization(Region *, RGPassManager &) メソッド

virtual bool doInitialization(Region *, RGPassManager &RGM);

doInitialization メソッドは、処理される関数に依存しない単純な初期化タイプの処理を行うように設計されています。doInitialization メソッドの呼び出しは、他のパスの実行と重複しないようにスケジュールされているため、非常に高速である必要があります。RPPassManager インターフェイスは、Function または Module レベルの分析情報にアクセスするために使用する必要があります。

runOnRegion メソッド

virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;

runOnRegion メソッドは、パスの変換や分析処理を行うためにサブクラスで実装する必要があります。通常、リージョンが変更された場合は true 値を返す必要があります。RGPassManager インターフェースを使用してリージョンツリーを更新する必要があります。

doFinalization() メソッド

virtual bool doFinalization();

doFinalization メソッドは、パスフレームワークがコンパイル対象プログラム内のすべてのリージョンに対して runOnRegion の呼び出しを完了したときに呼び出される、まれに使用されるメソッドです。

MachineFunctionPass クラス

MachineFunctionPass は、プログラム内の各 LLVM 関数のマシン依存表現に対して実行される LLVM コードジェネレーターの一部です。

コードジェネレーターパスは、TargetMachine::addPassesToEmitFile および類似のルーチンによって特別に登録および初期化されるため、一般的に opt または bugpoint コマンドから実行することはできません。

MachineFunctionPassFunctionPass でもあるため、FunctionPass に適用されるすべての制限も適用されます。MachineFunctionPass にはさらに追加の制限があります。特に、MachineFunctionPass は以下のいずれも実行できません。

  1. LLVM IR の Instruction, BasicBlock, Argument, Function, GlobalVariable, GlobalAlias, または Module を変更または作成すること。

  2. 現在処理中のもの以外の MachineFunction を変更すること。

  3. runOnMachineFunction の呼び出しをまたいで状態を維持すること(グローバルデータを含む)。

runOnMachineFunction(MachineFunction &MF) メソッド

virtual bool runOnMachineFunction(MachineFunction &MF) = 0;

runOnMachineFunctionMachineFunctionPass のメインエントリポイントと見なすことができます。つまり、MachineFunctionPass の処理を行うためにこのメソッドをオーバーライドする必要があります。

runOnMachineFunction メソッドは、Module 内のすべての MachineFunction で呼び出されるため、MachineFunctionPass は関数のマシン依存表現に対して最適化を実行できます。作業中の MachineFunction の LLVM Function にアクセスする場合は、MachineFunctiongetFunction() アクセサーメソッドを使用します。ただし、MachineFunctionPass から LLVM Function またはその内容を変更することはできないことに注意してください。

パスの登録

パスは RegisterPass テンプレートを使用して登録されます。テンプレートパラメータは、パスをプログラムに追加する必要があることを指定するためにコマンドラインで使用されるパスの名前です。最初の引数はパスの名前であり、プログラムの -help 出力や、–debug-pass オプションによって生成されるデバッグ出力に使用されます。

パスを簡単にダンプできるようにする場合は、仮想 print メソッドを実装する必要があります。

print メソッド

virtual void print(llvm::raw_ostream &O, const Module *M) const;

print メソッドは、分析結果の人間が判読できるバージョンを出力するために、「分析」によって実装する必要があります。これは、分析自体をデバッグしたり、他の人が分析がどのように機能するかを理解したりするのに役立ちます。このメソッドを呼び出すには、opt の -analyze 引数を使用します。

llvm::raw_ostream パラメータは結果を書き込むストリームを指定し、Module パラメータは分析されたプログラムのトップレベルモジュールへのポインタを提供します。ただし、このポインタは特定の状況(デバッガーから Pass::dump() を呼び出すなど)では NULL になる可能性があるため、デバッグ出力を強化するためにのみ使用する必要があり、依存すべきではありません。

パス間の相互作用の指定

PassManager の主な責任の 1 つは、パスが互いに正しく相互作用するようにすることです。PassManagerパスの実行を最適化 しようとするため、パスが互いにどのように相互作用し、さまざまなパス間にどのような依存関係が存在するかを知っている必要があります。これを追跡するために、各パスは現在のパスの前に実行する必要があるパスのセットと、現在のパスによって無効化されるパスを宣言できます。

通常、この機能は、パスが実行される前に分析結果が計算されるようにするために使用されます。任意の変換パスを実行すると、計算された分析結果が無効になる可能性があり、これは無効化セットが指定するものです。パスが getAnalysisUsage メソッドを実装していない場合、前提条件となるパスがなく、すべて の他のパスを無効化するのがデフォルトです。

getAnalysisUsage メソッド

virtual void getAnalysisUsage(AnalysisUsage &Info) const;

getAnalysisUsage メソッドを実装することにより、必要なセットと無効化されたセットを変換に対して指定できます。実装では、どのパスが必要で、無効化されないかに関する情報を AnalysisUsage オブジェクトに入力する必要があります。これを行うために、パスは AnalysisUsage オブジェクトで次のいずれかのメソッドを呼び出すことができます。

AnalysisUsage::addRequired<> および AnalysisUsage::addRequiredTransitive<> メソッド

パスで前のパス(たとえば分析)を実行する必要がある場合、これらのメソッドのいずれかを使用して、パスの前に実行されるように調整できます。LLVM には、DominatorSet から BreakCriticalEdges の範囲にわたる、さまざまなタイプの分析とパスがあります。たとえば、BreakCriticalEdges を要求すると、パスが実行されたときに CFG にクリティカルエッジが存在しないことが保証されます。

一部の分析は、ジョブを実行するために他の分析にチェーンされます。たとえば、AliasAnalysis 実装では、他のエイリアス分析パスに チェーン する必要があります。分析がチェーンされる場合は、addRequired メソッドではなく、addRequiredTransitive メソッドを使用する必要があります。これにより、PassManager は、推移的に必要なパスが、要求するパスがある限り存続する必要があることを通知します。

AnalysisUsage::addPreserved<> メソッド

PassManager の役割の1つは、解析がどのように、そしていつ実行されるかを最適化することです。特に、必要な場合を除き、データの再計算を避けるように試みます。このため、パスは、既存の解析が利用可能な場合、それを保持する(つまり、無効化しない)ことを宣言できます。たとえば、単純な定数畳み込みパスはCFGを変更しないため、支配木解析の結果に影響を与える可能性はありません。デフォルトでは、すべてのパスは他のすべてのパスを無効化すると想定されます。

AnalysisUsage クラスは、addPreserved に関連する特定の状況で役立ついくつかのメソッドを提供します。特に、setPreservesAll メソッドは、パスがLLVMプログラムをまったく変更しないこと(これは解析に当てはまります)を示すために呼び出すことができ、setPreservesCFG メソッドは、プログラム内の命令を変更するが、CFGまたはターミネータ命令を変更しない変換で使用できます。

addPreserved は、BreakCriticalEdges のような変換で特に役立ちます。このパスは、ループおよび支配木関連の解析が存在する場合に、それらの解析を更新する方法を知っているため、CFGをハックしているにもかかわらず、それらを保持できます。

getAnalysisUsage の実装例

// This example modifies the program, but does not modify the CFG
void LICM::getAnalysisUsage(AnalysisUsage &AU) const {
  AU.setPreservesCFG();
  AU.addRequired<LoopInfoWrapperPass>();
}

getAnalysis<> メソッドと getAnalysisIfAvailable<> メソッド

Pass::getAnalysis<> メソッドは、クラスによって自動的に継承され、getAnalysisUsage メソッドで必要と宣言したパスへのアクセスを提供します。このメソッドは、必要なパスのクラスを指定する単一のテンプレート引数を取り、そのパスへの参照を返します。たとえば、

bool LICM::runOnFunction(Function &F) {
  LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
  //...
}

このメソッド呼び出しは、目的のパスへの参照を返します。getAnalysisUsage の実装で必須として宣言しなかった解析を取得しようとすると、実行時のアサーションエラーが発生する可能性があります。このメソッドは、run* メソッドの実装、または run* メソッドによって呼び出される他のローカルメソッドによって呼び出すことができます。

モジュールレベルのパスは、このインターフェースを使用して関数レベルの解析情報を使用できます。たとえば、

bool ModuleLevelPass::runOnModule(Module &M) {
  //...
  DominatorTree &DT = getAnalysis<DominatorTree>(Func);
  //...
}

上記の例では、目的のパスへの参照を返す前に、パスマネージャーによって DominatorTreerunOnFunction が呼び出されます。

パスが解析を更新できる場合(上記のように、BreakCriticalEdges など)、アクティブな場合は解析へのポインタを返す getAnalysisIfAvailable メソッドを使用できます。たとえば、

if (DominatorSet *DS = getAnalysisIfAvailable<DominatorSet>()) {
  // A DominatorSet is active.  This code will update it.
}

パス統計

Statistic クラスは、パスからのさまざまな成功メトリクスを簡単に公開できるように設計されています。これらの統計は、コマンドラインで -stats コマンドラインオプションが有効になっている場合に、実行の最後に印刷されます。詳細については、プログラマーズマニュアルの統計セクションを参照してください。

PassManagerの機能

PassManager クラスは、パスのリストを受け取り、それらの前提条件が正しく設定されていることを確認し、パスを効率的に実行するようにスケジュールします。パスを実行するすべてのLLVMツールは、これらのパスの実行にPassManagerを使用します。

PassManagerは、一連のパスの実行時間を短縮するために、主に次の2つのことを行います。

  1. 解析結果の共有。PassManager は、可能な限り解析結果の再計算を回避しようとします。これは、どの解析がすでに利用可能か、どの解析が無効になるか、パスの実行に必要な解析を追跡することを意味します。重要なのは、PassManager がすべての解析結果の正確な有効期間を追跡し、解析結果を保持するために割り当てられたメモリが不要になったらすぐに解放できるようにすることです。

  2. プログラムでのパス実行のパイプライン化。PassManager は、パスをパイプライン化することにより、一連のパスからより優れたキャッシュおよびメモリ使用動作を得ようとします。これは、一連の連続したFunctionPassが与えられた場合、最初の関数ですべてのFunctionPassを実行し、次に2番目の関数ですべてのFunctionPassを実行し、プログラム全体がパスを通過するまで続けることを意味します。

    これにより、一度に単一の関数のLLVMプログラム表現のみに触れるため、プログラム全体をトラバースするのではなく、コンパイラのキャッシュ動作が改善されます。また、たとえば、一度に1つのDominatorSetのみを計算する必要があるため、コンパイラのメモリ消費が削減されます。

PassManager の有効性は、スケジュールしているパスの動作に関する情報量に直接影響されます。たとえば、「保持された」セットは、実装されていない getAnalysisUsage メソッドに直面した場合、意図的に控えめになります。実装すべきときに実装しないと、パスの実行中に解析結果を保持できなくなります。

PassManager クラスは、パスの実行をデバッグしたり、動作を確認したり、現在保持しているよりも多くの解析を保持する必要がある場合を診断したりするのに役立つ --debug-pass コマンドラインオプションを公開します。(--debug-pass オプションのすべてのバリアントに関する情報を取得するには、「llc -help-hidden」と入力してください)。

たとえば、–debug-pass=Structureオプションを使用すると、デフォルトの最適化パイプラインを調べることができます(出力はトリミングされています)。

$ llc -mtriple=arm64-- -O3 -debug-pass=Structure file.ll > /dev/null
(...)
ModulePass Manager
Pre-ISel Intrinsic Lowering
FunctionPass Manager
  Expand large div/rem
  Expand large fp convert
  Expand Atomic instructions
SVE intrinsics optimizations
  FunctionPass Manager
    Dominator Tree Construction
FunctionPass Manager
  Simplify the CFG
  Dominator Tree Construction
  Natural Loop Information
  Canonicalize natural loops
(...)

releaseMemory メソッド

virtual void releaseMemory();

PassManager は、解析結果をいつ計算するか、およびそれらをどのくらいの期間保持するかを自動的に決定します。パスオブジェクト自体の有効期間は、事実上、コンパイルプロセス全体であるため、解析結果が不要になった場合に解放する方法が必要です。releaseMemory 仮想メソッドは、これを行う方法です。

解析、または(「必須」としてパスを必要とし、getAnalysis メソッドを使用する別のパスで使用するために)大量の状態を保持するその他のパスを作成する場合は、この内部状態を維持するために割り当てられたメモリを解放するために releaseMemory を実装する必要があります。このメソッドは、クラスの run* メソッドの後、パス内の run* の次の呼び出しの前に呼び出されます。

動的にロードされたパスの登録

LLVMを使用して高品質の製品ツールを構築する場合、配布目的と、ターゲットシステムで実行するときの常駐コードサイズの規制の両方で、サイズが重要になります。したがって、いくつかのパスを選択的に使用し、他のパスを省略し、後で構成を変更する柔軟性を維持することが望ましくなります。これらすべてを実行し、ユーザーにフィードバックを提供できるようにする必要があります。ここで、パス登録が登場します。

パス登録の基本的なメカニズムは、MachinePassRegistry クラスと MachinePassRegistryNode のサブクラスです。

MachinePassRegistry のインスタンスは、MachinePassRegistryNode オブジェクトのリストを維持するために使用されます。このインスタンスは、リストを維持し、コマンドラインインターフェースへの追加と削除を伝達します。

MachinePassRegistryNode サブクラスのインスタンスは、特定のパスに関する情報(コマンドライン名、コマンドヘルプ文字列、パスのインスタンスを生成するために使用される関数のアドレスなど)を保持するために使用されます。これらのインスタンスのグローバル静的コンストラクタは、対応する MachinePassRegistry登録し、静的デストラクタは登録解除します。したがって、ツールに静的にリンクされたパスは起動時に登録されます。動的にロードされたパスはロード時に登録され、アンロード時に登録解除されます。

既存のレジストリの使用

命令スケジューリング (RegisterScheduler) およびレジスタ割り当て (RegisterRegAlloc) マシンパスを追跡するための事前定義されたレジストリがあります。ここでは、レジスタアロケータマシンパスを登録する方法について説明します。

レジスタアロケータマシンパスを実装します。レジスタアロケータの .cpp ファイルに、次のインクルードを追加します。

#include "llvm/CodeGen/RegAllocRegistry.h"

また、レジスタアロケータの .cpp ファイルで、次のような形式でクリエータ関数を定義します。

FunctionPass *createMyRegisterAllocator() {
  return new MyRegisterAllocator();
}

この関数のシグネチャは、RegisterRegAlloc::FunctionPassCtor の型と一致する必要があることに注意してください。同じファイルに、次のような形式で「インストール」宣言を追加します。

static RegisterRegAlloc myRegAlloc("myregalloc",
                                   "my register allocator help string",
                                   createMyRegisterAllocator);

ヘルプ文字列の前に2つのスペースがあることに注意してください。これにより、-help クエリで整然とした結果が得られます。

$ llc -help
  ...
  -regalloc                    - Register allocator to use (default=linearscan)
    =linearscan                -   linear scan register allocator
    =local                     -   local register allocator
    =simple                    -   simple register allocator
    =myregalloc                -   my register allocator help string
  ...

これで完了です。ユーザーは -regalloc=myregalloc をオプションとして自由に使用できます。命令スケジューラの登録も同様ですが、RegisterScheduler クラスを使用します。RegisterScheduler::FunctionPassCtorRegisterRegAlloc::FunctionPassCtor と大幅に異なることに注意してください。

レジスタアロケータのロード/リンクを llc/lli ツールに強制するには、クリエータ関数のグローバル宣言を Passes.h に追加し、llvm/Codegen/LinkAllCodegenComponents.h に「疑似」呼び出し行を追加します。

新しいレジストリの作成

開始する最も簡単な方法は、既存のレジストリのいずれかを複製することです。llvm/CodeGen/RegAllocRegistry.h を推奨します。変更する主な点は、クラス名と FunctionPassCtor 型です。

次に、レジストリを宣言する必要があります。例:パスレジストリが RegisterMyPasses の場合、次のように定義します。

MachinePassRegistry<RegisterMyPasses::FunctionPassCtor> RegisterMyPasses::Registry;

最後に、パスのコマンドラインオプションを宣言します。例:

cl::opt<RegisterMyPasses::FunctionPassCtor, false,
        RegisterPassParser<RegisterMyPasses> >
MyPassOpt("mypass",
          cl::init(&createDefaultMyPass),
          cl::desc("my pass option help"));

ここで、コマンドオプションは「mypass」で、デフォルトのクリエータは createDefaultMyPass です。

動的にロードされたパスでの GDB の使用

残念ながら、動的にロードされたパスで GDB を使用することは、本来あるべきほど簡単ではありません。まず、まだロードされていない共有オブジェクトにブレークポイントを設定することはできず、次に共有オブジェクト内のインライン関数に問題があります。GDB でパスをデバッグするためのいくつかの提案を以下に示します。

説明のために、ここでは opt によって呼び出される変換をデバッグしていると想定しますが、ここで説明することはそれに依存しません。

パスにブレークポイントを設定する

最初に、opt プロセスで gdb を起動します。

$ gdb opt
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.6"...
(gdb)

opt には多くのデバッグ情報が含まれているため、ロードに時間がかかることに注意してください。辛抱強くお待ちください。まだパスにブレークポイントを設定することはできないため(共有オブジェクトは実行時までロードされません)、プロセスを実行し、パスを呼び出す前、ただし共有オブジェクトをロードした後に停止させる必要があります。これを実行する最も確実な方法は、PassManager::run にブレークポイントを設定し、必要な引数を付けてプロセスを実行することです。

$ (gdb) break llvm::PassManager::run
Breakpoint 1 at 0x2413bc: file Pass.cpp, line 70.
(gdb) run test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Starting program: opt test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Breakpoint 1, PassManager::run (this=0xffbef174, M=@0x70b298) at Pass.cpp:70
70      bool PassManager::run(Module &M) { return PM->run(M); }
(gdb)

optPassManager::run メソッドで停止したら、パスにブレークポイントを設定して、実行をトレースしたり、その他の標準的なデバッグ作業を行うことができます。

その他の問題

基本がわかったら、GDB にはいくつかの問題があります。解決策があるものもあれば、ないものもあります。

  • インライン関数には誤ったスタック情報があります。一般に、GDB はスタックトレースを取得し、インライン関数をステップ実行するのに非常に優れています。ただし、パスが動的にロードされると、この機能が完全に失われます。私が知っている唯一の解決策は、関数を非インライン化することです(クラスの本体から .cpp ファイルに移動します)。

  • プログラムを再起動するとブレークポイントが壊れます。上記の情報を実行した後、パスにいくつかのブレークポイントを配置することに成功しました。次に、プログラムを再起動する(つまり、「run」を再度入力する)と、ブレークポイントを設定できないというエラーが発生し始めます。この問題を「修正」する唯一の方法は、パスにすでに設定されているブレークポイントを削除し、プログラムを実行し、実行が PassManager::run で停止したらブレークポイントを再設定することです。

これらのヒントが、一般的なデバッグ状況に役立つことを願っています。独自のヒントを提供したい場合は、Chris にご連絡ください。