Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

JVMのControl Flow Graph可視化

GraalでのOSRを見たとき、"Control Flow Graph: 制御フローグラフ"(CFG)が出てきました。

Register AllocationとCFGは密接に関わります。さて、JavaでのCFGを検索すると論文が出てきました。

Thomas Würthingerさんの"Visualization of Java Control Flow Graphs"です。 https://pdfs.semanticscholar.org/1f9f/d3658f77aaa4d144d8b7e59bfe4cd63d4475.pdf

またまたヨハネス・ケプラー大学の論文です。前回読んだlinear scan register allocationの論文もこちらの大学でした。そして、Graalな方はピンときたでしょう。そう、Thomas Würthingerさん。現在はOracle LabsでGraalの開発をしておられる方です!

http://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO12-Polygot-interview.pdf

この論文では、Java-XX:+PrintCFGToFileした際のファイルを可視化するツールを作成しています。それは、現在までc1visualizerというツールで公開されています。

起動すると、ヨハネス・ケプラー大学のエンブレムがあることがわかります。ただ、以前はjava.netで公開されていましたが、java.netは閉鎖されました。今c1visualizerをダウンロードできる場所について、Davidさんがツイートしてくれています。

at(オーストリアドメイン、URLから推測するにヨハネス・ケプラー大学のサーバのようです。c1visualizerはダウンロードして解凍するだけでOKです。ひとまず1.4を使いました。

さてCFGを出力するため、サンプルコードを使います。

class Demo {
    public static void main(String[] args) {
        while (true) {
            new Demo().workload(14, 2);
        }
    }

    private int workload(int a, int b) {
        return a + b;
    }
}

-XX:+PrintCFGToFileして実行しましょう。

$ java -XX:+PrintCFGToFile Demo
Error: VM option 'PrintCFGToFile' is develop and is available only in debug version of VM.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

デバッグバージョンでしかこのオプションは使えません。OpenJDKをデバッグビルドします。こちらを参考にしてください。基本的には--enable-debugをつけてconfigureするだけです。

jyukutyo.hatenablog.com

$ java -XX:+PrintCFGToFile -XX:CompileOnly=Demo.workload -Xcomp -XX:-Inline Demo

-XX:+PrintCFGToFileはC1コンパイルでのCFGを出力します。C2だけだと何もでません。今はデフォルトでTieredCompilationが有効ですが、そうでない場合は-XX:+TieredCompilationで有効にしてください。

無限ループなので適当に停止します。CompileOnlyなしだと全メソッドのCFGが出て出力量が多くなりすぎりので、指定します。-XcompはインタープリトせずすぐにJITコンパイルします。-XX:-Inlineはインライン化をしない設定です。

すると.cfgファイルが出力されます。

$ ls -lat
...
-rw-r--r--   1 jyukutyo  jyukutyo    13452 12 15 19:00 output_tid23299_pid22562.cfg
...

このファイルはテキストファイルです。

begin_compilation
  name " Demo::workload"
  method "virtual jint Demo.workload(jint, jint)"
  date 1513329125814
end_compilation
begin_cfg
  name "BlockListBuilder virtual jint Demo.workload(jint, jint)"
  begin_block
    name "B0"
    from_bci 0
    to_bci -1
    predecessors
    successors
    xhandlers
    flags "std"
  end_block
end_cfg
...

CFGとしてブロックとそのブロックの各predecessors(先行ブロック)/successors(後続ブロック)、bci(バイトコードインデックス)といった情報が出力されます。こういった情報をテキストで見ても、わかりづらいですね。なのでc1visualizerで可視化します。binディレクトリにあるc1visualizerコマンドを実行します。Java 9だとそのままでは動作しないので、8以前で起動します。

c1visualizerでさきほどの.cfgファイルを読み込みます。

f:id:jyukutyo:20171215181435p:plain

メソッドのJITコンパイルに至るまでの各状態を見ることができます。たとえば"After Generation of HIR"でHIRの状態が見れます。

virtual jint Demo.workload(jint, jint)
2017/12/15 18:12:05
After Generation of HIR

B1 -> B2 [0, 0]
  Locals size 3 [virtual jint Demo.workload(jint, jint)]
    0    a1   [method parameter]
    1    i2   [method parameter]
    2    i3   [method parameter]
_p__bci__use__tid__instruction__________________________________________________ (HIR)
 .  0    0    v9   std entry B2
  
B2 <- B1 -> B0 [0, 0] std
  Locals size 3 [virtual jint Demo.workload(jint, jint)]
    1    i2   
    2    i3   
_p__bci__use__tid__instruction__________________________________________________ (HIR)
 .  0    0    v8   goto B0
  
B0 <- B2 [0, 3] std
  Locals size 3 [virtual jint Demo.workload(jint, jint)]
    1    i2   
    2    i3   
_p__bci__use__tid__instruction__________________________________________________ (HIR)
    2    0    i4   i2 + i3
 .  3    0    i5   ireturn i4

3つのブロックB0、B1、B2があり、B1からB2、B2からB0へ遷移して最後returnという流れです。

"After Register Allocation"では、レジスタ割り当てが終わったあとの各intervalの状態が見れます。

f:id:jyukutyo:20171215182618p:plain

各行がintervalに対応しています。Number 0にはrsiを割り当てています。今回の割り当てではspillしているintervalはありませんでした。

LIRも見れます。

B1 -> B2 [0, 0]
_nr__instruction________________________________________________________________ (LIR)
 0   label [label:0x00007fa62983b890]
 2   std_entry
 4   move [rsi|L] [R569|L]
 6   move [rdx|I] [R570|I]
 8   move [rcx|I] [R571|I]
 10  move [metadata:0x000000012722f128|M] [R572|M]
 12  move [Base:[R572|M] Disp: 300|I] [R573|I]
 14  add [R573|I] [int:8|I] [R573|I]
 16  move [R573|I] [Base:[R572|M] Disp: 300|I]
 18  branch [AL] [CounterOverflowStub: 0x00007fa62a1194d0]
 20  label [label:0x00007fa62a119500]
 22  branch [AL] [B2]

かなりマシンコードに近い表現になっています。c1visualizerでCFGやHIR、LIRを可視化できます。

さらに各ダイアグラムを右クリックすると"Open Control Flow Graph"があります。これを選択すると、CFGを見れます。

f:id:jyukutyo:20171218142017p:plain

この例ではCFGが単純すぎました。次回以降でもう少し複雑なコードでCFGを見てみます。