前回はグラフノードの生成を見ました。
この記事に沿ってGraalの理解を深めています。
Understanding How Graal Works - a Java JIT Compiler Written in Java
最終的には、グラフにある各ノードに対してマシンコードを生成します(グラフ自体を簡素化するアプローチは別途)。マシンコードが関連してくるので、メソッドの処理を加算ではなくインクリメントにします。
class Demo2 { public static void main(String[] args) { while (true) { new Demo2().workload(14); } } private int workload(int a) { return a + 1; } }
マシンコードは、org.graalvm.compiler.asm.Assemblerクラスのサブクラスで生成します。
/** * The platform-independent base class for the assembler. */ public abstract class Assembler {
マシンコードですので、各CPUごとにサブクラスがあります。
Assembler (org.graalvm.compiler.asm)
AArch64Assembler (org.graalvm.compiler.asm.aarch64)
AArch64MacroAssembler (org.graalvm.compiler.asm.aarch64)
TestProtectedAssembler (org.graalvm.compiler.asm.aarch64.test)
AMD64Assembler (org.graalvm.compiler.asm.amd64)
AMD64MacroAssembler (org.graalvm.compiler.asm.amd64)
SPARCAssembler (org.graalvm.compiler.asm.sparc)
SPARCMacroAssembler (org.graalvm.compiler.asm.sparc)
Intel 64ビットCPUですので、AMD64Assemblerです。CPUのinstructionと対応するので、INC命令と対応するメソッドがあります。
protected final void incl(Register dst) { // Use two-byte form (one-byte from is a REX prefix in 64-bit mode) int encode = prefixAndEncode(dst.encoding); emitByte(0xFF); emitByte(0xC0 | encode); }
このコードの理解を深めるため、"Intel® 64 and IA-32 Architectures Software Developer’s Manual"を見ます。Instruction Set Referenceですので、INC命令の記載があります。

64ビットの場合REX.W + FF /0のようです。Javaコードのコメントにある"Use two-byte form (one-byte from is a REX prefix in 64-bit mode)"と合っています。emitByte()メソッドに0xFFを渡しているのも、INC命令のオペコードがFFだからですね。そのあとの0xC0 | encodeが何を表すか、僕にはわかっていません。
emitByte()は最終的にorg.graalvm.compiler.asm.Buffer#emitByte()を実行します。
public void emitByte(int b) { assert NumUtil.isUByte(b) || NumUtil.isByte(b); ensureSize(data.position() + 1); data.put((byte) (b & 0xFF)); }
このbはincl()メソッドで渡した0xFFなんですが、どうしてここで別の0xFFとビット演算の論理積を取るんでしょうね??
dataはjava.nio.ByteBufferオブジェクトです。ByteBuffer#array()でバイト配列になります。
さて、生成されたマシンコードを出力してみましょう。org.graalvm.compiler.hotspot.HotSpotGraalCompilerクラスに戻ります。
public CompilationResult compileHelper(CompilationResultBuilderFactory crbf, CompilationResult result, StructuredGraph graph, ResolvedJavaMethod method, int entryBCI, boolean useProfilingInfo, OptionValues options) { ... System.err.println(method.getName() + " machine code: " + Arrays.toString(result.getTargetCode())); return result; }
$ java \ --module-path=/Users/jyukutyo/code/graal/sdk/mxbuild/modules/org.graalvm.graal_sdk:/Users/jyukutyo/code/graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \ --upgrade-module-path=/Users/jyukutyo/code/graal/compiler/mxbuild/modules/jdk.internal.vm.compiler \ -XX:+UnlockExperimentalVMOptions \ -XX:+EnableJVMCI \ -XX:+UseJVMCICompiler \ -XX:-TieredCompilation \ -XX:+PrintCompilation \ -XX:CompileOnly=Demo2::workload \ -XX:CompileCommand=quiet \ Demo2 ... workload bytecode: [27, 4, 96, -84] ... workload machine code: [68, -117, 86, 8, 73, -63, -30, 3, 73, 59, -62, 15, -123, -17, -1, -1, -1, -112, 15, 31, -128, 0, 0, 0, 0, 15, 31, -128, 0, 0, 0, 0, 15, 31, 68, 0, 0, -1, -62, -117, -62, -123, 5, 0, 0, 0, 0, -59, -8, 119, -61, -24, 0, 0, 0, 0, -112, -24, 0, 0, 0, 0, -112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
マシンコードが出ましたが、何もわかりません!disassembleしましょう。そのためにはhsdis(HotSpot disAssembler)が必要です。これはGraal関連のことではなく、HotSpot標準です。ビルド方法はこちらを参照ください。最新のbinutilsではうまくビルドできなかったので、2.27を利用しました。
ビルドするとbuild/macosx-amd64ディレクトリにhsdis-amd64.dylibがあるので、これを適切なディレクトリに置きます。JDK 9なら$JAVA_HOME/lib/です。disassembleした結果を出力するために2つオプションを追加します。-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssemblyです。
$ java \ --module-path=/Users/jyukutyo/code/graal/sdk/mxbuild/modules/org.graalvm.graal_sdk:/Users/jyukutyo/code/graal/truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar \ --upgrade-module-path=/Users/jyukutyo/code/graal/compiler/mxbuild/modules/jdk.internal.vm.compiler \ -XX:+UnlockExperimentalVMOptions \ -XX:+EnableJVMCI \ -XX:+UseJVMCICompiler \ -XX:-TieredCompilation \ -XX:+PrintCompilation \ -XX:CompileOnly=Demo2::workload \ -XX:CompileCommand=quiet \ -XX:+UnlockDiagnosticVMOptions \ -XX:+PrintAssembly \ Demo2 Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output Loaded disassembler from /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/lib/hsdis-amd64.dylib
かなり長い出力が出ますが、最後にこのメソッドのdisassembleした出力があります。
----------------------------------------------------------------------
Demo2.workload(I)I (Demo2.workload(int)) [0x000000010b997b20, 0x000000010b997b60] 64 bytes
[Entry Point]
[Constants]
# {method} {0x000000012709faf8} 'workload' '(I)I' in 'Demo2'
# this: rsi:rsi = 'Demo2'
# parm0: rdx = int
# [sp+0x10] (sp of caller)
0x000000010b997b20: mov 0x8(%rsi),%r10d
0x000000010b997b24: shl $0x3,%r10
0x000000010b997b28: cmp %r10,%rax
0x000000010b997b2b: jne 0x000000010b92f000 ; {runtime_call ic_miss_stub}
0x000000010b997b31: nop
0x000000010b997b32: nopl 0x0(%rax)
0x000000010b997b39: nopl 0x0(%rax)
[Verified Entry Point]
0x000000010b997b40: nopl 0x0(%rax,%rax,1)
0x000000010b997b45: inc %edx ;*iadd {reexecute=0 rethrow=0 return_oop=0}
; - Demo2::workload@2 (line 9)
0x000000010b997b47: mov %edx,%eax ;*ireturn {reexecute=0 rethrow=0 return_oop=0}
; - Demo2::workload@3 (line 9)
0x000000010b997b49: test %eax,-0x15a2b49(%rip) # 0x000000010a3f5006
; {poll_return}
0x000000010b997b4f: vzeroupper
0x000000010b997b52: retq
[Exception Handler]
0x000000010b997b53: callq 0x000000010b997480 ; {runtime_call Stub<ExceptionHandlerStub.exceptionHandler>}
0x000000010b997b58: nop
[Deopt Handler Code]
0x000000010b997b59: callq 0x000000010b92ff20 ; {runtime_call DeoptimizationBlob}
0x000000010b997b5e: nop
[Stub Code]
0x000000010b997b5f: hlt
0x000000010b997b45: inc %edx ;*iadd {reexecute=0 rethrow=0 return_oop=0}
; - Demo2::workload@2 (line 9)で、Javaバイトコードiaddが、CPUのINC命令となっていることが読み取れます。
まとめ
インクリメントするコードは、[27, 4, 96, -84] -> [68, -117, 86, 8, 73, -63, -30, 3, 73, 59, -62, 15, -123, ...]とJITコンパイルでマシンコードに変換されることがわかりました。