控制转移相关的字节码指令如下表所示。
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 |
跳转至指定16位offset位置,并将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 |
跳转至指定32位offset位置,并将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篇-虚拟机字节码指令之操作数栈管理指令