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