【JVM源码解析】模板解释器解释执行Java字节码指令(下)

编程入门 行业动态 更新时间:2024-10-12 03:18:08

【JVM源码解析】模板解释器解释执行Java<a href=https://www.elefans.com/category/jswz/34/1769891.html style=字节码指令(下)"/>

【JVM源码解析】模板解释器解释执行Java字节码指令(下)

本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布

第22篇-虚拟机字节码之运算指令

虚拟机规范中与运算相关的字节码指令如下表所示。

0x60iadd将栈顶两int型数值相加并将结果压入栈顶
0x61ladd将栈顶两long型数值相加并将结果压入栈顶
0x62fadd将栈顶两float型数值相加并将结果压入栈顶
0x63dadd将栈顶两double型数值相加并将结果压入栈顶
0x64isub将栈顶两int型数值相减并将结果压入栈顶
0x65lsub将栈顶两long型数值相减并将结果压入栈顶
0x66fsub将栈顶两float型数值相减并将结果压入栈顶
0x67dsub将栈顶两double型数值相减并将结果压入栈顶
0x68imul将栈顶两int型数值相乘并将结果压入栈顶
0x69lmul将栈顶两long型数值相乘并将结果压入栈顶
0x6afmul将栈顶两float型数值相乘并将结果压入栈顶
0x6bdmul将栈顶两double型数值相乘并将结果压入栈顶
0x6cidiv将栈顶两int型数值相除并将结果压入栈顶
0x6dldiv将栈顶两long型数值相除并将结果压入栈顶
0x6efdiv将栈顶两float型数值相除并将结果压入栈顶
0x6fddiv将栈顶两double型数值相除并将结果压入栈顶
0x70irem将栈顶两int型数值作取模运算并将结果压入栈顶
0x71lrem将栈顶两long型数值作取模运算并将结果压入栈顶
0x72frem将栈顶两float型数值作取模运算并将结果压入栈顶
0x73drem将栈顶两double型数值作取模运算并将结果压入栈顶
0x74ineg将栈顶int型数值取负并将结果压入栈顶
0x75lneg将栈顶long型数值取负并将结果压入栈顶
0x76fneg将栈顶float型数值取负并将结果压入栈顶
0x77dneg将栈顶double型数值取负并将结果压入栈顶
0x78ishl将int型数值左移位指定位数并将结果压入栈顶
0x79lshl将long型数值左移位指定位数并将结果压入栈顶
0x7aishr将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7blshr将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7ciushr将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7dlushr将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7eiand将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7fland将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80ior将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81lor将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82ixor将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83lxor将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84iinc将指定int型变量增加指定值(i++、i–、i+=2)
0x94lcmp比较栈顶两long型数值大小,并将结果(1、0或-1)压入栈顶
0x95fcmpl比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96fcmpg比较栈顶两float型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97dcmpl比较栈顶两double型数值大小,并将结果(1、0或-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98dcmpg比较栈顶两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虚拟机规范中定义的类型转换相关的字节码指令如下表所示。

0x85i2l将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86i2f将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87i2d将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0x88l2i将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89l2f将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8al2d将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8bf2i将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8cf2l将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8df2d将栈顶float型数值强制转换成double型数值并将结果压入栈顶
0x8ed2i将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8fd2l将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0x90d2f将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0x91i2b将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92i2c将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93i2s将栈顶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字节码指令(下)

本文发布于:2024-02-06 07:07:56,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1747039.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:字节   指令   源码   模板   JVM

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!