字节码指令(下)"/>
【JVM源码解析】模板解释器解释执行Java字节码指令(下)
本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布
第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型数值大小,并将结果(1、0或-1)压入栈顶 |
0x95 | fcmpl | 比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 |
0x96 | fcmpg | 比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶 |
0x97 | dcmpl | 比较栈顶两double型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 |
0x98 | dcmpg | 比较栈顶两double型数值大小,并将结果(1、0或-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型数值大小,并将结果(1、0或-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 --
如上汇编代码的逻辑非常简单,这里不再介绍。
关于其它字节码指令的逻辑也相对简单,有兴趣的可自行研究。
第23篇-虚拟机字节码指令之类型转换
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;// Conversionswitch (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));__ jc
更多推荐
【JVM源码解析】模板解释器解释执行Java字节码指令(下)
发布评论