第22篇-虛擬機字節碼之運算指令


虛擬機規范中與運算相關的字節碼指令如下表所示。

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型數值大小,並將結果(10-1)壓入棧頂

0x95

fcmpl

比較棧頂兩float型數值大小,並將結果(10-1)壓入棧頂;當其中一個數值為NaN時,將-1壓入棧頂

0x96

fcmpg

比較棧頂兩float型數值大小,並將結果(10-1)壓入棧頂;當其中一個數值為NaN時,將1壓入棧頂

0x97

dcmpl

比較棧頂兩double型數值大小,並將結果(10-1)壓入棧頂;當其中一個數值為NaN時,將-1壓入棧頂

0x98

dcmpg

比較棧頂兩double型數值大小,並將結果(10-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型數值大小,並將結果(10-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 --

如上匯編代碼的邏輯非常簡單,這里不再介紹。

關於其它字節碼指令的邏輯也相對簡單,有興趣的可自行研究。

推薦閱讀:

第1篇-關於JVM運行時,開篇說的簡單些

第2篇-JVM虛擬機這樣來調用Java主類的main()方法

第3篇-CallStub新棧幀的創建

第4篇-JVM終於開始調用Java主類的main()方法啦

第5篇-調用Java方法后彈出棧幀及處理返回結果

第6篇-Java方法新棧幀的創建

第7篇-為Java方法創建棧幀

第8篇-dispatch_next()函數分派字節碼

第9篇-字節碼指令的定義

第10篇-初始化模板表

第11篇-認識Stub與StubQueue

第12篇-認識CodeletMark

第13篇-通過InterpreterCodelet存儲機器指令片段

第14篇-生成重要的例程

第15章-解釋器及解釋器生成器

第16章-虛擬機中的匯編器

第17章-x86-64寄存器

第18章-x86指令集之常用指令

第19篇-加載與存儲指令(1)

第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)

第21篇-加載與存儲指令之iload、_fast_iload等(3)

如果有問題可直接評論留言或加作者微信mazhimazh

關注公眾號,有HotSpot VM源碼剖析系列文章!

  

 

  

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM