虛擬機規范中與運算相關的字節碼指令如下表所示。
| 0x60 |
iadd |
將棧頂兩int型數值相加並將結果壓入棧頂 |
| 0x61 |
ladd |
將棧頂兩long型數值相加並將結果壓入棧頂 |
| 0x62 |
fadd |
將棧頂兩float型數值相加並將結果壓入棧頂 |
| 0x63 |
dadd |
將棧頂兩double型數值相加並將結果壓入棧頂 |
| 0x64 |
isub |
將棧頂兩int型數值相減並將結果壓入棧頂 |
| 0x65 |
lsub |
將棧頂兩long型數值相減並將結果壓入棧頂 |
| 0x66 |
fsub |
將棧頂兩float型數值相減並將結果壓入棧頂 |
| 0x67 |
dsub |
將棧頂兩double型數值相減並將結果壓入棧頂 |
| 0x68 |
imul |
將棧頂兩int型數值相乘並將結果壓入棧頂 |
| 0x69 |
lmul |
將棧頂兩long型數值相乘並將結果壓入棧頂 |
| 0x6a |
fmul |
將棧頂兩float型數值相乘並將結果壓入棧頂 |
| 0x6b |
dmul |
將棧頂兩double型數值相乘並將結果壓入棧頂 |
| 0x6c |
idiv |
將棧頂兩int型數值相除並將結果壓入棧頂 |
| 0x6d |
ldiv |
將棧頂兩long型數值相除並將結果壓入棧頂 |
| 0x6e |
fdiv |
將棧頂兩float型數值相除並將結果壓入棧頂 |
| 0x6f |
ddiv |
將棧頂兩double型數值相除並將結果壓入棧頂 |
| 0x70 |
irem |
將棧頂兩int型數值作取模運算並將結果壓入棧頂 |
| 0x71 |
lrem |
將棧頂兩long型數值作取模運算並將結果壓入棧頂 |
| 0x72 |
frem |
將棧頂兩float型數值作取模運算並將結果壓入棧頂 |
| 0x73 |
drem |
將棧頂兩double型數值作取模運算並將結果壓入棧頂 |
| 0x74 |
ineg |
將棧頂int型數值取負並將結果壓入棧頂 |
| 0x75 |
lneg |
將棧頂long型數值取負並將結果壓入棧頂 |
| 0x76 |
fneg |
將棧頂float型數值取負並將結果壓入棧頂 |
| 0x77 |
dneg |
將棧頂double型數值取負並將結果壓入棧頂 |
| 0x78 |
ishl |
將int型數值左移位指定位數並將結果壓入棧頂 |
| 0x79 |
lshl |
將long型數值左移位指定位數並將結果壓入棧頂 |
| 0x7a |
ishr |
將int型數值右(符號)移位指定位數並將結果壓入棧頂 |
| 0x7b |
lshr |
將long型數值右(符號)移位指定位數並將結果壓入棧頂 |
| 0x7c |
iushr |
將int型數值右(無符號)移位指定位數並將結果壓入棧頂 |
| 0x7d |
lushr |
將long型數值右(無符號)移位指定位數並將結果壓入棧頂 |
| 0x7e |
iand |
將棧頂兩int型數值作“按位與”並將結果壓入棧頂 |
| 0x7f |
land |
將棧頂兩long型數值作“按位與”並將結果壓入棧頂 |
| 0x80 |
ior |
將棧頂兩int型數值作“按位或”並將結果壓入棧頂 |
| 0x81 |
lor |
將棧頂兩long型數值作“按位或”並將結果壓入棧頂 |
| 0x82 |
ixor |
將棧頂兩int型數值作“按位異或”並將結果壓入棧頂 |
| 0x83 |
lxor |
將棧頂兩long型數值作“按位異或”並將結果壓入棧頂 |
| 0x84 |
iinc |
將指定int型變量增加指定值(i++、i--、i+=2) |
| 0x94 |
lcmp |
比較棧頂兩long型數值大小,並將結果(1、0或-1)壓入棧頂 |
| 0x95 |
fcmpl |
比較棧頂兩float型數值大小,並將結果(1、0或-1)壓入棧頂;當其中一個數值為NaN時,將-1壓入棧頂 |
| 0x96 |
fcmpg |
比較棧頂兩float型數值大小,並將結果(1、0或-1)壓入棧頂;當其中一個數值為NaN時,將1壓入棧頂 |
| 0x97 |
dcmpl |
比較棧頂兩double型數值大小,並將結果(1、0或-1)壓入棧頂;當其中一個數值為NaN時,將-1壓入棧頂 |
| 0x98 |
dcmpg |
比較棧頂兩double型數值大小,並將結果(1、0或-1)壓入棧頂;當其中一個數值為NaN時,將1壓入棧頂 |
1、基本加、減、乘與除指令
1、iadd指令
iadd指令將兩個棧頂的整數相加,然后將相加的結果壓入棧頂,其指令的格式如下:
iadd val1,val2
val1與val2表示兩個int類型的整數,在指令執行時,將val1與val3從操作數棧中出棧,將這兩個數值相加得到 int 類型數據 result,將result壓入操作數棧中。
iadd指令的模板定義如下:
def(Bytecodes::_iadd , ____|____|____|____, itos, itos, iop2 , add);
生成函數為TemplateTable::iop2(),實現如下:
void TemplateTable::iop2(Operation op) {
switch (op) {
case add : __ pop_i(rdx); __ addl (rax, rdx); break;
case sub : __ movl(rdx, rax); __ pop_i(rax); __ subl (rax, rdx); break;
case mul : __ pop_i(rdx); __ imull(rax, rdx); break;
case _and : __ pop_i(rdx); __ andl (rax, rdx); break;
case _or : __ pop_i(rdx); __ orl (rax, rdx); break;
case _xor : __ pop_i(rdx); __ xorl (rax, rdx); break;
case shl : __ movl(rcx, rax); __ pop_i(rax); __ shll (rax); break;
case shr : __ movl(rcx, rax); __ pop_i(rax); __ sarl (rax); break;
case ushr : __ movl(rcx, rax); __ pop_i(rax); __ shrl (rax); break;
default : ShouldNotReachHere();
}
}
可以看到,這個函數是許多指令的生成函數,如iadd、isub、imul、iand、ior、ixor、ishl、ishr、iushr。
為iadd指令生成的匯編代碼如下:
mov (%rsp),%edx add $0x8,%rsp add %edx,%eax
將棧頂與棧頂中緩存的%eax相加后的結果存儲到%eax中。
2、isub指令
isub指令生成的匯編代碼如下:
mov %eax,%edx mov (%rsp),%eax add $0x8,%rsp sub %edx,%eax
代碼實現比較簡單,這里不再介紹。
3、idiv指令
idiv是字節碼除法指令,這個指令的格式如下:
idiv val1,val2
val1 和 val2 都必須為 int 類型數據,指令執行時,val1 和 val2從操作數棧中出棧,並且將這兩個數值相除(val1÷val2),結果轉換為 int 類型值 result,最后 result 被壓入到操作數棧中。
idiv指令的模板定義如下:
def(Bytecodes::_idiv , ____|____|____|____, itos, itos, idiv , _ );
調用的生成函數為TemplateTable::idiv(),生成的匯編如下:
0x00007fffe1019707: mov %eax,%ecx 0x00007fffe1019709: mov (%rsp),%eax 0x00007fffe101970c: add $0x8,%rsp // 測試一下被除數是否為0x80000000,如果不是,就跳轉到normal_case 0x00007fffe1019710: cmp $0x80000000,%eax 0x00007fffe1019716: jne 0x00007fffe1019727 // 被除數是0x80000000,而除數如果是-1的話,則跳轉到special_case 0x00007fffe101971c: xor %edx,%edx 0x00007fffe101971e: cmp $0xffffffff,%ecx 0x00007fffe1019721: je 0x00007fffe101972a // -- normal_case -- // cltd將eax寄存器中的數據符號擴展到edx:eax,具體就是 // 把eax的32位整數擴展為64位,高32位用eax的符號位填充保存到edx 0x00007fffe1019727: cltd 0x00007fffe1019728: idiv %ecx // -- special_case --
其中idiv函數會使用規定的寄存器,如下圖所示。

匯編對0x80000000 / -1 這個特殊的除法做了檢查。參考:利用反匯編調試與補碼解釋0x80000000 / -1整形輸出異常不一致
2、比較指令
lcmp指令比較棧頂兩long型數值大小,並將結果(1、0或-1)壓入棧頂。指令的格式如下:
lcmp val1,val2
val1 和 val2 都必須為 long 類型數據,指令執行時, val1 和 val2從操作數棧中出棧,使用一個 int 數值作為比較結果:
- 如果 val1 大於val2,結果為 1;
- 如果 val1 等於 val2,結果為 0;
- 如果 val1小於 val2,結果為-1。
最后比較結果被壓入到操作數棧中。
lcmp字節碼指令的模板定義如下:
def(Bytecodes::_lcmp , ____|____|____|____, ltos, itos, lcmp , _ );
生成函數為TemplateTable::lcmp(), 生成的匯編如下:
0x00007fffe101a6c8: mov (%rsp),%rdx 0x00007fffe101a6cc: add $0x10,%rsp // cmp指令描述如下: // 第1操作數<第2操作數時,ZF=0 // 第1操作數=第2操作數時,ZF=1 // 第1操作數>第2操作數時,ZF=0 0x00007fffe101a6d0: cmp %rax,%rdx 0x00007fffe101a6d3: mov $0xffffffff,%eax // 將-1移到%eax中 // 如果第1操作數小於第2操作數就跳轉到done 0x00007fffe101a6d8: jl 0x00007fffe101a6e0 // cmp指令執行后,執行setne指令就能獲取比較的結果 // 根據eflags中的狀態標志(CF,SF,OF,ZF和PF)將目標操作數設置為0或1 0x00007fffe101a6da: setne %al 0x00007fffe101a6dd: movzbl %al,%eax // -- done --
如上匯編代碼的邏輯非常簡單,這里不再介紹。
關於其它字節碼指令的邏輯也相對簡單,有興趣的可自行研究。
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
第13篇-通過InterpreterCodelet存儲機器指令片段
第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)
第21篇-加載與存儲指令之iload、_fast_iload等(3)
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot VM源碼剖析系列文章!

