Java虛擬機規范中定義的類型轉換相關的字節碼指令如下表所示。
0x85 |
i2l |
將棧頂int型數值強制轉換成long型數值並將結果壓入棧頂 |
0x86 |
i2f |
將棧頂int型數值強制轉換成float型數值並將結果壓入棧頂 |
0x87 |
i2d |
將棧頂int型數值強制轉換成double型數值並將結果壓入棧頂 |
0x88 |
l2i |
將棧頂long型數值強制轉換成int型數值並將結果壓入棧頂 |
0x89 |
l2f |
將棧頂long型數值強制轉換成float型數值並將結果壓入棧頂 |
0x8a |
l2d |
將棧頂long型數值強制轉換成double型數值並將結果壓入棧頂 |
0x8b |
f2i |
將棧頂float型數值強制轉換成int型數值並將結果壓入棧頂 |
0x8c |
f2l |
將棧頂float型數值強制轉換成long型數值並將結果壓入棧頂 |
0x8d |
f2d |
將棧頂float型數值強制轉換成double型數值並將結果壓入棧頂 |
0x8e |
d2i |
將棧頂double型數值強制轉換成int型數值並將結果壓入棧頂 |
0x8f |
d2l |
將棧頂double型數值強制轉換成long型數值並將結果壓入棧頂 |
0x90 |
d2f |
將棧頂double型數值強制轉換成float型數值並將結果壓入棧頂 |
0x91 |
i2b |
將棧頂int型數值強制轉換成byte型數值並將結果壓入棧頂 |
0x92 |
i2c |
將棧頂int型數值強制轉換成char型數值並將結果壓入棧頂 |
0x93 |
i2s |
將棧頂int型數值強制轉換成short型數值並將結果壓入棧頂 |
上表字節碼指令的模板定義如下:
def(Bytecodes::_i2l , ____|____|____|____, itos, ltos, convert , _ ); def(Bytecodes::_i2f , ____|____|____|____, itos, ftos, convert , _ ); def(Bytecodes::_i2d , ____|____|____|____, itos, dtos, convert , _ ); def(Bytecodes::_l2i , ____|____|____|____, ltos, itos, convert , _ ); def(Bytecodes::_l2f , ____|____|____|____, ltos, ftos, convert , _ ); def(Bytecodes::_l2d , ____|____|____|____, ltos, dtos, convert , _ ); def(Bytecodes::_f2i , ____|____|____|____, ftos, itos, convert , _ ); def(Bytecodes::_f2l , ____|____|____|____, ftos, ltos, convert , _ ); def(Bytecodes::_f2d , ____|____|____|____, ftos, dtos, convert , _ ); def(Bytecodes::_d2i , ____|____|____|____, dtos, itos, convert , _ ); def(Bytecodes::_d2l , ____|____|____|____, dtos, ltos, convert , _ ); def(Bytecodes::_d2f , ____|____|____|____, dtos, ftos, convert , _ ); def(Bytecodes::_i2b , ____|____|____|____, itos, itos, convert , _ ); def(Bytecodes::_i2c , ____|____|____|____, itos, itos, convert , _ ); def(Bytecodes::_i2s , ____|____|____|____, itos, itos, convert , _ );
相關字節碼轉換指令的生成函數為TemplateTable::convert(),此函數的實現如下:
void TemplateTable::convert() { static const int64_t is_nan = 0x8000000000000000L; // Conversion switch (bytecode()) { case Bytecodes::_i2l: __ movslq(rax, rax); break; case Bytecodes::_i2f: __ cvtsi2ssl(xmm0, rax); break; case Bytecodes::_i2d: __ cvtsi2sdl(xmm0, rax); break; case Bytecodes::_i2b: __ movsbl(rax, rax); break; case Bytecodes::_i2c: __ movzwl(rax, rax); break; case Bytecodes::_i2s: __ movswl(rax, rax); break; case Bytecodes::_l2i: __ movl(rax, rax); break; case Bytecodes::_l2f: __ cvtsi2ssq(xmm0, rax); break; case Bytecodes::_l2d: __ cvtsi2sdq(xmm0, rax); break; case Bytecodes::_f2i: { Label L; __ cvttss2sil(rax, xmm0); __ cmpl(rax, 0x80000000); // NaN or overflow/underflow? __ jcc(Assembler::notEqual, L); __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2i), 1); __ bind(L); } break; case Bytecodes::_f2l: { Label L; __ cvttss2siq(rax, xmm0); // NaN or overflow/underflow? __ cmp64(rax, ExternalAddress((address) &is_nan)); __ jcc(Assembler::notEqual, L); __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2l), 1); __ bind(L); } break; case Bytecodes::_f2d: __ cvtss2sd(xmm0, xmm0); break; case Bytecodes::_d2i: { Label L; __ cvttsd2sil(rax, xmm0); __ cmpl(rax, 0x80000000); // NaN or overflow/underflow? __ jcc(Assembler::notEqual, L); __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2i), 1); __ bind(L); } break; case Bytecodes::_d2l: { Label L; __ cvttsd2siq(rax, xmm0); // NaN or overflow/underflow? __ cmp64(rax, ExternalAddress((address) &is_nan)); __ jcc(Assembler::notEqual, L); __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2l), 1); __ bind(L); } break; case Bytecodes::_d2f: __ cvtsd2ss(xmm0, xmm0); break; default: ShouldNotReachHere(); } }
如_i2l指令將棧頂int型數值強制轉換成long型數值並將結果壓入棧頂,其對應的匯編代碼如下:
movslq %eax,%rax // 將一個雙字擴展后送到一個四字中
對於浮點數float或long轉int或long類型相對復雜,下面看一個float轉int類型的f2i指令。
// 把標量單精度數轉換為占用雙字的標量整數 0x00007fffe1019189: vcvttss2si %xmm0,%eax // 和0x80000000進行比較,如果不相等,則跳轉到L 0x00007fffe101918d: cmp $0x80000000,%eax 0x00007fffe1019193: jne 0x00007fffe10191bc // 如果棧頂指針已經按16字節對齊,則可直接調用調用SharedRuntime::f2i()函數,否則 // 將棧頂指令按16字節對齊后再調用 0x00007fffe1019199: test $0xf,%esp 0x00007fffe101919f: je 0x00007fffe10191b7 0x00007fffe10191a5: sub $0x8,%rsp // 調用SharedRuntime::f2i()函數 0x00007fffe10191a9: callq 0x00007ffff6a0f946 0x00007fffe10191ae: add $0x8,%rsp 0x00007fffe10191b2: jmpq 0x00007fffe10191bc // 調用SharedRuntime::f2i()函數 0x00007fffe10191b7: callq 0x00007ffff6a0f946 ---- L ----
生成的匯編指令vcvttss2si的意思為把標量單精度數轉換為占用雙字的標量整數,名稱的由來解讀如下:
cvt:convert,轉換;
t:truncation,截斷;
ss:scalar single,標量單精度數;
2:to;
si:scalar integer,標量整數。
調用的SharedRuntime::f2i()函數的實現如下:
JRT_LEAF(jint, SharedRuntime::f2i(jfloat x)) if (g_isnan(x)) // 如果為非數字值,直接返回0 return 0; if (x >= (jfloat) max_jint) return max_jint; if (x <= (jfloat) min_jint) return min_jint; return (jint) x; JRT_END
C++函數調用時,需要一個參數x,在GNU / Linux上遵循System V AMD64 ABI的調用約定。寄存器RDI,RSI,RDX,RCX,R8和R9是用於整數和存儲器地址的參數和XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6和XMM7用於浮點參數,所以將用xmm0做為第1個參數,這個參數恰好是棧頂用來緩存浮點數的寄存器,所以默認不用任何操作。
返回值存儲到%rax中,由於tos_out為itos,%rax寄存器用來做棧頂緩存,所以也不需要做額外的操作。
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
第13篇-通過InterpreterCodelet存儲機器指令片段
第20篇-加載與存儲指令之ldc與_fast_aldc指令(2)
第21篇-加載與存儲指令之iload、_fast_iload等(3)
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot VM源碼剖析系列文章!