フォーラム » つれづれ Java編 »
返答 (2)
高徹 JITが生成する機械語のアセンブリコードを確認したい - 高橋 徹 さんが2日前に追加
JavaVMのJITコンパイラが生成するネイティブの機械語のアセンブリコードを見ることができれば、どのような最適化がなされているか分かります。
JavaVMには、-XX:+PrintAssembly オプションがあり、JITがコンパイルした機械語のアセンブリコードを参照する基本的な枠組みがあります。
C:\work> java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly MulDivShift
ただし、普通にOpenJDKディストリビューションを入れただけでは、このオプションを指定しても機械語の16進数値しか表示されません。アセンブリコードを見るには、さらに hsdis(HotSpot DISassembler)が必要になります。hsdisは、OpenJDKプロジェクトに含まれるJavaVMのプラグインライブラリで、JITが生成した機械語からアセンブリコードに逆アセンブルします。ただし hsdisはOpenJDKディストリビューションには含まれていないので、次のどちらかで用意します。
- ビルド済みのhsdisライブラリを探して入手する
- OpenJDKのソースコードを入手して自分でビルドする
ビルド済みのhsdisライブラリ¶
ビルド済みのhsdisライブラリを公式に提供しているサイトはないので、野良ビルド的なものを取得することになります。このhsdisはOpenJDKのバージョンに依存するので、使用するJDKバージョンと同じバージョンのhsdisを探して取得しないと動きません。これが中な大変です。
次のサイトでは、本日(2026-03-03)現在、OpenJDK 17のソースコードからビルドしたhsdisライブラリを公開しています。
https://chriswhocodes.com/hsdis/
Windows OSの場合、このサイトから hsdis-amd64.dll を入手し、OpenJDK 17ディストリビューションをインストールし、OpenJDK 17のインストールディレクトリ\bin\serverの下にhsdis-amd64.dllを保存します。
C:\work> java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly MulDivShift
:
[Verified Entry Point]
# {method} {0x000001e56a4002e0} 'calc' '(I)I' in 'MulDivShift'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000001e57b228b80: sub $0x18,%rsp
0x000001e57b228b87: mov %rbp,0x10(%rsp) ;*synchronization entry
; - MulDivShift::calc@-1 (line 9)
0x000001e57b228b8c: mov %edx,%eax
0x000001e57b228b8e: sar $0x1f,%eax
0x000001e57b228b91: shr $0x1e,%eax
0x000001e57b228b94: add %edx,%eax
0x000001e57b228b96: sar $0x2,%eax ;*idiv {reexecute=0 rethrow=0 return_oop=0}
; - MulDivShift::calc@2 (line 9)
0x000001e57b228b99: add $0x10,%rsp
0x000001e57b228b9d: pop %rbp
0x000001e57b228b9e: cmp 0x348(%r15),%rsp ; {poll_return}
0x000001e57b228ba5: ja 0x000001e57b228bac
0x000001e57b228bab: ret
このアセンブリコード(ニーモニック)はAT&T記法です。Intel記法で出すには、-XX:PrintAssemblyOptions=intel を指定します。
C:\work> java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:PrintAssemblyOption=intel MulDivShift
JITコンパイラは賢いので、短いメソッドは呼び出し元の箇所へインライン化したり、ある一定回数以上実行されたコードでないとJITコンパイルしないとか、第1段階では軽い最適化(C1コンパイル)で第2段階で深い最適化(C2コンパイル)するなどの振る舞いを持ちます。JITが吐き出すアセンブリコードを見るには、
C:\work> java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:PrintAssemblyOption=intel ^
-XX:-TieredCompilation -XX:-Inline -XX: MulDivShift
高徹 hsdisについて補足 - 高橋 徹 さんが約16時間前に追加
hsdisが逆アセンブルを行う際に利用するライブラリは、hsdisのビルド時に次の3つから選択します。
- GNU binutils
- LLVM
- Capstone
hsdisは最初はGNU binutilsを利用して逆アセンブルを行い、その後LLVMとCapstoneを利用することが可能になりました。
GNU binutilsはGPLv3のライセンスで提供され、OpenJDKのGPLv2とは互換性がありません。そのため、hsdisのバイナリを提供するとなると、ちょっと困ったことになります。これが hsdis のバイナリが大っぴらに提供されていない理由かもしれません。また、binutilsのABIがバージョンアップで不安定、libbfdが重い、Windows OS環境ではMSYS2とMinGW64を使う必要がある、などの課題もあります。
LLVMはコンパイラ基盤で、逆アセンブラ機能(MC Disassembler API)を使います。
ビルドにはLLVMをインストールする必要がありますが、そこそこ大きなソフトウェアです。
Capstoneは、逆アセンブラ機能そのものです。