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

  • 2021 年 8 月 30 日
  • 筆記

在TemplateInterpreterGenerator::generate_all()函数中生成了许多字节码指令以及一些虚拟机辅助执行的机器指令片段,例如生成空指针异常抛出入口的实现如下:

{
    CodeletMark cm(_masm, "throw exception entrypoints");
    // ...
    Interpreter::_throw_NullPointerException_entry = generate_exception_handler("java/lang/NullPointerException",NULL);
    // ...
}

调用generate_exception_handler()函数生成抛出空指针的代码片段。

address generate_exception_handler(const char* name, const char* message) {
    return generate_exception_handler_common(name, message, false);
}

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

address TemplateInterpreterGenerator::generate_exception_handler_common(
const char* name, 
const char* message, 
bool pass_oop
) {

  assert(!pass_oop || message == NULL, "either oop or message but not both");
  address entry = __ pc();
  if (pass_oop) {
    // object is at TOS
    __ pop(c_rarg2);
  }

  // expression stack must be empty before entering the VM if an
  // exception happened
  __ empty_expression_stack();

  // setup parameters
  __ lea(c_rarg1, ExternalAddress((address)name));

  if (pass_oop) {
    __ call_VM(rax,
               CAST_FROM_FN_PTR(address,InterpreterRuntime::create_klass_exception),
               c_rarg1,c_rarg2);
  } else {
    // kind of lame ExternalAddress can't take NULL because
    // external_word_Relocation will assert.
    if (message != NULL) {
      __ lea(c_rarg2, ExternalAddress((address)message));
    } else {
      __ movptr(c_rarg2, NULL_WORD);
    }
    __ call_VM(rax,
               CAST_FROM_FN_PTR(address, InterpreterRuntime::create_exception),
               c_rarg1, c_rarg2);
  }

  // throw exception
  __ jump(ExternalAddress(Interpreter::throw_exception_entry()));

  return entry;
}

生成的汇编代码如下:

0x00007fffe10101cb: mov    -0x40(%rbp),%rsp
0x00007fffe10101cf: movq   $0x0,-0x10(%rbp)
0x00007fffe10101d7: movabs $0x7ffff6e09878,%rsi
0x00007fffe10101e1: movabs $0x0,%rdx
0x00007fffe10101eb: callq  0x00007fffe10101f5
0x00007fffe10101f0: jmpq   0x00007fffe1010288
0x00007fffe10101f5: lea    0x8(%rsp),%rax
0x00007fffe10101fa: mov    %r13,-0x38(%rbp)
0x00007fffe10101fe: mov    %r15,%rdi
0x00007fffe1010201: mov    %rbp,0x200(%r15)
0x00007fffe1010208: mov    %rax,0x1f0(%r15)
0x00007fffe101020f: test   $0xf,%esp
0x00007fffe1010215: je     0x00007fffe101022d
0x00007fffe101021b: sub    $0x8,%rsp
0x00007fffe101021f: callq  0x00007ffff66b3fbc
0x00007fffe1010224: add    $0x8,%rsp
0x00007fffe1010228: jmpq   0x00007fffe1010232
0x00007fffe101022d: callq  0x00007ffff66b3fbc
0x00007fffe1010232: movabs $0x0,%r10
0x00007fffe101023c: mov    %r10,0x1f0(%r15)
0x00007fffe1010243: movabs $0x0,%r10
0x00007fffe101024d: mov    %r10,0x200(%r15)
0x00007fffe1010254: cmpq   $0x0,0x8(%r15)
0x00007fffe101025c: je     0x00007fffe1010267
0x00007fffe1010262: jmpq   0x00007fffe1000420
0x00007fffe1010267: mov    0x250(%r15),%rax
0x00007fffe101026e: movabs $0x0,%r10
0x00007fffe1010278: mov    %r10,0x250(%r15)
0x00007fffe101027f: mov    -0x38(%rbp),%r13
0x00007fffe1010283: mov    -0x30(%rbp),%r14
0x00007fffe1010287: retq   
0x00007fffe1010288: jmpq   0x00007fffe100f3d3

在这里的重点不是读懂TemplateInterpreterGenerator::generate_exception_handler_common()函数的逻辑及生成的汇编代码,而是要清楚知道CodeletMark的应用,以及generate_exception_handler_common()函数生成的机器指令是如何写入InterpreterCodelet实例中的。之前介绍过InterpreterCodelet与CodeBuffer类,如下: 

通过CodeBuffer操作InterpreterCodelet实例的存储机器指令片段的内存区域,而CodeBuffer中的代码部分(CodeSection)被赋值给AbstractAssembler::_code_section。这样我们就可以通过_code_section属性向InterpreterCodelet实例中写入机器指令了。

向CodeletMark中传入的_masm参数定义在AbstractInterpreterGenerator类中,如下:

class AbstractInterpreterGenerator: public StackObj {
   protected:
      InterpreterMacroAssembler* _masm;
      // ...
}

generate_exception_handler_common()函数中的__是一个宏,定义如下:

#define __ _masm->

这样其实就是调用InterpreterMacroAssembler类中的相关函数写机器指令,例如

__ pop(c_rarg2);

调用的pop()函数如下:

// 定义在InterpreterMacroAssembler中
void pop(Register r ) {
  ((MacroAssembler*)this)->pop(r);
}

// 定义在Assembler类中
void Assembler::pop(Register dst) {
  int encode = prefix_and_encode(dst->encoding());
  emit_int8(0x58 | encode);
}

// 定义在AbstractAssembler类中
void emit_int8(   int8_t  x) { 
   code_section()->emit_int8(   x); 
}

code_section()函数获取的就是AbstractAssembler的_code_section属性的值。  

 

推荐阅读:

第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

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

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