乐趣区

关于java: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;

  // Conversion
  switch (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));
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::f2l), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_f2d:
    __ cvtss2sd(xmm0, xmm0);
    break;
  case Bytecodes::_d2i:
  {
    Label L;
    __ cvttsd2sil(rax, xmm0);
    __ cmpl(rax, 0x80000000); // NaN or overflow/underflow?
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2i), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_d2l:
  {
    Label L;
    __ cvttsd2siq(rax, xmm0);
    // NaN or overflow/underflow?
    __ cmp64(rax, ExternalAddress((address) &is_nan));
    __ jcc(Assembler::notEqual, L);
    __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::d2l), 1);
    __ bind(L);
  }
    break;
  case Bytecodes::_d2f:
    __ cvtsd2ss(xmm0, xmm0);
    break;
  default:
    ShouldNotReachHere();}
}

如_i2l 指令将栈顶 int 型数值强制转换成 long 型数值并将后果压入栈顶,其对应的汇编代码如下:

movslq %eax,%rax  // 将一个双字扩大后送到一个四字中

对于浮点数 float 或 long 转 int 或 long 类型绝对简单,上面看一个 float 转 int 类型的 f2i 指令。

// 把标量单精度数转换为占用双字的标量整数
0x00007fffe1019189: vcvttss2si %xmm0,%eax
// 和 0x80000000 进行比拟,如果不相等,则跳转到 L
0x00007fffe101918d: cmp    $0x80000000,%eax
0x00007fffe1019193: jne    0x00007fffe10191bc

// 如果栈顶指针曾经按 16 字节对齐,则可间接调用调用 SharedRuntime::f2i()函数,否则
// 将栈顶指令按 16 字节对齐后再调用

0x00007fffe1019199: test   $0xf,%esp
0x00007fffe101919f: je     0x00007fffe10191b7
0x00007fffe10191a5: sub    $0x8,%rsp
// 调用 SharedRuntime::f2i()函数
0x00007fffe10191a9: callq  0x00007ffff6a0f946
0x00007fffe10191ae: add    $0x8,%rsp
0x00007fffe10191b2: jmpq   0x00007fffe10191bc
// 调用 SharedRuntime::f2i()函数
0x00007fffe10191b7: callq  0x00007ffff6a0f946 

---- L ----

生成的汇编指令 vcvttss2si 的意思为把标量单精度数转换为占用双字的标量整数,名称的由来解读如下:

cvt:convert,转换;

t:truncation,截断;

ss:scalar single,标量单精度数;

2:to;

si:scalar integer,标量整数。

调用的 SharedRuntime::f2i()函数的实现如下:

JRT_LEAF(jint, SharedRuntime::f2i(jfloat  x))
  if (g_isnan(x))  // 如果为非数字值,间接返回 0
    return 0;
  if (x >= (jfloat) max_jint)
    return max_jint;
  if (x <= (jfloat) min_jint)
    return min_jint;
  return (jint) x;
JRT_END

C++ 函数调用时,须要一个参数 x,在 GNU / Linux 上遵循 System V AMD64 ABI 的调用约定。寄存器 RDI,RSI,RDX,RCX,R8 和 R9 是用于整数和存储器地址的参数和 XMM0,XMM1,XMM2,XMM3,XMM4,XMM5,XMM6 和 XMM7 用于浮点参数,所以将用 xmm0 做为第 1 个参数,这个参数恰好是栈顶用来缓存浮点数的寄存器,所以默认不必任何操作。

返回值存储到 %rax 中,因为 tos_out 为 itos,%rax 寄存器用来做栈顶缓存,所以也不须要做额定的操作。

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

Java 虚拟机标准中定义的对象操作相干的字节码指令如下表所示。

0xb2 getstatic 获取指定类的动态域,并将其值压入栈顶
0xb3 putstatic 为指定的类的动态域赋值
0xb4 getfield 获取指定类的实例域,并将其值压入栈顶
0xb5 putfield 为指定的类的实例域赋值
0xbb new 创立一个对象,并将其援用值压入栈顶
0xbc newarray 创立一个指定原始类型(如 int,、float,、char 等)的数组,并将其援用值压入栈顶
0xbd anewarray 创立一个援用型(如类、接口或数组)的数组,并将其援用值压入栈顶
0xbe arraylength 取得数组的长度值并压入栈顶
0xc0 checkcast 测验类型转换,测验未通过将抛出 ClassCastException
0xc1 instanceof 测验对象是否是指定的类的实例,如果是将 1 压入栈顶,否则将 0 压入栈顶
0xc5 multianewarray 创立指定类型和指定维度的多维数组(执行该指令时,操作栈中必须蕴含各维度的长度值),并将其援用值压入栈顶

字节码指令的模板定义如下:

def(Bytecodes::_getstatic           , ubcp|____|clvm|____, vtos, vtos, getstatic           , f1_byte);
def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte);
def(Bytecodes::_getfield            , ubcp|____|clvm|____, vtos, vtos, getfield            , f1_byte);
def(Bytecodes::_putfield            , ubcp|____|clvm|____, vtos, vtos, putfield            , f2_byte);

def(Bytecodes::_new                 , ubcp|____|clvm|____, vtos, atos, _new                ,  _);
def(Bytecodes::_newarray            , ubcp|____|clvm|____, itos, atos, newarray            ,  _);
def(Bytecodes::_anewarray           , ubcp|____|clvm|____, itos, atos, anewarray           ,  _);
def(Bytecodes::_multianewarray      , ubcp|____|clvm|____, vtos, atos, multianewarray      ,  _);

def(Bytecodes::_arraylength         , ____|____|____|____, atos, itos, arraylength         ,  _);

def(Bytecodes::_checkcast           , ubcp|____|clvm|____, atos, atos, checkcast           ,  _);

def(Bytecodes::_instanceof          , ubcp|____|clvm|____, atos, itos, instanceof          ,  _);

new 字节码指令的生成函数为 TemplateTable::_new(),这在《深刻分析 Java 虚拟机:源码分析与实例详解(根底卷)》的第 9 章类对象创立时具体介绍过,这里不再介绍。

getstatic 字节码指令获取指定类的动态域,并将其值压入栈顶。格局如下:

getstatic indexbyte1 indexbyte2

无符号数 indexbyte1 和 indexbyte2 构建为(indexbyte1<<8)|indexbyte2,这个值指明了一个以后类的运行时常量池索引值,指向的运行时常量池项为一个字段的符号援用。

getstatic 字节码指令的生成函数为 TemplateTable::getstatic(),还有个相似的 getfield 指令,这些生成函数如下:

void TemplateTable::getfield(int byte_no) {getfield_or_static(byte_no, false); // getfield 的 byte_no 值为 1
}

void TemplateTable::getstatic(int byte_no) {getfield_or_static(byte_no, true); // getstatic 的 byte_no 的值为 1
}

最终都会调用 getfield_or_static()函数生成机器指令片段。此函数生成的机器指令片段对应的汇编代码如下:

// 获取 ConstantPoolCache 中 ConstantPoolCacheEntry 的 index
0x00007fffe101fd10: movzwl 0x1(%r13),%edx
// 从栈中获取 ConstantPoolCache 的首地址
0x00007fffe101fd15: mov    -0x28(%rbp),%rcx
// 左移 2 位,因为 %edx 中存储的是 ConstantPoolCacheEntry index,// 左移 2 位是因为 ConstantPoolCacheEntry 的内存占用是 4 个字
0x00007fffe101fd19: shl    $0x2,%edx
// 计算 %rcx+%rdx*8+0x10,获取 ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices
// 因为 ConstantPoolCache 的大小为 0x16 字节,%rcx+0x10 定位到第一个 ConstantPoolCacheEntry 的开始地位
// %rdx* 8 算进去的是绝对于第一个 ConstantPoolCacheEntry 的字节偏移
0x00007fffe101fd1c: mov    0x10(%rcx,%rdx,8),%ebx
// _indices 向右挪动 16 位后获取 [get bytecode,set bytecode,original constant pool index] 中的 get bytecode 与 set bytecode
0x00007fffe101fd20: shr    $0x10,%ebx
// 获取 set bytecode 字段的值
0x00007fffe101fd23: and    $0xff,%ebx
// 0xb2 是 getstatic 指令的 Opcode,比拟值,如果相等就阐明曾经连贯,跳转到 resolved
0x00007fffe101fd29: cmp    $0xb2,%ebx
0x00007fffe101fd2f: je     0x00007fffe101fdce


// 将 getstatic 字节码的 Opcode 存储到 %ebx 中
0x00007fffe101fd35: mov    $0xb2,%ebx

// 省略通过调用 MacroAssembler::call_VM()函数来执行 InterpreterRuntime::resolve_get_put()函数的汇编代码
// ...

调用 MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行 InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前曾经具体介绍过,这里不再介绍,间接给出汇编代码,如下:

0x00007fffe101fd3a: callq  0x00007fffe101fd44
0x00007fffe101fd3f: jmpq   0x00007fffe101fdc2

0x00007fffe101fd44: mov    %rbx,%rsi
0x00007fffe101fd47: lea    0x8(%rsp),%rax
0x00007fffe101fd4c: mov    %r13,-0x38(%rbp)
0x00007fffe101fd50: mov    %r15,%rdi
0x00007fffe101fd53: mov    %rbp,0x200(%r15)
0x00007fffe101fd5a: mov    %rax,0x1f0(%r15)
0x00007fffe101fd61: test   $0xf,%esp
0x00007fffe101fd67: je     0x00007fffe101fd7f
0x00007fffe101fd6d: sub    $0x8,%rsp
0x00007fffe101fd71: callq  0x00007ffff66b567c
0x00007fffe101fd76: add    $0x8,%rsp
0x00007fffe101fd7a: jmpq   0x00007fffe101fd84
0x00007fffe101fd7f: callq  0x00007ffff66b567c
0x00007fffe101fd84: movabs $0x0,%r10
0x00007fffe101fd8e: mov    %r10,0x1f0(%r15)
0x00007fffe101fd95: movabs $0x0,%r10
0x00007fffe101fd9f: mov    %r10,0x200(%r15)
0x00007fffe101fda6: cmpq   $0x0,0x8(%r15)
0x00007fffe101fdae: je     0x00007fffe101fdb9
0x00007fffe101fdb4: jmpq   0x00007fffe1000420
0x00007fffe101fdb9: mov    -0x38(%rbp),%r13
0x00007fffe101fdbd: mov    -0x30(%rbp),%r14
0x00007fffe101fdc1: retq   

如上代码实现的事件很简略,就是调用 C ++ 函数编写的 InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中 ConstantPoolCacheEntry 信息,对于 ConstantPoolCache 以及 ConstantPoolCacheEntry,还有 ConstantPoolCacheEntry 中各个字段的含意在《深刻分析 Java 虚拟机:源码分析与实例详解(根底卷)》中曾经具体介绍过,这里不再介绍。

InterpreterRuntime::resolve_get_put()函数的实现比拟多,咱们首先看一部分实现,如下:

IRT_ENTRY(void, InterpreterRuntime::resolve_get_put(JavaThread* thread, Bytecodes::Code bytecode))
  // resolve field
  fieldDescriptor      info;
  constantPoolHandle   pool(thread, method(thread)->constants());
  bool  is_put    = (bytecode == Bytecodes::_putfield  || bytecode == Bytecodes::_putstatic);
  bool  is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic);

  {JvmtiHideSingleStepping jhss(thread);
    int x = get_index_u2_cpcache(thread, bytecode); // 依据线程栈中的 bcp 来获取常量池缓存索引
    LinkResolver::resolve_field_access(info, pool, x ,bytecode, CHECK); // 向 info 中收集信息
  } 

  // check if link resolution caused cpCache to be updated
  if (already_resolved(thread)){return;}

   ...
}

调用 get_index_u2_cpcache()函数从以后办法对应的栈帧中获取 bcp,而后通过 bcp 来获取字节码指令的操作数,也就是常量池索引,失去常量池索引后调用 LinkResolver::resolve_field_access()函数可能会连贯类和字段,而后将查问到的字段相干信息存储到 fieldDescriptor 中。resolve_field_access()函数的实现如下:

void LinkResolver::resolve_field_access(
 fieldDescriptor&     result,
 constantPoolHandle   pool,
 int                  index, // 常量池索引
 Bytecodes::Code      byte,
 TRAPS
) {Symbol* field = pool->name_ref_at(index);
  Symbol* sig   = pool->signature_ref_at(index);

  // resolve specified klass  连贯特定的类
  KlassHandle resolved_klass;
  resolve_klass(resolved_klass, pool, index, CHECK);

  KlassHandle  current_klass(THREAD, pool->pool_holder());
  resolve_field(result, resolved_klass, field, sig, current_klass, byte, true, true, CHECK);
} 

从 pool 中查找到的 index 处的索引项为 CONSTANT_NameAndType_info,格局如下:

CONSTANT_NameAndType_info {
   u1 tag;
   u2 name_index;       // 占用 16 位
   u2 descriptor_index; // 占用 16 位
}

常量池中的一个 CONSTANT_NameAndType_info 数据项,能够看做 CONSTANT_NameAndType 类型的一个实例。从这个数据项的名称能够看出,它形容了两种信息,第一种信息是名称(Name),第二种信息是类型(Type)。这里的名称是指办法的名称或者字段的名称,而 Type 是狭义上的类型,它其实形容的是字段的描述符或办法的描述符。也就是说,如果 Name 局部是一个字段名称,那么 Type 局部就是相应字段的描述符;如果 Name 局部形容的是一个办法的名称,那么 Type 局部就是对应的办法的描述符。也就是说,一个 CONSTANT_NameAndType_info 就示意了一个办法或一个字段。

调用 resolve_klass()连贯类,调用 resolve_field()连贯字段。在 resolve_field()函数中有如下实现:

InstanceKlass* tmp = InstanceKlass::cast(resolved_klass());
KlassHandle    sel_klass(THREAD, tmp->find_field(field, sig, &fd));

最重要的就是调用 InstanceKlass 的 find_field()函数查找字段,将查找到的相干信息存储到 fieldDescriptor 类型的 fd 中。对于字段在 InstanceKlass 中的存储以及具体的布局在《深刻分析 Java 虚拟机:源码分析与实例详解(根底卷)》中曾经具体介绍过,这里不再介绍。

fieldDescriptor 类及重要属性的定义如下:

class fieldDescriptor VALUE_OBJ_CLASS_SPEC {
 private:
  AccessFlags          _access_flags;
  int                  _index; // the field index
  constantPoolHandle   _cp;
  ...
}

其中的_access_flags 可用来示意字段是否有 volatile、final 等关键字润饰,_index 示意字段是存储在 InstanceKlass 中相应数组的第几个元组中。_cp 示意定义以后字段的类的常量池。

通过调用 resolve_klass()和 resolve_field()函数后就可拿到这些信息,而后返回到 InterpreterRuntime::resolve_get_put()函数持续查看实现逻辑:

  TosState state  = as_TosState(info.field_type());

  Bytecodes::Code put_code = (Bytecodes::Code)0;


  InstanceKlass* klass = InstanceKlass::cast(info.field_holder());
  bool uninitialized_static = ((bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic) &&
                                 !klass->is_initialized());
  Bytecodes::Code get_code = (Bytecodes::Code)0;

  if (!uninitialized_static) {get_code = ((is_static) ? Bytecodes::_getstatic : Bytecodes::_getfield);
    // 1、是 putfield 或 putstatic 指令
    // 2、是 getstatic 或 getfield 指令并且不是获取 final 变量的值
    if (is_put || !info.access_flags().is_final()) {put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield);
    }
  }

  ConstantPoolCacheEntry* cpce = cache_entry(thread);
  cpce->set_field(
    get_code,            // 设置的是_indices 中的 b1,当为 getstatic 或 getfield 时,则其中存储的是 Opcode
    put_code,            // 设置的是_indices 中的 b2,当为 setstatic 或 setfield 时,则其中存储的是 Opcode,所以 get_code 与 put_code 如果要连贯了,其值不为 0
    info.field_holder(), // 设置的是_f1 字段,示意字段的拥有者
    info.index(),                      // field_index,设置的是 flags
    info.offset(),                     // field_offset,设置的是_f2 字段,Offset (in words) of field from start of instanceOop / Klass*
    state,                             // field_type,设置的是 flags
    info.access_flags().is_final(),    // 设置的是 flags
    info.access_flags().is_volatile(), // 设置的是 flags
    pool->pool_holder());

通过 info 中的信息就能够失去字段的各种信息,而后填充 ConstantPoolEntry 信息,这样下次就不必对字段进行连贯了,或者说不必从 InstanceKlass 中查找字段信息了,可间接从 ConstantPoolCacheEntry 中找到所有想得到的信息。

  

上图在《深刻分析 Java 虚拟机:源码分析与实例详解(根底卷)》一书中具体介绍过,通过咱们解读 getstatic 字节码的解释执行过程,能够分明的晓得常量池缓存项的作用。对于 getstatic 来说,开始就会判断_indices 中的高 8 位存储的是否为 getstatic 的操作码,如果不是,则示意没有连贯,所以要调用 InterpreterRuntime::resolve_get_put()函数进行连贯操作。

在连贯实现或曾经连贯实现时会继续执行如下汇编代码:

// 将 ConstantPoolCacheEntry 的索引存储么 %edx
0x00007fffe101fdc2: movzwl 0x1(%r13),%edx
// 将 ConstantPoolCache 的首地址存储到 %rcx
0x00007fffe101fdc7: mov    -0x28(%rbp),%rcx
// 获取对应的 ConstantPoolCacheEntry 对应的索引
0x00007fffe101fdcb: shl    $0x2,%edx

// --resolved --

// 获取 [_indices,_f1,_f2,_flags] 中的_f2,因为 ConstantPoolCache 占用 16 字节,而_indices
// 和_f2 各占用 8 字节,所以_f2 的偏移为 32 字节,也就是 0x32
// _f2 中保留的是字段在 java.lang.Class 实例中的字节偏移,通过此偏移就可获取此字段存储在
// java.lang.Class 实例的值
0x00007fffe101fdce: mov    0x20(%rcx,%rdx,8),%rbx
// 获取 [_indices,_f1,_f2,_flags] 中的_flags 
0x00007fffe101fdd3: mov 0x28(%rcx,%rdx,8),%eax
// 获取 [_indices,_f1,_f2,_flags] 中的_f1,_f1 保留了字段拥有者,// 也就是 java.lang.Class 对象
0x00007fffe101fdd7: mov 0x18(%rcx,%rdx,8),%rcx

// 从_f1 中获取_java_mirror 属性的值
0x00007fffe101fddc: mov    0x70(%rcx),%rcx
// 将_flags 向右挪动 28 位,剩下 TosState
0x00007fffe101fde0: shr    $0x1c,%eax
0x00007fffe101fde3: and    $0xf,%eax
// 如果不相等,阐明 TosState 的值不为 0,则跳转到 notByte
0x00007fffe101fde6: jne    0x00007fffe101fdf6

// btos
// btos 的编号为 0, 代码执行到这里时,可能栈顶缓存要求是 btos
// %rcx 中存储的是_java_mirror,%rbx 中存储的是_f2,因为动态变量存储在_java_mirror 中,所以要获取
// 对应的首地址并压入栈中
0x00007fffe101fdec: movsbl (%rcx,%rbx,1),%eax
0x00007fffe101fdf0: push   %rax
// 跳转到 Done
0x00007fffe101fdf1: jmpq   0x00007fffe101ff0c
// -- notByte --
// %eax 中存储的是 TosState,如果不为 atos,则跳转到 notObj
0x00007fffe101fdf6: cmp    $0x7,%eax
0x00007fffe101fdf9: jne    0x00007fffe101fe90

// atos
// %rcx 中存储的是_java_mirror,%rbx 中存储的是_f2,// 所以要获取动态变量的首地址并压入栈内
0x00007fffe101fdff: mov    (%rcx,%rbx,1),%eax
0x00007fffe101fe02: push   %r10
0x00007fffe101fe04: cmp    0x163a8d45(%rip),%r12   # 0x00007ffff73c8b50 
0x00007fffe101fe0b: je     0x00007fffe101fe88
0x00007fffe101fe11: mov    %rsp,-0x28(%rsp)
0x00007fffe101fe16: sub    $0x80,%rsp
0x00007fffe101fe1d: mov    %rax,0x78(%rsp)
0x00007fffe101fe22: mov    %rcx,0x70(%rsp)
0x00007fffe101fe27: mov    %rdx,0x68(%rsp)
0x00007fffe101fe2c: mov    %rbx,0x60(%rsp)
0x00007fffe101fe31: mov    %rbp,0x50(%rsp)
0x00007fffe101fe36: mov    %rsi,0x48(%rsp)
0x00007fffe101fe3b: mov    %rdi,0x40(%rsp)
0x00007fffe101fe40: mov    %r8,0x38(%rsp)
0x00007fffe101fe45: mov    %r9,0x30(%rsp)
0x00007fffe101fe4a: mov    %r10,0x28(%rsp)
0x00007fffe101fe4f: mov    %r11,0x20(%rsp)
0x00007fffe101fe54: mov    %r12,0x18(%rsp)
0x00007fffe101fe59: mov    %r13,0x10(%rsp)
0x00007fffe101fe5e: mov    %r14,0x8(%rsp)
0x00007fffe101fe63: mov    %r15,(%rsp)
0x00007fffe101fe67: movabs $0x7ffff6d4d828,%rdi
0x00007fffe101fe71: movabs $0x7fffe101fe11,%rsi
0x00007fffe101fe7b: mov    %rsp,%rdx
0x00007fffe101fe7e: and    $0xfffffffffffffff0,%rsp
0x00007fffe101fe82: callq  0x00007ffff6872e3a
0x00007fffe101fe87: hlt 
0x00007fffe101fe88: pop    %r10
0x00007fffe101fe8a: push   %rax
0x00007fffe101fe8b: jmpq   0x00007fffe101ff0c

// -- notObj --
0x00007fffe101fe90: cmp    $0x3,%eax
// 如果不为 itos,则跳转到 notInt
0x00007fffe101fe93: jne    0x00007fffe101fea2

// itos
0x00007fffe101fe99: mov    (%rcx,%rbx,1),%eax
0x00007fffe101fe9c: push   %rax
// 跳转到 Done
0x00007fffe101fe9d: jmpq   0x00007fffe101ff0c
// -- notInt --
// 如果不为 ctos,则跳转到 notChar
0x00007fffe101fea2: cmp    $0x1,%eax
0x00007fffe101fea5: jne    0x00007fffe101feb5

// ctos
0x00007fffe101feab: movzwl (%rcx,%rbx,1),%eax
0x00007fffe101feaf: push   %rax
// 跳转到 Done
0x00007fffe101feb0: jmpq   0x00007fffe101ff0c
// -- notChar --
// 如果不为 stos,则跳转到 notShort
0x00007fffe101feb5: cmp    $0x2,%eax
0x00007fffe101feb8: jne    0x00007fffe101fec8

// stos
0x00007fffe101febe: movswl (%rcx,%rbx,1),%eax
0x00007fffe101fec2: push   %rax
// 跳转到 done
0x00007fffe101fec3: jmpq   0x00007fffe101ff0c
// -- notShort --
// 如果不为 ltos,则跳转到 notLong
0x00007fffe101fec8: cmp    $0x4,%eax
0x00007fffe101fecb: jne    0x00007fffe101fee2

// ltos
0x00007fffe101fed1: mov    (%rcx,%rbx,1),%rax
0x00007fffe101fed5: sub    $0x10,%rsp
0x00007fffe101fed9: mov    %rax,(%rsp)
// 跳转到 Done
0x00007fffe101fedd: jmpq   0x00007fffe101ff0c
// -- notLong --
// 如果不为 ftos,则跳转到 notFloat
0x00007fffe101fee2: cmp    $0x5,%eax
0x00007fffe101fee5: jne    0x00007fffe101fefe

// ftos
0x00007fffe101feeb: vmovss (%rcx,%rbx,1),%xmm0
0x00007fffe101fef0: sub    $0x8,%rsp
0x00007fffe101fef4: vmovss %xmm0,(%rsp)
// 跳转到 Done
0x00007fffe101fef9: jmpq   0x00007fffe101ff0c
// -- notFloat --
0x00007fffe101fefe: vmovsd (%rcx,%rbx,1),%xmm0
0x00007fffe101ff03: sub    $0x10,%rsp
0x00007fffe101ff07: vmovsd %xmm0,(%rsp)  
  
// -- Done --
  

如上汇编代码尽管多,然而实现的逻辑却非常简单,就是通过 ConstantPoolCacheEntry 中存储的信息(所谓的字节码连贯实现指的就是对应的常量池缓存项的信息曾经欠缺)实现压栈的逻辑。因为动态字段的值存储在 java.lang.Class 实例中,所以须要获取到对应的值,而后依据栈顶缓存要求的状态将值压入表达式栈即可。

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

getfield 指令示意获取指定类的实例域,并将其值压入栈顶。其格局如下:

getstatic indexbyte1 indexbyte2

无符号数 indexbyte1 和 indexbyte2 构建为(indexbyte1<<8)|indexbyte2,这个值指明了一个以后类的运行时常量池索引值,指向的运行时常量池项为一个字段的符号援用。

getfield 字节码指令的生成函数为 TemplateTable::getfield(),这些生成函数如下:

void TemplateTable::getfield(int byte_no) {getfield_or_static(byte_no, false); // getfield 的 byte_no 值为 1
}

最终会调用 getfield_or_static()函数生成机器指令片段。此函数生成的机器指令片段对应的汇编代码如下:

0x00007fffe10202d0: movzwl 0x1(%r13),%edx
0x00007fffe10202d5: mov    -0x28(%rbp),%rcx
0x00007fffe10202d9: shl    $0x2,%edx
0x00007fffe10202dc: mov    0x10(%rcx,%rdx,8),%ebx
0x00007fffe10202e0: shr    $0x10,%ebx
0x00007fffe10202e3: and    $0xff,%ebx
// 0xb4 是 getfield 指令的 Opcode,如果相等,则阐明曾经连贯,间接跳转到 resolved
0x00007fffe10202e9: cmp    $0xb4,%ebx
0x00007fffe10202ef: je     0x00007fffe102038e

0x00007fffe10202f5: mov    $0xb4,%ebx
// 省略通过调用 MacroAssembler::call_VM()函数来执行 
// InterpreterRuntime::resolve_get_put()函数的汇编代码 
// ...

调用 MacroAssembler::call_VM()函数生成如下代码,通过这些代码来执行 InterpreterRuntime::resolve_get_put()函数。MacroAssembler::call_VM()函数的汇编在之前曾经具体介绍过,这里不再介绍,间接给出汇编代码,如下:

0x00007fffe10202fa: callq  0x00007fffe1020304
0x00007fffe10202ff: jmpq   0x00007fffe1020382

0x00007fffe1020304: mov    %rbx,%rsi
0x00007fffe1020307: lea    0x8(%rsp),%rax
0x00007fffe102030c: mov    %r13,-0x38(%rbp)
0x00007fffe1020310: mov    %r15,%rdi
0x00007fffe1020313: mov    %rbp,0x200(%r15)
0x00007fffe102031a: mov    %rax,0x1f0(%r15)
0x00007fffe1020321: test   $0xf,%esp
0x00007fffe1020327: je     0x00007fffe102033f
0x00007fffe102032d: sub    $0x8,%rsp
0x00007fffe1020331: callq  0x00007ffff66b567c
0x00007fffe1020336: add    $0x8,%rsp
0x00007fffe102033a: jmpq   0x00007fffe1020344
0x00007fffe102033f: callq  0x00007ffff66b567c
0x00007fffe1020344: movabs $0x0,%r10
0x00007fffe102034e: mov    %r10,0x1f0(%r15)
0x00007fffe1020355: movabs $0x0,%r10
0x00007fffe102035f: mov    %r10,0x200(%r15)
0x00007fffe1020366: cmpq   $0x0,0x8(%r15)
0x00007fffe102036e: je     0x00007fffe1020379
0x00007fffe1020374: jmpq   0x00007fffe1000420
0x00007fffe1020379: mov    -0x38(%rbp),%r13
0x00007fffe102037d: mov    -0x30(%rbp),%r14
0x00007fffe1020381: retq   

如上代码实现的事件很简略,就是调用 C ++ 函数编写的 InterpreterRuntime::resolve_get_put()函数,此函数会填充常量池缓存中 ConstantPoolCacheEntry 信息,对于 ConstantPoolCache 以及 ConstantPoolCacheEntry,还有 ConstantPoolCacheEntry 中各个字段的含意在《深刻分析 Java 虚拟机:源码分析与实例详解(根底卷)》中曾经具体介绍过,这里不再介绍。

0x00007fffe1020382: movzwl 0x1(%r13),%edx
0x00007fffe1020387: mov    -0x28(%rbp),%rcx
0x00007fffe102038b: shl    $0x2,%edx

---- resolved ---- 

// 获取 [_indices,_f1,_f2,_flags] 中的_f2,因为 ConstantPoolCache 占用 16 字节,而_indices
// 和_f2 各占用 8 字节,所以_f2 的偏移为 32 字节,也就是 0x32
// _f2 中保留的是字段在 oop 实例中的字节偏移,通过此偏移就可获取此字段存储在
// oop 中的值
0x00007fffe102038e: mov    0x20(%rcx,%rdx,8),%rbx

// 获取 [_indices,_f1,_f2,_flags] 中的_flags 
0x00007fffe1020393: mov    0x28(%rcx,%rdx,8),%eax

// 将栈中的 objectref 对象弹出到 %rcx 中
0x00007fffe1020397: pop    %rcx

// provoke(激发; 引起; 引发)OS NULL exception if reg = NULL by
// accessing M[reg] w/o changing any (non-CC) registers
// NOTE: cmpl is plenty(足够)here to provoke a segv
0x00007fffe1020398: cmp    (%rcx),%rax

// 将_flags 向右挪动 28 位,剩下 TosState
0x00007fffe102039b: shr    $0x1c,%eax
0x00007fffe102039e: and    $0xf,%eax
// 如果不相等,阐明 TosState 的值不为 0,则跳转到 notByte
0x00007fffe10203a1: jne    0x00007fffe10203ba

// btos

// btos 的编号为 0, 代码执行到这里时,可能栈顶缓存要求是 btos
// %rcx 中存储的是 objectref,%rbx 中存储的是_f2,获取字段对应的值存储到 %rax 中
0x00007fffe10203a7: movsbl (%rcx,%rbx,1),%eax
0x00007fffe10203ab: push   %rax

// 对字节码指令进行重写,将 Bytecodes::_fast_bgetfield 的 Opcode 存储到 %ecx 中
0x00007fffe10203ac: mov    $0xcc,%ecx
// 将 Bytecodes::_fast_bgetfield 的 Opcode 更新到字节码指令的操作码
0x00007fffe10203b1: mov    %cl,0x0(%r13)
// 跳转到 ---- Done ----
0x00007fffe10203b5: jmpq   0x00007fffe102050f
---- notByte ----
0x00007fffe10203ba: cmp    $0x7,%eax
0x00007fffe10203bd: jne    0x00007fffe102045d  // 跳转到 notObj


// atos

// 调用 MacroAssembler::load_heap_oop()函数生成如下代码
0x00007fffe10203c3: mov    (%rcx,%rbx,1),%eax
// ... 省略局部代码
// 完结 MacroAssembler::load_heap_oop()函数的调用
0x00007fffe102044e: push   %rax
// 重写字节码指令为 Bytecodes::_fast_agetfield
0x00007fffe102044f: mov    $0xcb,%ecx
0x00007fffe1020454: mov    %cl,0x0(%r13)
0x00007fffe1020458: jmpq   0x00007fffe102050f
// -- notObj --
0x00007fffe102045d: cmp    $0x3,%eax
0x00007fffe1020460: jne    0x00007fffe1020478 // 跳转到 notInt

// itos

0x00007fffe1020466: mov    (%rcx,%rbx,1),%eax
0x00007fffe1020469: push   %rax
// 重写字节码指令 o Bytecodes::_fast_igetfield
0x00007fffe102046a: mov    $0xd0,%ecx
0x00007fffe102046f: mov    %cl,0x0(%r13)
0x00007fffe1020473: jmpq   0x00007fffe102050f
// --- notInt ----
0x00007fffe1020478: cmp    $0x1,%eax
0x00007fffe102047b: jne    0x00007fffe1020494 // 跳转到 notChar


// ctos

0x00007fffe1020481: movzwl (%rcx,%rbx,1),%eax
0x00007fffe1020485: push   %rax
// 重写字节码指令为 Bytecodes::_fast_cgetfield
0x00007fffe1020486: mov    $0xcd,%ecx
0x00007fffe102048b: mov    %cl,0x0(%r13)
0x00007fffe102048f: jmpq   0x00007fffe102050f
// ---- notChar ----
0x00007fffe1020494: cmp    $0x2,%eax
0x00007fffe1020497: jne    0x00007fffe10204b0 // 跳转到 notShort

// stos

0x00007fffe102049d: movswl (%rcx,%rbx,1),%eax
0x00007fffe10204a1: push   %rax
// 重写字节码指令为 Bytecodes::_fast_sgetfield
0x00007fffe10204a2: mov    $0xd2,%ecx
0x00007fffe10204a7: mov    %cl,0x0(%r13)
0x00007fffe10204ab: jmpq   0x00007fffe102050f
// ---- notShort ----
0x00007fffe10204b0: cmp    $0x4,%eax
0x00007fffe10204b3: jne    0x00007fffe10204d3 // 跳转到 notLong

// ltos

0x00007fffe10204b9: mov    (%rcx,%rbx,1),%rax
0x00007fffe10204bd: sub    $0x10,%rsp
0x00007fffe10204c1: mov    %rax,(%rsp)
// 重写字节码指令为 Bytecodes::_fast_lgetfield,
0x00007fffe10204c5: mov    $0xd1,%ecx
0x00007fffe10204ca: mov    %cl,0x0(%r13)
0x00007fffe10204ce: jmpq   0x00007fffe102050f
// ---- notLong ----
0x00007fffe10204d3: cmp    $0x5,%eax
0x00007fffe10204d6: jne    0x00007fffe10204f8 // 跳转到 notFloat


// ftos
0x00007fffe10204dc: vmovss (%rcx,%rbx,1),%xmm0
0x00007fffe10204e1: sub    $0x8,%rsp
0x00007fffe10204e5: vmovss %xmm0,(%rsp)
// 重写字节码指令为 Bytecodes::_fast_fgetfield
0x00007fffe10204ea: mov    $0xcf,%ecx
0x00007fffe10204ef: mov    %cl,0x0(%r13)
0x00007fffe10204f3: jmpq   0x00007fffe102050f
// ---- notFloat ----
0x00007fffe10204f8: vmovsd (%rcx,%rbx,1),%xmm0
0x00007fffe10204fd: sub    $0x10,%rsp
0x00007fffe1020501: vmovsd %xmm0,(%rsp)
0x00007fffe1020506: mov    $0xce,%ecx
0x00007fffe102050b: mov    %cl,0x0(%r13)

// -- Done --  
  

咱们须要介绍一下虚拟机外部的一些自定义指令,这些自定义指令的模板如下:

// JVM bytecodes
def(Bytecodes::_fast_agetfield      , ubcp|____|____|____, atos, atos, fast_accessfield    ,  atos);
def(Bytecodes::_fast_bgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos);
def(Bytecodes::_fast_cgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos);
def(Bytecodes::_fast_dgetfield      , ubcp|____|____|____, atos, dtos, fast_accessfield    ,  dtos);
def(Bytecodes::_fast_fgetfield      , ubcp|____|____|____, atos, ftos, fast_accessfield    ,  ftos);
def(Bytecodes::_fast_igetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos);
def(Bytecodes::_fast_lgetfield      , ubcp|____|____|____, atos, ltos, fast_accessfield    ,  ltos);
def(Bytecodes::_fast_sgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos);

以_fast_agetfield 外部定义的字节码指令为例为来,生成函数为 TemplateTable::fast_accessfield()函数,汇编代码如下:

0x00007fffe101e4e1: movzwl 0x1(%r13),%ebx
0x00007fffe101e4e6: mov    -0x28(%rbp),%rcx
0x00007fffe101e4ea: shl    $0x2,%ebx
// 计算 %rcx+%rdx*8+0x20,获取 ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_f2
// 因为 ConstantPoolCache 的大小为 0x16 字节,%rcx+0x20 定位到第一个 ConstantPoolCacheEntry 的开始地位
// %rdx* 8 算进去的是绝对于第一个 ConstantPoolCacheEntry 的字节偏移
0x00007fffe101e4ed: mov    0x20(%rcx,%rbx,8),%rbx

// 查看空异样
0x00007fffe101e4f2: cmp    (%rax),%rax
// %rax 中存储的是 objectref,也就是要从这个实例中获取字段的值,通过偏移 %rbx 后就
// 能获取到偏移的值,而后加载到 %eax
0x00007fffe101e4f5: mov    (%rax,%rbx,1),%eax
  

其它的字节码指令相似,这里不再过多介绍。从这里能够看出,咱们不须要再执行 getfield 对应的那些汇编指令,只执行_fast 结尾的指令即可,这些指令比起 getfield 指令来说简化了很多,大大提高了解释执行的速度。

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

之前曾经介绍了 getstatic 与 getfield 指令的汇编代码执行逻辑,这一篇介绍 putstatic 指令的执行逻辑,putfield 将不再介绍,大家能够本人去钻研,置信大家有这个实力。

putstatic 指令为指定类的动态域赋值。字节码指令的格局如下:

putstatic indexbyte1 indexbyte2

无符号数 indexbyte1 和 indexbyte2 构建为(indexbyte1<<8)|indexbyte2,该索引所指向的运行时常量池项该当是一个字段的符号援用。

指令的模板定义如下:

def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte);  

生成函数为 putstatic(),函数的实现如下:

void TemplateTable::putstatic(int byte_no) {putfield_or_static(byte_no, false);
}

调用 TemplateTable::putfield_or_static()函数生成的机器指令对应的汇编代码如下:

0x00007fffe101ff90: movzwl 0x1(%r13),%edx
0x00007fffe101ff95: mov    -0x28(%rbp),%rcx
0x00007fffe101ff99: shl    $0x2,%edx
0x00007fffe101ff9c: mov    0x10(%rcx,%rdx,8),%ebx
0x00007fffe101ffa0: shr    $0x18,%ebx
0x00007fffe101ffa3: and    $0xff,%ebx
// 是否曾经对 putstatic 指令进行了连贯,如果曾经连贯,则跳转到 resolved
0x00007fffe101ffa9: cmp    $0xb3,%ebx
0x00007fffe101ffaf: je     0x00007fffe102004e 

调用 TemplateTable::resolve_cache_and_index()函数生成如下汇编代码:

// 执行到这里,阐明字段还没有连贯
0x00007fffe101ffb5: mov    $0xb3,%ebx

// 调用 MacroAssembler::call_VM()函数生成如下代码,// 用来执行 InterpreterRuntime::resolve_get_put()函数
0x00007fffe101ffba: callq  0x00007fffe101ffc4
0x00007fffe101ffbf: jmpq   0x00007fffe1020042
0x00007fffe101ffc4: mov    %rbx,%rsi
0x00007fffe101ffc7: lea    0x8(%rsp),%rax
0x00007fffe101ffcc: mov    %r13,-0x38(%rbp)
0x00007fffe101ffd0: mov    %r15,%rdi
0x00007fffe101ffd3: mov    %rbp,0x200(%r15)
0x00007fffe101ffda: mov    %rax,0x1f0(%r15)
0x00007fffe101ffe1: test   $0xf,%esp
0x00007fffe101ffe7: je     0x00007fffe101ffff
0x00007fffe101ffed: sub    $0x8,%rsp
0x00007fffe101fff1: callq  0x00007ffff66b567c
0x00007fffe101fff6: add    $0x8,%rsp
0x00007fffe101fffa: jmpq   0x00007fffe1020004
0x00007fffe101ffff: callq  0x00007ffff66b567c
0x00007fffe1020004: movabs $0x0,%r10
0x00007fffe102000e: mov    %r10,0x1f0(%r15)
0x00007fffe1020015: movabs $0x0,%r10
0x00007fffe102001f: mov    %r10,0x200(%r15)
0x00007fffe1020026: cmpq   $0x0,0x8(%r15)
0x00007fffe102002e: je     0x00007fffe1020039
0x00007fffe1020034: jmpq   0x00007fffe1000420
0x00007fffe1020039: mov    -0x38(%rbp),%r13
0x00007fffe102003d: mov    -0x30(%rbp),%r14
0x00007fffe1020041: retq   


0x00007fffe1020042: movzwl 0x1(%r13),%edx
0x00007fffe1020047: mov    -0x28(%rbp),%rcx
0x00007fffe102004b: shl    $0x2,%edx

接下来生成的汇编代码如下:

// ---- resolved ----

// 执行如下代码时,示意字段曾经连贯实现

0x00007fffe102004e: mov    0x20(%rcx,%rdx,8),%rbx
0x00007fffe1020053: mov    0x28(%rcx,%rdx,8),%eax
0x00007fffe1020057: mov    0x18(%rcx,%rdx,8),%rcx
0x00007fffe102005c: mov    0x70(%rcx),%rcx
0x00007fffe1020060: mov    %eax,%edx
// 将_flags 向右挪动 21 位,判断是否有 volatile 关键字
0x00007fffe1020062: shr    $0x15,%edx
0x00007fffe1020065: and    $0x1,%edx
// 将_flags 向右挪动 28 位,剩下 TosState
0x00007fffe1020068: shr    $0x1c,%eax

// 如果不为 btos,则跳转到 notByte
0x00007fffe102006b: and    $0xf,%eax
0x00007fffe102006e: jne    0x00007fffe1020083

// btos

// 将栈顶的值存储到 %eax 中,这个值会写入到对应的字段中
0x00007fffe1020074: mov    (%rsp),%eax
0x00007fffe1020077: add    $0x8,%rsp
// %rcx 为_java_mirror,%rbx 为_f2,示意域在类中的偏移
0x00007fffe102007b: mov    %al,(%rcx,%rbx,1)
0x00007fffe102007e: jmpq   0x00007fffe10201be  // 跳转到 Done
// -- notByte --
// 如果不为 atos,则跳转到 notObj
0x00007fffe1020083: cmp    $0x7,%eax
0x00007fffe1020086: jne    0x00007fffe1020130

// atos
// 将栈顶的值弹出到 %rax 中,这个值将用来更新对应字段的值
0x00007fffe102008c: pop    %rax
// ...
// 将值更新到对应的字段上
0x00007fffe1020115: mov    %eax,(%rcx,%rbx,1)
// 其中的 0x9 是 CardTableModRefBS::card_shift,shr 示意逻辑右移,因为 %rcx 指向的是
// java.lang.Class 实例的首地址,向右移后 %rcx 就算出了卡表的索引
0x00007fffe1020118: shr    $0x9,%rcx
// 地址常量 $0x7fffe07ff000 示意卡表的基地址
0x00007fffe102011c: movabs $0x7fffe07ff000,%r10 
// 将对应的卡表项标记为脏,其中常量 0x0 就示意是脏卡
0x00007fffe1020126: movb $0x0,(%r10,%rcx,1) 
0x00007fffe102012b: jmpq 
0x00007fffe10201be // 跳转到 Done
// ---- notObj ----
// 如果不为 itos,那么跳转到 notInt
0x00007fffe1020130: cmp    $0x3,%eax
0x00007fffe1020133: jne    0x00007fffe1020148

// itos
0x00007fffe1020139: mov    (%rsp),%eax
// 如果不为 ctos,则跳转到 notChar
0x00007fffe102013c: add    $0x8,%rsp
0x00007fffe1020140: mov    %eax,(%rcx,%rbx,1)
0x00007fffe1020143: jmpq   0x00007fffe10201be   // 跳转到 Done
0x00007fffe1020148: cmp    $0x1,%eax
0x00007fffe102014b: jne    0x00007fffe1020161

// ctos
0x00007fffe1020151: mov    (%rsp),%eax
0x00007fffe1020154: add    $0x8,%rsp
0x00007fffe1020158: mov    %ax,(%rcx,%rbx,1)
0x00007fffe102015c: jmpq   0x00007fffe10201be  // 跳转到 Done
0x00007fffe1020161: cmp    $0x2,%eax
0x00007fffe1020164: jne    0x00007fffe102017a

// stos
0x00007fffe102016a: mov    (%rsp),%eax
0x00007fffe102016d: add    $0x8,%rsp
0x00007fffe1020171: mov    %ax,(%rcx,%rbx,1)
0x00007fffe1020175: jmpq   0x00007fffe10201be  // 跳转到 Done
0x00007fffe102017a: cmp    $0x4,%eax
0x00007fffe102017d: jne    0x00007fffe1020194

// ltos
0x00007fffe1020183: mov    (%rsp),%rax
0x00007fffe1020187: add    $0x10,%rsp
0x00007fffe102018b: mov    %rax,(%rcx,%rbx,1)
0x00007fffe102018f: jmpq   0x00007fffe10201be  // 跳转到 Done
0x00007fffe1020194: cmp    $0x5,%eax
0x00007fffe1020197: jne    0x00007fffe10201b0

// ftos
0x00007fffe102019d: vmovss (%rsp),%xmm0
0x00007fffe10201a2: add    $0x8,%rsp
0x00007fffe10201a6: vmovss %xmm0,(%rcx,%rbx,1)
0x00007fffe10201ab: jmpq   0x00007fffe10201be   // 跳转到 Done

// dtos
0x00007fffe10201b0: vmovsd (%rsp),%xmm0
0x00007fffe10201b5: add    $0x10,%rsp
0x00007fffe10201b9: vmovsd %xmm0,(%rcx,%rbx,1)


// ---- Done ----

0x00007fffe10201be: test   %edx,%edx
0x00007fffe10201c0: je     0x00007fffe10201cb
0x00007fffe10201c6: lock addl $0x0,(%rsp)

// ---- notVolatile ---- 
  

在如上代码中,最值得关注的 2 个点如下:

(1)更新援用字段时,通过屏障将对应的卡表项标记为脏,这样可在 GC 过程中扫描脏卡就可将沉闷对象标记进去而不会造成脱漏;

(2)当字段有 volatile 关键字润饰时,须要填写 lock 指令前缀,这个前缀在之前介绍 x86-64 机器指令时没有介绍过,这里摘抄一下他人对此指令的介绍:

Intel 手册对 lock 前缀的阐明如下:

  1. 确保被润饰指令执行的原子性;
  2. 禁止该指令与后面和前面的读写指令重排序;
  3. 指令执行完后把写缓冲区的 所有数据 刷新到内存中(这样这个指令之前的其余批改对所有处理器可见)。

在所有的 X86 CPU 上都具备锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就能够阻止其余的系统总线读取或批改这个内存地址。这种能力是通过 lock 指令前缀再加上上面的汇编指令来实现的。当应用 lock 指令前缀时,它会使 CPU 宣告一个 lock# 信号,这样就能确保在多处理器零碎或多线程竞争的环境下互斥地应用这个内存地址。当指令执行结束,这个锁定动作也就会隐没。

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

操作数栈治理相干的字节码指令如下表所示。

0x57 pop 将栈顶数值弹出 (数值不能是 long 或 double 类型的)
0x58 pop2 将栈顶的一个(long 或 double 类型的)或两个数值弹出(其它)
0x59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(long 或 double 类型的)或两个(其它)数值并将复制值压入栈顶
0x5d dup2_x1 dup_x1 指令的双倍版本
0x5e dup2_x2 dup_x2 指令的双倍版本
0x5f swap 将栈最顶端的两个数值调换(数值不能是 long 或 double 类型的)

字节码指令对应的模板定义如下:

def(Bytecodes::_pop         , ____|____|____|____, vtos, vtos, pop         ,  _);
def(Bytecodes::_pop2        , ____|____|____|____, vtos, vtos, pop2        ,  _);
def(Bytecodes::_dup         , ____|____|____|____, vtos, vtos, dup         ,  _);
def(Bytecodes::_dup_x1      , ____|____|____|____, vtos, vtos, dup_x1      ,  _);
def(Bytecodes::_dup_x2      , ____|____|____|____, vtos, vtos, dup_x2      ,  _);
def(Bytecodes::_dup2        , ____|____|____|____, vtos, vtos, dup2        ,  _);
def(Bytecodes::_dup2_x1     , ____|____|____|____, vtos, vtos, dup2_x1     ,  _);
def(Bytecodes::_dup2_x2     , ____|____|____|____, vtos, vtos, dup2_x2     ,  _);
def(Bytecodes::_swap        , ____|____|____|____, vtos, vtos, swap        ,  _);

pop 指令将栈顶数值弹出。对应的汇编代码如下:

add    $0x8,%rsp   

pop2 指令将栈顶数值弹出。对应的汇编代码如下:

add    $0x10,%rsp  

dup 指令复制栈顶数值并将复制值压入栈顶。对应的汇编代码如下:

mov    (%rsp),%rax
push   %rax

swap 指令将栈最顶端的两个数值调换(数值不能是 long 或 double 类型的)。对应的汇编代码如下:

mov    0x8(%rsp),%rcx
mov    (%rsp),%rax
mov    %rcx,(%rsp)
mov    %rax,0x8(%rsp)

指令的执行逻辑比较简单,这里不再过多介绍。

第 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 跳转至指定 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

退出移动版