专注虚拟机与编译器研究

第28篇-虚拟机字节码指令之控制转移指令

控制转移相关的字节码指令如下表所示。 

0x99

ifeq

当栈顶int型数值等于0时跳转

0x9a

ifne

当栈顶int型数值不等于0时跳转

0x9b

iflt

当栈顶int型数值小于0时跳转

0x9c

ifge

当栈顶int型数值大于等于0时跳转

0x9d

ifgt

当栈顶int型数值大于0时跳转

0x9e

ifle

当栈顶int型数值小于等于0时跳转

0x9f

if_icmpeq

比较栈顶两int型数值大小,当结果等于0时跳转

0xa0

if_icmpne

比较栈顶两int型数值大小,当结果不等于0时跳转

0xa1

if_icmplt

比较栈顶两int型数值大小,当结果小于0时跳转

0xa2

if_icmpge

比较栈顶两int型数值大小,当结果大于等于0时跳转

0xa3

if_icmpgt

比较栈顶两int型数值大小,当结果大于0时跳转

0xa4

if_icmple

比较栈顶两int型数值大小,当结果小于等于0时跳转

0xa5

if_acmpeq

比较栈顶两引用型数值,当结果相等时跳转

0xa6

if_acmpne

比较栈顶两引用型数值,当结果不相等时跳转

0xa7

goto

无条件跳转

0xa8

jsr

跳转至指定16offset位置,并将jsr下一条指令地址压入栈顶

0xa9

ret

返回至本地变量指令的index的指令位置(一般与jsr或jsr_w联合使用)

0xaa

tableswitch

用于switch条件跳转,case值连续(可变长度指令)

0xab

lookupswitch

用于switch条件跳转,case值不连续(可变长度指令)

0xac

ireturn

从当前方法返回int

0xad

lreturn

从当前方法返回long

0xae

freturn

从当前方法返回float

0xaf

dreturn

从当前方法返回double

0xb0

areturn

从当前方法返回对象引用

0xb1

return

从当前方法返回void

0xc6

ifnull

null时跳转

0xc7

ifnonnull

不为null时跳转

0xc8

goto_w

无条件跳转(宽索引)

0xc9

jsr_w

跳转至指定32offset位置,并将jsr_w下一条指令地址压入栈顶

 

模板定义如下:

def(Bytecodes::_ifeq                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , equal        );
def(Bytecodes::_ifne                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , not_equal    );
def(Bytecodes::_iflt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less         );
def(Bytecodes::_ifge                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater_equal);
def(Bytecodes::_ifgt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater      );
def(Bytecodes::_ifle                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less_equal   );
def(Bytecodes::_if_icmpeq           , ubcp|____|clvm|____, itos, vtos, if_icmp             , equal        );
def(Bytecodes::_if_icmpne           , ubcp|____|clvm|____, itos, vtos, if_icmp             , not_equal    );
def(Bytecodes::_if_icmplt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less         );
def(Bytecodes::_if_icmpge           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater_equal);
def(Bytecodes::_if_icmpgt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater      );
def(Bytecodes::_if_icmple           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less_equal   );
def(Bytecodes::_if_acmpeq           , ubcp|____|clvm|____, atos, vtos, if_acmp             , equal        );
def(Bytecodes::_if_acmpne           , ubcp|____|clvm|____, atos, vtos, if_acmp             , not_equal    );
def(Bytecodes::_goto                , ubcp|disp|clvm|____, vtos, vtos, _goto               ,  _           );
def(Bytecodes::_jsr                 , ubcp|disp|____|____, vtos, vtos, jsr                 ,  _           ); // result is not an oop, so do not transition to atos
def(Bytecodes::_ret                 , ubcp|disp|____|____, vtos, vtos, ret                 ,  _           );
def(Bytecodes::_tableswitch         , ubcp|disp|____|____, itos, vtos, tableswitch         ,  _           );
def(Bytecodes::_lookupswitch        , ubcp|disp|____|____, itos, itos, lookupswitch        ,  _           );
def(Bytecodes::_ireturn             , ____|disp|clvm|____, itos, itos, _return             , itos         );
def(Bytecodes::_lreturn             , ____|disp|clvm|____, ltos, ltos, _return             , ltos         );
def(Bytecodes::_freturn             , ____|disp|clvm|____, ftos, ftos, _return             , ftos         );
def(Bytecodes::_dreturn             , ____|disp|clvm|____, dtos, dtos, _return             , dtos         );
def(Bytecodes::_areturn             , ____|disp|clvm|____, atos, atos, _return             , atos         );
def(Bytecodes::_return              , ____|disp|clvm|____, vtos, vtos, _return             , vtos         );

def(Bytecodes::_ifnull              , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , equal        );
def(Bytecodes::_ifnonnull           , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , not_equal    );
def(Bytecodes::_goto_w              , ubcp|____|clvm|____, vtos, vtos, goto_w              ,  _           );
def(Bytecodes::_jsr_w               , ubcp|____|____|____, vtos, vtos, jsr_w               ,  _           );

下面介绍几个典型指令的汇编实现。

1、goto指令

goto字节码指令的生成函数为TemplateTable::_goto(),生成的汇编代码如下:(在生成代码时添加命令-Xint   -XX:-ProfileInterpreter,这样可排除生成一些不必要的指令)

// Method*保存到%rcx中
0x00007fffe1019df0: mov    -0x18(%rbp),%rcx
// 将goto后的index(2个字节)存储到%edx中
0x00007fffe1019df4: movswl 0x1(%r13),%edx
0x00007fffe1019df9: bswap  %edx
// 算术右移指令
0x00007fffe1019dfb: sar    $0x10,%edx
// 将一个双字符号扩展后送到一个四字地址中
0x00007fffe1019dfe: movslq %edx,%rdx
// 将当前字节码地址加上rdx保存的偏移量,计算跳转的目标地址
0x00007fffe1019e01: add    %rdx,%r13
// %r13已经变成目标跳转地址,这里是加载跳转地址的第一个字节码到rbx中
0x00007fffe1019e04: movzbl 0x0(%r13),%ebx

// continue with the bytecode @ target
// eax: return bci for jsr's, unused otherwise
// ebx: target bytecode
// r13: target bcp
// 开始执行跳转地址处的字节码,其中的常量地址为
// TemplateInterpreter::_active_table的、栈顶缓存状态为vtos的首地址
0x00007fffe1019e09: movabs $0x7ffff73ba4a0,%r10
0x00007fffe1019e13: jmpq   *(%r10,%rbx,8)

其实goto指令实际上生成的汇编代码要比上面的代码多的多,因为goto指令是一个分支指令,其中会做一些性能统计以辅助进行编译优化,而且goto如果是在循环中的话,还可能会涉及到栈上替换的技术,所以后面我们在介绍到对应的技术点时再详细介绍goto指令的其它一些汇编逻辑。 

2、ifeq、ifne等指令  

现在ifeq、ifne等指令的生成函数为TemplateTable::if_0cmp()。ifeq字节码指令表示栈顶值与零值比较,当且仅当栈顶的int类型的值为0时,比较结果为真。对应的汇编代码如下: 

0x00007fffe10196c7: test   %eax,%eax
// 当栈顶缓存%eax不为0时,直接跳到not_taken
0x00007fffe10196c9: jne    0x00007fffe10196f6

// 调用TemplateTable::branch(false,false)函数生成的汇编代码

// 将当前栈帧中保存的Method* 拷贝到rcx中
0x00007fffe10196cf: mov    -0x18(%rbp),%rcx
// 将当前字节码位置往后偏移1字节处开始的2字节数据读取到edx中
0x00007fffe10196d3: movswl 0x1(%r13),%edx
// 将%edx中的值字节次序变反
0x00007fffe10196d8: bswap  %edx
// 将edx中的值右移16位,上述两步就是为了计算跳转分支的偏移量
0x00007fffe10196da: sar    $0x10,%edx
// 将edx中的数据从2字节扩展成4字节
0x00007fffe10196dd: movslq %edx,%rdx
// 将当前字节码地址加上rdx保存的偏移量,计算跳转的目标地址
0x00007fffe10196e0: add    %rdx,%r13
// r13已经变成目标跳转地址,这里是加载跳转地址的第一个字节码到ebx中
0x00007fffe10196e3: movzbl 0x0(%r13),%ebx

// 开始执行跳转地址处的字节码,其中的常量地址为
// TemplateInterpreter::_active_table的、栈顶缓存状态为vtos的首地址
0x00007fffe10196e8: movabs $0x7ffff73ba4a0,%r10
0x00007fffe10196f2: jmpq   *(%r10,%rbx,8)

// -- not_taken -- 

类似的指令实现逻辑也高度类似,大家有兴趣可自行研究。 

3、lookupswitch、tableswitch等指令 

lookupswitch指令根据键值在跳转表中寻找配对的分支并跳转,具体的格式如下图所示。

 

这是一条变长指令并且要求所有的操作数都4字节对齐,所以紧跟在lookupswitch指令之后可能会有0到3个字节作为空白填充,而后面的default、npairs等都用4字节来表示,从当前方法开始(第一条字节码指令)计算的地址,即紧随空白填充的是一系列32位有符号整数值,包括默认跳转地址default、匹配坐标的数量npairs以及npairs组匹配坐标。其中npairs的值应当大于或等于0,每一组匹配坐标都包含了一个整数值match以及一个有符号32位偏移量offset。上述所有的32位有符号数值都是通过以下方式计算得到:

(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4

tableswitch指令根据键值在跳转表中寻找配对的分支并跳转,具体的格式如下图所示。

这是一条变长指令并且要求所有的操作数都4字节对齐,所以紧跟在lookupswitch指令之后可能会有0到3个字节作为空白填充,而后面的default、lowbyte、highbyte等用4字节来表示,从当前方法开始(第一条字节码指令)计算的地址,即紧随空白填充的是一系列32位有符号整数值,包括默认跳转地址default、高位值high以及低位值low,在此之后是high-low+1个有符号32位偏移offset。上述所有的32位有符号数值都是通过以下方式计算得到:

(byte1<<24)|(byte2<<24)|(byte3<<24)|byte4

生成函数为TemplateTable::tableswitch(),生成的汇编如下:

// align r13,按照4字节对齐
0x00007fffe1019fa7: lea    0x4(%r13),%rbx
0x00007fffe1019fab: and    $0xfffffffffffffffc,%rbx
// load lo & hi
0x00007fffe1019faf: mov    0x4(%rbx),%ecx
0x00007fffe1019fb2: mov    0x8(%rbx),%edx
0x00007fffe1019fb5: bswap  %ecx
0x00007fffe1019fb7: bswap  %edx

// check against lo & hi
// %ecx中存储的是lowbyte
0x00007fffe1019fb9: cmp    %ecx,%eax
// 如果比低位值还低,则跳转到default_case
0x00007fffe1019fbb: jl     0x00007fffe1019feb 
// %edx中存储的是highbyte
0x00007fffe1019fc1: cmp    %edx,%eax
// 如果比高位值还高,则跳转到default_case
0x00007fffe1019fc3: jg     0x00007fffe1019feb

// lookup dispatch offset
0x00007fffe1019fc9: sub    %ecx,%eax
// %rbx中存储的是对齐后的字节码指令地址,%rax中存储的是栈顶缓存值
0x00007fffe1019fcb: mov    0xc(%rbx,%rax,4),%edx
// -- continue_execution --
// continue execution
0x00007fffe1019fcf: bswap  %edx
0x00007fffe1019fd1: movslq %edx,%rdx
0x00007fffe1019fd4: movzbl 0x0(%r13,%rdx,1),%ebx
0x00007fffe1019fda: add    %rdx,%r13

0x00007fffe1019fdd: movabs $0x7ffff73ba4a0,%r10
0x00007fffe1019fe7: jmpq   *(%r10,%rbx,8)

// -- default_case --
// handle default
0x00007fffe1019feb: mov (%rbx),%edx 
// 跳转到continue_execution
0x00007fffe1019fed: jmp 0x00007fffe1019fcf

推荐阅读:

第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)

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

第23篇-虚拟机字节码指令之类型转换

第24篇-虚拟机对象操作指令之getstatic

第25篇-虚拟机对象操作指令之getfield

第26篇-虚拟机对象操作指令之putstatic

第27篇-虚拟机字节码指令之操作数栈管理指令

 

 

  

  

  

 

 

 

 

 

 

 

 

  

  

 

posted on 2021-09-27 10:40  鸠摩(马智)  阅读(695)  评论(0编辑  收藏  举报

导航