-opt-bisect-limit を使用した最適化エラーのデバッグ¶
はじめに¶
-opt-bisect-limit オプションは、パスマネージャの構成方法を変更せずに、指定された制限を超えるすべての最適化パスを無効にする方法を提供します。このオプションの目的は、最適化中の誤った変換がランタイム時の誤った動作を引き起こす問題の追跡を支援することです。
この機能は、オプトイン方式で実装されています。正しくコード生成できる状態で安全にスキップできるパスは、最適化を実行する前に opt-bisect の制限を確認する関数を呼び出します。実行が必須であるパスまたは IR を変更しないパスは、このチェックを実行しないため、スキップされることはありません。一般的に、これは解析パス、CodeGenOptLevel::None で実行されるパス、およびレジスタ割り当てに必要なパスを意味します。
-opt-bisect-limit オプションは、最適化とコード生成にコア LLVM ライブラリを使用する clang などのフロントエンドを含む、あらゆるツールで使用できます。オプションを呼び出すための正確な構文については、以下で説明します。
この機能は、bugpoint などの他のデバッグツールを置き換えることを目的としたものではありません。むしろ、問題を再現するために bugpoint の使用が非現実的になるような複雑なビルドインフラストラクチャが必要な場合や、opt や llc などのツールで複製が困難な一連の変換が必要な場合に、代替手段を提供します。
はじめに¶
-opt-bisect-limit コマンドラインオプションは、opt、llc、lli などのツールに直接渡すことができます。構文は次のとおりです。
<tool name> [other options] -opt-bisect-limit=<limit>
-1 の値が使用された場合、ツールはすべての最適化を実行しますが、スキップできる各最適化に対して、その最適化に関連付けられているインデックス値を示すメッセージが stderr に出力されます。最適化をスキップするには、実行する最後の最適化の値を opt-bisect-limit として渡します。より高いインデックス値を持つすべての最適化はスキップされます。
LLVM コアライブラリのラッパーを提供するドライバで -opt-bisect-limit オプションを使用するには、ドライバで定義されている追加のプレフィックスオプションが必要になる場合があります。たとえば、このオプションを clang で使用するには、"-mllvm" プレフィックスを使用する必要があります。一般的な clang の呼び出しは次のようになります。
clang -O2 -mllvm -opt-bisect-limit=256 my_file.c
-opt-bisect-limit オプションは、リンカのプラグインオプションであることを示すプレフィックスを使用することで、リンク時最適化にも適用できます。次の構文は、LTO 変換の二分割制限を設定します。
# When using lld, or ld64 (macOS)
clang -flto -Wl,-mllvm,-opt-bisect-limit=256 my_file.o my_other_file.o
# When using Gold
clang -flto -Wl,-plugin-opt,-opt-bisect-limit=256 my_file.o my_other_file.o
LTO パスは、リンカによって呼び出されるライブラリインスタンスによって実行されます。したがって、プライマドライバのコンパイルフェーズで実行されるパスは、'-Wl,-plugin-opt' を介して渡されるオプションの影響を受けず、LTO パスは '-mllvm' を介してドライバによって呼び出される LLVM の呼び出しに渡されるオプションの影響を受けません。
-opt-bisect-print-ir-path=path/foo.ll
を渡すと、-opt-bisect-limit がパスのスキップを開始するときに、IR が path/foo.ll
にダンプされます。
二分インデックス値¶
単一のインデックス値に関連付けられた最適化の粒度は可変です。最適化パスがどのようにインストルメント化されたかに応じて、値は、呼び出された IR ユニットに対して最適化パスによって実行されるすべての変換(たとえば、FunctionPass の runOnFunction の 1 回の呼び出し中)または単一の変換に関連付けられる可能性があります。インデックス値はネストすることもでき、パスの呼び出しがスキップされない場合、その呼び出し内の個々の変換をスキップできます。
割り当てられた値の順序は、制限として指定された値まで、実行ごとに安定しており、一貫性が保たれることが保証されています。制限値を超えると、最適化のスキップによって番号付けが変更される可能性がありますが、制限を超えるすべての最適化がスキップされるため、これは問題ではありません。
opt-bisect インデックス値がパスの run 関数の呼び出し全体を参照する場合、パスは呼び出されるたびにスキップする必要があるかどうかを照会し、各呼び出しに一意の値が割り当てられます。たとえば、モジュールに 3 つの関数を含む FunctionPass が使用されている場合、パスの実行時に各関数に対してパスに異なるインデックス値が割り当てられます。パスは 2 つの関数で実行できますが、3 番目の関数ではスキップされます。
パスが内部的に小さな IR ユニットで操作を実行する場合、このより細かい粒度で二分割を有効にするには、パスを特別にインストルメント化する必要があります(詳細については以下を参照)。
使用例¶
$ opt -O2 -o test-opt.bc -opt-bisect-limit=16 test.ll
BISECT: running pass (1) Simplify the CFG on function (g)
BISECT: running pass (2) SROA on function (g)
BISECT: running pass (3) Early CSE on function (g)
BISECT: running pass (4) Infer set function attributes on module (test.ll)
BISECT: running pass (5) Interprocedural Sparse Conditional Constant Propagation on module (test.ll)
BISECT: running pass (6) Global Variable Optimizer on module (test.ll)
BISECT: running pass (7) Promote Memory to Register on function (g)
BISECT: running pass (8) Dead Argument Elimination on module (test.ll)
BISECT: running pass (9) Combine redundant instructions on function (g)
BISECT: running pass (10) Simplify the CFG on function (g)
BISECT: running pass (11) Remove unused exception handling info on SCC (<<null function>>)
BISECT: running pass (12) Function Integration/Inlining on SCC (<<null function>>)
BISECT: running pass (13) Deduce function attributes on SCC (<<null function>>)
BISECT: running pass (14) Remove unused exception handling info on SCC (f)
BISECT: running pass (15) Function Integration/Inlining on SCC (f)
BISECT: running pass (16) Deduce function attributes on SCC (f)
BISECT: NOT running pass (17) Remove unused exception handling info on SCC (g)
BISECT: NOT running pass (18) Function Integration/Inlining on SCC (g)
BISECT: NOT running pass (19) Deduce function attributes on SCC (g)
BISECT: NOT running pass (20) SROA on function (g)
BISECT: NOT running pass (21) Early CSE on function (g)
BISECT: NOT running pass (22) Speculatively execute instructions if target has divergent branches on function (g)
... etc. ...
パスのスキップの実装¶
-opt-bisect-limit の実装は、個々のパスが opt-bisect プロセスにオプトインすることに依存します。プロセスを管理する OptBisect オブジェクトは完全に受動的であり、パスの実装方法に関する知識はありません。パスがスキップされる可能性がある場合、パスが実行されるときに、OptBisect オブジェクトを呼び出してスキップする必要があるかどうかを確認する必要があります。
OptBisect オブジェクトは LLVMContext を介してアクセスすることを目的としており、各パスの基本クラスには、すべてのパスでこのチェックを均一にするために詳細を抽象化するヘルパー関数が含まれています。これらのヘルパー関数は次のとおりです。
bool ModulePass::skipModule(Module &M);
bool CallGraphSCCPass::skipSCC(CallGraphSCC &SCC);
bool FunctionPass::skipFunction(const Function &F);
bool LoopPass::skipLoop(const Loop *L);
MachineFunctionPass は、次のように FunctionPass::skipFunction() を使用する必要があります。
bool MyMachineFunctionPass::runOnMachineFunction(Function &MF) {
if (skipFunction(*MF.getFunction())
return false;
// Otherwise, run the pass normally.
}
パスをスキップする必要があるかどうかを確認するために OptBisect クラスを使用するだけでなく、skipFunction()、skipLoop()、および skipBasicBlock() ヘルパー関数は、「optnone」関数属性の存在も探します。呼び出し側のパスは、「optnone」属性が存在するか、opt-bisect-limit に達したためにスキップされているかどうかを判断できません。いずれの場合も動作は同じであるため、これは望ましいことです。
スキップできる LLVM パスの大部分は、上記の方法ですでにインストルメント化されています。新しいパスを追加している場合、または opt-bisect プロセスに含まれていないが、含まれているはずのパスを見つけたと思われる場合は、上記の説明に従って追加できます。
より細かい粒度の追加¶
誤った変換が実行されるパスが特定されたら、どの特定の変換が問題を引き起こしているかを特定するために、さらに分析を実行すると役立つ場合があります。デバッグカウンターをこの目的で使用できます。