GraalVM の native-image によるバイナリ生成時にグラフをダンプする方法を調べたのでまとめておく。参考にしたサイトは以下。
- Command-line Options - Graph Dumping
- Native Image 機能使用時にグラフをダンプするためのオプションについて (公式ドキュメント)
- Ideal Graph Visualizer
- ダンプしたグラフを表示したり解析したりするための GUI ツール Ideal Graph Visualizer (IGV) についての公式ドキュメント
- Understanding Basic Graal Graphs – Logico Inside
- Shopify の中の人の記事の日本語訳
- グラフの見方について詳しく説明されている
使用するサンプルコードはこちら。グラフの内容には触れないのでなんでもよいのだけれど、oracle/graal#9320 の issue について調べている過程でグラフを見てみたくなってダンプ方法を調べたので、こちらの issue の再現コードを使用する。
- zzz.java
public class zzz {
public static void main(String args[]) {
hoge();
}
public static void hoge() {
int[] pos = new int[11];
int mov = 1;
for (; ; ) {
for (int i = pos.length - 1; i > 0; i--) {
pos[i] = pos[i - 1];
}
pos[0] += mov;
}
}
}
グラフのダンプ
結論、ダンプコマンドは以下の通り。zzz
クラスの hoge
メソッドをダンプ対象として指定している。
$ native-image zzz -H:Dump= -H:MethodFilter=zzz.hoge
コマンド実行後、graal_dumps というディレクトリが作成され、2つの bgv ファイルが生成される。
$ tree graal_dumps/
graal_dumps/
└── 2025.01.23.01.16.40.367
├── PointsToAnalysisMethod:18438[zzz.hoge()void].bgv
└── SubstrateHostedCompilation-3245[zzz.hoge()void].bgv
2 directories, 2 files
IGV でグラフを表示する
次に、ダンプしたグラフを表示する。まずは mx にパスが通っている場所で以下のコマンドを実行する。
$ mx igv
すると、冒頭でも触れた Ideal Graph Visualizer (IGV) が起動するので、bgv ファイルを開いていく。
2つ同時に選択。
以下の通り、Outline の領域に読み込んだ内容が表示される。
試しに一つ開いてみた様子が以下。無事グラフが表示された。
ソースコードを紐づけることもできる。Source Collections という領域で右クリックし、Add source root を選択。 (Source Group が何かはわかりません・・)
ソースコードが置いてあるフォルダを選択すると以下の通りソースが読み込まれる。
すると、グラフ上のノードを選択すると対応するソースコードの行がハイライトされるようになる。
グラフがダンプされるタイミング
最後に、グラフがダンプされるタイミングについて触れておく。先ほど貼った画像を再掲。一つの bgv ファイルにつき一つのグラフ、というわけではないらしい。
名前からして、Performing analysis ステージと Compiling methods ステージ中にダンプされたように見える。なお、これらのステージで何をしているかについては以下の記事で触れている。
実際にコードを確認してみる。まずは InlineBeforeAnalysis
クラスを見てみると、グラフをダンプしている箇所を発見。
public static StructuredGraph decodeGraph(BigBang bb, AnalysisMethod method, AnalysisParsedGraph analysisParsedGraph) {
...
StructuredGraph result = new StructuredGraph.Builder(bb.getOptions(), debug, bb.getHostVM().allowAssumptions(method))
.method(method)
.trackNodeSourcePosition(analysisParsedGraph.getEncodedGraph().trackNodeSourcePosition())
.recordInlinedMethods(analysisParsedGraph.getEncodedGraph().isRecordingInlinedMethods())
.build();
...
debug.dump(DebugContext.BASIC_LEVEL, result, "InlineBeforeAnalysis after decode");
...
こちらは案の定、Performing analysis ステージ内で実行されていた。
次に、もう一方のダンプがいつ実行されているかを確認。Before phase
などの文字列でソースコードを検索した結果、一つ目のグラフは以下の箇所でダンプされていた。
private boolean dumpBefore(final StructuredGraph graph, final C context, boolean isTopLevel, boolean applied) {
...
debug.dump(DebugContext.BASIC_LEVEL, graph, "Before phase %s%s", getName(), tag);
...
こちらは以前の記事でも見た emitFrontEnd
メソッド内の suites.getHighTier().apply(graph, highTierContext)
という処理の中で呼ばれている。また、以降の処理中で After xxx tier
という名前でグラフをダンプしていることもわかる。
public static void emitFrontEnd(Providers providers, TargetProvider target, StructuredGraph graph, PhaseSuite<HighTierContext> graphBuilderSuite, OptimisticOptimizations optimisticOpts,
ProfilingInfo profilingInfo, Suites suites) {
...
suites.getHighTier().apply(graph, highTierContext);
graph.maybeCompress();
debug.dump(DebugContext.BASIC_LEVEL, graph, "After high tier");
MidTierContext midTierContext = new MidTierContext(providers, target, optimisticOpts, profilingInfo);
suites.getMidTier().apply(graph, midTierContext);
graph.maybeCompress();
debug.dump(DebugContext.BASIC_LEVEL, graph, "After mid tier");
LowTierContext lowTierContext = new LowTierContext(providers, target);
suites.getLowTier().apply(graph, lowTierContext);
debug.dump(DebugContext.BASIC_LEVEL, graph, "After low tier");
...
つまり、こちらも案の定 Compiling methods ステージ内でダンプが実行されているということになる。