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源碼剖析系列文章!

