前回はグラフノードの生成を見ました。
この記事に沿って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コンパイルでマシンコードに変換されることがわかりました。