zoukankan      html  css  js  c++  java
  • 第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型数值大小,并将结果(10-1)压入栈顶

    0x95

    fcmpl

    比较栈顶两float型数值大小,并将结果(10-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶

    0x96

    fcmpg

    比较栈顶两float型数值大小,并将结果(10-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶

    0x97

    dcmpl

    比较栈顶两double型数值大小,并将结果(10-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶

    0x98

    dcmpg

    比较栈顶两double型数值大小,并将结果(10-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型数值大小,并将结果(10-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 --
    

    如上汇编代码的逻辑非常简单,这里不再介绍。

    关于其它字节码指令的逻辑也相对简单,有兴趣的可自行研究。

    推荐阅读:

    第1篇-关于JVM运行时,开篇说的简单些

    第2篇-JVM虚拟机这样来调用Java主类的main()方法

    第3篇-CallStub新栈帧的创建

    第4篇-JVM终于开始调用Java主类的main()方法啦

    第5篇-调用Java方法后弹出栈帧及处理返回结果

    第6篇-Java方法新栈帧的创建

    第7篇-为Java方法创建栈帧

    第8篇-dispatch_next()函数分派字节码

    第9篇-字节码指令的定义

    第10篇-初始化模板表

    第11篇-认识Stub与StubQueue

    第12篇-认识CodeletMark

    第13篇-通过InterpreterCodelet存储机器指令片段

    第14篇-生成重要的例程

    第15章-解释器及解释器生成器

    第16章-虚拟机中的汇编器

    第17章-x86-64寄存器

    第18章-x86指令集之常用指令

    第19篇-加载与存储指令(1)

    第20篇-加载与存储指令之ldc与_fast_aldc指令(2)

    第21篇-加载与存储指令之iload、_fast_iload等(3)

    如果有问题可直接评论留言或加作者微信mazhimazh

    关注公众号,有HotSpot VM源码剖析系列文章!

      

      

     

  • 相关阅读:
    20150324--Mysql索引优化-02
    20150324--Mysql索引优化-01
    20150323--memcache-02
    20150323--memcache-01
    轮播效果/cursor
    事件监听和事件概念
    BOM与DOM操作
    for循环语句/命名函数
    数组/控制语句
    数据类型转换/正则表达式
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/15261028.html
Copyright © 2011-2022 走看看