第27篇-虛擬機位元組碼指令之控制轉移指令
- 2021 年 9 月 27 日
- 筆記
控制轉移相關的位元組碼指令如下表所示。
0x99 |
ifeq |
當棧頂int型數值等於0時跳轉 |
0x9a |
ifne |
當棧頂int型數值不等於0時跳轉 |
0x9b |
iflt |
當棧頂int型數值小於0時跳轉 |
0x9c |
ifge |
當棧頂int型數值大於等於0時跳轉 |
0x9d |
ifgt |
當棧頂int型數值大於0時跳轉 |
0x9e |
ifle |
當棧頂int型數值小於等於0時跳轉 |
0x9f |
if_icmpeq |
比較棧頂兩int型數值大小,當結果等於0時跳轉 |
0xa0 |
if_icmpne |
比較棧頂兩int型數值大小,當結果不等於0時跳轉 |
0xa1 |
if_icmplt |
比較棧頂兩int型數值大小,當結果小於0時跳轉 |
0xa2 |
if_icmpge |
比較棧頂兩int型數值大小,當結果大於等於0時跳轉 |
0xa3 |
if_icmpgt |
比較棧頂兩int型數值大小,當結果大於0時跳轉 |
0xa4 |
if_icmple |
比較棧頂兩int型數值大小,當結果小於等於0時跳轉 |
0xa5 |
if_acmpeq |
比較棧頂兩引用型數值,當結果相等時跳轉 |
0xa6 |
if_acmpne |
比較棧頂兩引用型數值,當結果不相等時跳轉 |
0xa7 |
goto |
無條件跳轉 |
0xa8 |
jsr |
跳轉至指定16位offset位置,並將jsr下一條指令地址壓入棧頂 |
0xa9 |
ret |
返回至本地變數指令的index的指令位置(一般與jsr或jsr_w聯合使用) |
0xaa |
tableswitch |
用於switch條件跳轉,case值連續(可變長度指令) |
0xab |
lookupswitch |
用於switch條件跳轉,case值不連續(可變長度指令) |
0xac |
ireturn |
從當前方法返回int |
0xad |
lreturn |
從當前方法返回long |
0xae |
freturn |
從當前方法返回float |
0xaf |
dreturn |
從當前方法返回double |
0xb0 |
areturn |
從當前方法返回對象引用 |
0xb1 |
return |
從當前方法返回void |
0xc6 |
ifnull |
為null時跳轉 |
0xc7 |
ifnonnull |
不為null時跳轉 |
0xc8 |
goto_w |
無條件跳轉(寬索引) |
0xc9 |
jsr_w |
跳轉至指定32位offset位置,並將jsr_w下一條指令地址壓入棧頂 |
模板定義如下:
def(Bytecodes::_ifeq , ubcp|____|clvm|____, itos, vtos, if_0cmp , equal ); def(Bytecodes::_ifne , ubcp|____|clvm|____, itos, vtos, if_0cmp , not_equal ); def(Bytecodes::_iflt , ubcp|____|clvm|____, itos, vtos, if_0cmp , less ); def(Bytecodes::_ifge , ubcp|____|clvm|____, itos, vtos, if_0cmp , greater_equal); def(Bytecodes::_ifgt , ubcp|____|clvm|____, itos, vtos, if_0cmp , greater ); def(Bytecodes::_ifle , ubcp|____|clvm|____, itos, vtos, if_0cmp , less_equal ); def(Bytecodes::_if_icmpeq , ubcp|____|clvm|____, itos, vtos, if_icmp , equal ); def(Bytecodes::_if_icmpne , ubcp|____|clvm|____, itos, vtos, if_icmp , not_equal ); def(Bytecodes::_if_icmplt , ubcp|____|clvm|____, itos, vtos, if_icmp , less ); def(Bytecodes::_if_icmpge , ubcp|____|clvm|____, itos, vtos, if_icmp , greater_equal); def(Bytecodes::_if_icmpgt , ubcp|____|clvm|____, itos, vtos, if_icmp , greater ); def(Bytecodes::_if_icmple , ubcp|____|clvm|____, itos, vtos, if_icmp , less_equal ); def(Bytecodes::_if_acmpeq , ubcp|____|clvm|____, atos, vtos, if_acmp , equal ); def(Bytecodes::_if_acmpne , ubcp|____|clvm|____, atos, vtos, if_acmp , not_equal ); def(Bytecodes::_goto , ubcp|disp|clvm|____, vtos, vtos, _goto , _ ); def(Bytecodes::_jsr , ubcp|disp|____|____, vtos, vtos, jsr , _ ); // result is not an oop, so do not transition to atos def(Bytecodes::_ret , ubcp|disp|____|____, vtos, vtos, ret , _ ); def(Bytecodes::_tableswitch , ubcp|disp|____|____, itos, vtos, tableswitch , _ ); def(Bytecodes::_lookupswitch , ubcp|disp|____|____, itos, itos, lookupswitch , _ ); def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos ); def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos ); def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos ); def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos ); def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos ); def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos ); def(Bytecodes::_ifnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , equal ); def(Bytecodes::_ifnonnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , not_equal ); def(Bytecodes::_goto_w , ubcp|____|clvm|____, vtos, vtos, goto_w , _ ); def(Bytecodes::_jsr_w , ubcp|____|____|____, vtos, vtos, jsr_w , _ );
下面介紹幾個典型指令的彙編實現。
1、goto指令
goto位元組碼指令的生成函數為TemplateTable::_goto(),生成的彙編程式碼如下:(在生成程式碼時添加命令-Xint -XX:-ProfileInterpreter,這樣可排除生成一些不必要的指令)
// Method*保存到%rcx中 0x00007fffe1019df0: mov -0x18(%rbp),%rcx // 將goto後的index(2個位元組)存儲到%edx中 0x00007fffe1019df4: movswl 0x1(%r13),%edx 0x00007fffe1019df9: bswap %edx // 算術右移指令 0x00007fffe1019dfb: sar $0x10,%edx // 將一個雙字元號擴展後送到一個四字地址中 0x00007fffe1019dfe: movslq %edx,%rdx // 將當前位元組碼地址加上rdx保存的偏移量,計算跳轉的目標地址 0x00007fffe1019e01: add %rdx,%r13 // %r13已經變成目標跳轉地址,這裡是載入跳轉地址的第一個位元組碼到rbx中 0x00007fffe1019e04: movzbl 0x0(%r13),%ebx // continue with the bytecode @ target // eax: return bci for jsr's, unused otherwise // ebx: target bytecode // r13: target bcp // 開始執行跳轉地址處的位元組碼,其中的常量地址為 // TemplateInterpreter::_active_table的、棧頂快取狀態為vtos的首地址 0x00007fffe1019e09: movabs $0x7ffff73ba4a0,%r10 0x00007fffe1019e13: jmpq *(%r10,%rbx,8)
其實goto指令實際上生成的彙編程式碼要比上面的程式碼多的多,因為goto指令是一個分支指令,其中會做一些性能統計以輔助進行編譯優化,而且goto如果是在循環中的話,還可能會涉及到棧上替換的技術,所以後面我們在介紹到對應的技術點時再詳細介紹goto指令的其它一些彙編邏輯。
2、ifeq、ifne等指令
現在ifeq、ifne等指令的生成函數為TemplateTable::if_0cmp()。ifeq位元組碼指令表示棧頂值與零值比較,當且僅當棧頂的int類型的值為0時,比較結果為真。對應的彙編程式碼如下:
0x00007fffe10196c7: test %eax,%eax // 當棧頂快取%eax不為0時,直接跳到not_taken 0x00007fffe10196c9: jne 0x00007fffe10196f6 // 調用TemplateTable::branch(false,false)函數生成的彙編程式碼 // 將當前棧幀中保存的Method* 拷貝到rcx中 0x00007fffe10196cf: mov -0x18(%rbp),%rcx // 將當前位元組碼位置往後偏移1位元組處開始的2位元組數據讀取到edx中 0x00007fffe10196d3: movswl 0x1(%r13),%edx // 將%edx中的值位元組次序變反 0x00007fffe10196d8: bswap %edx // 將edx中的值右移16位,上述兩步就是為了計算跳轉分支的偏移量 0x00007fffe10196da: sar $0x10,%edx // 將edx中的數據從2位元組擴展成4位元組 0x00007fffe10196dd: movslq %edx,%rdx // 將當前位元組碼地址加上rdx保存的偏移量,計算跳轉的目標地址 0x00007fffe10196e0: add %rdx,%r13 // r13已經變成目標跳轉地址,這裡是載入跳轉地址的第一個位元組碼到ebx中 0x00007fffe10196e3: movzbl 0x0(%r13),%ebx // 開始執行跳轉地址處的位元組碼,其中的常量地址為 // TemplateInterpreter::_active_table的、棧頂快取狀態為vtos的首地址 0x00007fffe10196e8: movabs $0x7ffff73ba4a0,%r10 0x00007fffe10196f2: jmpq *(%r10,%rbx,8) // -- not_taken --
類似的指令實現邏輯也高度類似,大家有興趣可自行研究。
3、lookupswitch、tableswitch等指令
lookupswitch指令根據鍵值在跳轉表中尋找配對的分支並跳轉,具體的格式如下圖所示。
這是一條變長指令並且要求所有的操作數都4位元組對齊,所以緊跟在lookupswitch指令之後可能會有0到3個位元組作為空白填充,而後面的default、npairs等都用4位元組來表示,從當前方法開始(第一條位元組碼指令)計算的地址,即緊隨空白填充的是一系列32位有符號整數值,包括默認跳轉地址default、匹配坐標的數量npairs以及npairs組匹配坐標。其中npairs的值應當大於或等於0,每一組匹配坐標都包含了一個整數值match以及一個有符號32位偏移量offset。上述所有的32位有符號數值都是通過以下方式計算得到:
(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4
tableswitch指令根據鍵值在跳轉表中尋找配對的分支並跳轉,具體的格式如下圖所示。
這是一條變長指令並且要求所有的操作數都4位元組對齊,所以緊跟在lookupswitch指令之後可能會有0到3個位元組作為空白填充,而後面的default、lowbyte、highbyte等用4位元組來表示,從當前方法開始(第一條位元組碼指令)計算的地址,即緊隨空白填充的是一系列32位有符號整數值,包括默認跳轉地址default、高位值high以及低位值low,在此之後是high-low+1個有符號32位偏移offset。上述所有的32位有符號數值都是通過以下方式計算得到:
(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4
生成函數為TemplateTable::tableswitch(),生成的彙編如下:
// align r13,按照4位元組對齊 0x00007fffe1019fa7: lea 0x4(%r13),%rbx 0x00007fffe1019fab: and $0xfffffffffffffffc,%rbx // load lo & hi 0x00007fffe1019faf: mov 0x4(%rbx),%ecx 0x00007fffe1019fb2: mov 0x8(%rbx),%edx 0x00007fffe1019fb5: bswap %ecx 0x00007fffe1019fb7: bswap %edx // check against lo & hi // %ecx中存儲的是lowbyte 0x00007fffe1019fb9: cmp %ecx,%eax // 如果比低位值還低,則跳轉到default_case 0x00007fffe1019fbb: jl 0x00007fffe1019feb // %edx中存儲的是highbyte 0x00007fffe1019fc1: cmp %edx,%eax // 如果比高位值還高,則跳轉到default_case 0x00007fffe1019fc3: jg 0x00007fffe1019feb // lookup dispatch offset 0x00007fffe1019fc9: sub %ecx,%eax // %rbx中存儲的是對齊後的位元組碼指令地址,%rax中存儲的是棧頂快取值 0x00007fffe1019fcb: mov 0xc(%rbx,%rax,4),%edx // -- continue_execution -- // continue execution 0x00007fffe1019fcf: bswap %edx 0x00007fffe1019fd1: movslq %edx,%rdx 0x00007fffe1019fd4: movzbl 0x0(%r13,%rdx,1),%ebx 0x00007fffe1019fda: add %rdx,%r13 0x00007fffe1019fdd: movabs $0x7ffff73ba4a0,%r10 0x00007fffe1019fe7: jmpq *(%r10,%rbx,8) // -- default_case -- // handle default 0x00007fffe1019feb: mov (%rbx),%edx // 跳轉到continue_execution 0x00007fffe1019fed: jmp 0x00007fffe1019fcf
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
第13篇-通過InterpreterCodelet存儲機器指令片段
第20篇-載入與存儲指令之ldc與_fast_aldc指令(2)
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot VM源碼剖析系列文章!