HotSpot模板解释器目标代码生成过程-第2篇

在字节码入口逻辑中,会调用set_entry_points_for_all_bytes()方法对所有被定义的字节码生成目标代码并设置对应的入口,set_entry_points_for_all_bytes()方法的实现如下:

void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() {
  for (int i = 0; i < DispatchTable::length; i++) {
    Bytecodes::Code code = (Bytecodes::Code)i;
    if (Bytecodes::is_defined(code)) {
      set_entry_points(code);
    } else {
      set_unimplemented(i);
    }
  }
}

当code已经是定义时,会调用set_entry_points()方法,set_entry_points()将取出该字节码对应的Template模板,并调用set_short_enrty_points()进行处理,将入口地址保存在转发表(DispatchTable)_normal_table或_wentry_table(使用wide指令)中。实现如下: 

void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {
  CodeletMark cm(_masm, Bytecodes::name(code), code);
  // initialize entry points
  assert(_unimplemented_bytecode    != NULL, "should have been generated before");
  assert(_illegal_bytecode_sequence != NULL, "should have been generated before");
  address bep = _illegal_bytecode_sequence;
  address cep = _illegal_bytecode_sequence;
  address sep = _illegal_bytecode_sequence;
  address aep = _illegal_bytecode_sequence;
  address iep = _illegal_bytecode_sequence;
  address lep = _illegal_bytecode_sequence;
  address fep = _illegal_bytecode_sequence;
  address dep = _illegal_bytecode_sequence;
  address vep = _unimplemented_bytecode;
  address wep = _unimplemented_bytecode;
  // code for short & wide version of bytecode
  if (Bytecodes::is_defined(code)) {
    Template* t = TemplateTable::template_for(code);
    assert(t->is_valid(), "just checking");
    set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);
  }
  if (Bytecodes::wide_is_defined(code)) {
    Template* t = TemplateTable::template_for_wide(code);
    assert(t->is_valid(), "just checking");
    set_wide_entry_point(t, wep);
  }
  // set entry points
  EntryPoint entry(bep, cep, sep, aep, iep, lep, fep, dep, vep);
  Interpreter::_normal_table.set_entry(code, entry);
  Interpreter::_wentry_point[code] = wep;
}

以非wide指令为例进行分析,bep(byte entry point)、cep、 sep、aep、iep、lep、fep、dep、vep分别为指令执行前栈顶元素状态为byte/boolean、char、short、array/reference(对象引用)、int、long、float、double、void类型时的入口地址。

非wild指令将调用set_short_entry_points()方法,方法的实现如下:

void TemplateInterpreterGenerator::set_short_entry_points(Template* t,
		address& bep, address& cep, address& sep, address& aep, address& iep,
		address& lep, address& fep, address& dep, address& vep) {
  assert(t->is_valid(), "template must exist");
  switch (t->tos_in()) {
    case btos:
    case ctos:
    case stos:
      ShouldNotReachHere();  // btos/ctos/stos should use itos.
      break;
    case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t);   break;
    case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t);   break;
    case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t);   break;
    case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t);   break;
    case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t);   break;
    case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);  break;
    default  : ShouldNotReachHere();                                                   break;
  }
}

 set_short_entry_points()方法根据操作数栈栈顶元素类型进行判断,首先byte、char和short类型都应被当做int类型进行处理,对于非void类型将调用generate_and_dispatch()产生目标代码,这里以iconst_0为例对TOS的处理进行介绍: 

对于iconst,其期望的_tos_in(执行前栈顶元素类型)是void类型(vtos),期望的_tos_out(执行后栈顶元素类型)是int类型(itos) 

以期望的栈顶状态为vtos状态为例,分析set_vtos_entry_points()方法:

// Helper for vtos entry point generation
void TemplateInterpreterGenerator::set_vtos_entry_points(Template* t,
                                                         address& bep,
                                                         address& cep,
                                                         address& sep,
                                                         address& aep,
                                                         address& iep,
                                                         address& lep,
                                                         address& fep,
                                                         address& dep,
                                                         address& vep) {
  assert(t->is_valid() && t->tos_in() == vtos, "illegal template");
  Label L;
  aep = __ pc();  __ push_ptr();  __ jmp(L);
  fep = __ pc();  __ push_f();    __ jmp(L);
  dep = __ pc();  __ push_d();    __ jmp(L);
  lep = __ pc();  __ push_l();    __ jmp(L);
  bep = cep = sep =
  iep = __ pc();  __ push_i();
  vep = __ pc();
  __ bind(L);
  generate_and_dispatch(t);
}

以ftos入口类型为例(vtos即当前字节码的实现不关心栈顶元素的状态),分析该入口的处理指令: 
push_f()方法定义在定义在 /hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp中,如下:

void InterpreterMacroAssembler::push_f(XMMRegister r) {
  subptr(rsp, wordSize);
  movflt(Address(rsp, 0), r);
}

其中r的默认值为xmm0,wordSize为机器字长(如64位机器为8字节)  

subptr()实际上调用了subq():  

void MacroAssembler::subptr(Register dst, int32_t imm32) {
  LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));
}

subq()的实现如下:  

void Assembler::subq(Register dst, int32_t imm32) {
   (void) prefixq_and_encode(dst->encoding());
   emit_arith(0x81, 0xE8, dst, imm32);
}

而emit_arith()将调用emit_byte()/emit_long()写入指令的二进制代码”83 EC 08”(由于8可由8位有符号数表示,第一个字节为0x81 | 0x02,即0x83,rsp的寄存器号为4,第二个字节为0xE8 | 0x04,即0xEC,第三个字节为0x08 & 0xFF,即0x08),该指令即AT&T风格的sub $0x8,%rsp  

void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) {
  assert(isByte(op1) && isByte(op2), "wrong opcode");
  assert((op1 & 0x01) == 1, "should be 32bit operation");
  assert((op1 & 0x02) == 0, "sign-extension bit should not be set");
  if (is8bit(imm32)) {
    emit_int8(op1 | 0x02); // set sign bit
    emit_int8(op2 | encode(dst));
    emit_int8(imm32 & 0xFF);
  } else {
    emit_int8(op1);
    emit_int8(op2 | encode(dst));
    emit_int32(imm32);
  }
}

emit_byte()定义在/hotspot/src/share/vm/asm/assembler.inlilne.hpp中: 
该函数将把该字节复制到_code_pos处  

inline void AbstractAssembler::emit_byte(int x) {
  assert(isByte(x), "not a byte");
  *(unsigned char*)_code_pos = (unsigned char)x;
  _code_pos += sizeof(unsigned char);
  sync();
}

故subq()向代码缓冲写入了指令sub $0x8,%rsp 
类似地,movflt()向代码缓冲写入了指令 movss %xmm0,(%rsp) 
jmp()向代码缓冲写入了指令jmpq (addr为字节码的本地代码入口) 

set_vtos_entry_points()产生的入口部分代码如下:

push %rax        .....(atos entry)
jmpq <addr> 
sub $0x8,%rsp     .....(ftos entry)
movss %xmm0,(%rsp)
jmpq <addr>(addr为字节码的本地代码入口)
sub $0x10,%rsp    .....(dtos entry)
movsd %xmm0,(%rsp)
jmpq <addr>
sub $0x10,%rsp     .....(ltos entry)
mov %rax,(%rsp)
jmpq <addr>
push %rax         ...(itos entry)

set_vtos_entry_points()的最后调用generate_and_dispatch()写入了当前字节码的解释代码和跳转到下一个字节码继续执行的逻辑处理部分

generate_and_dispatch()主要内容如下:

void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {
  // ...
  // generate template
  t->generate(_masm);
  // advance
  if (t->does_dispatch()) {
    //asserts
  } else {
    // dispatch to next bytecode
    __ dispatch_epilog(tos_out, step);
  }
}

这里我们以iconst()为目标代码生成器为例,分析generate():  

void Template::generate(InterpreterMacroAssembler* masm) {
  // parameter passing
  TemplateTable::_desc = this;
  TemplateTable::_masm = masm;
  // code generation
  _gen(_arg);
  masm->flush();
}

generate()会调用生成器函数_gen(_arg),该函数根据平台而不同,如x86_64平台下,定义在/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp中  

void TemplateTable::iconst(int value) {
  transition(vtos, itos);
  if (value == 0) {
    __ xorl(rax, rax);
  } else {
    __ movl(rax, value);
  }
}

我们知道,iconst_i指令是将i压入栈,这里生成器函数iconst()在i为0时,没有直接将0写入rax,而是使用异或运算清零,即向代码缓冲区写入指令”xor %rax, %rax”;在i不为0时,写入指令”mov $0xi, %rax” 

当不需要转发时,会调用dispatch_epilog()生成取下一条指令和分派的目标代码:

void InterpreterMacroAssembler::dispatch_epilog(TosState state, int step) {
   dispatch_next(state, step);
}

dispatch_next()实现如下:  

void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {
  // load next bytecode (load before advancing r13 to prevent AGI)
  load_unsigned_byte(rbx, Address(r13, step));
  // advance r13
  increment(r13, step);
  dispatch_base(state, Interpreter::dispatch_table(state));
}

dispatch_next()首先调用load_unsigned_byte()写入指令”movzbl (%r13),%rbx”,再调用increment()写入指令”inc/add (,)%r13”指令,最后调用dispatch_base()写入”jmp *(%r10,%rbx,8)”。这类似于PC自增一条指令的宽度再继续取值运行的过程。 

分析到这里,不禁有一个疑问,_code_pos是哪里?之前说过,StubQueue是用来保存生成的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象包含了字节码对应的本地代码以及一些调试和输出信息。那么_code_pos是如何和InterpreterCodelet对应的呢? 

我们注意到无论是为JVM的各种入口函数,还是为字节码生成本地代码,都会构造一个CodeletMark对象

CodeletMark cm(_masm, Bytecodes::name(code), code);

CodeletMark的构造函数如下:在初始值列表中,调用了StubQueue的request()创建了一个InterpreterCodelet对象,并以该InterpreterCodelet目标代码地址和大小为参数构造了一块CodeBuffer用来存放生成的目标代码。  

public:
  CodeletMark(
    InterpreterMacroAssembler*& masm,
    const char* description,
    Bytecodes::Code bytecode = Bytecodes::_illegal):
    _clet((InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size())),
    _cb(_clet->code_begin(), _clet->code_size())

  { // request all space (add some slack for Codelet data)
    assert (_clet != NULL, "we checked not enough space already");

    // initialize Codelet attributes
    _clet->initialize(description, bytecode);
    // create assembler for code generation
    masm  = new InterpreterMacroAssembler(&_cb);
    _masm = &masm;
  }

但在此时还未生成目标代码,所以并不知道生成的目标代码有多大,所以这里会向StubQueue申请全部的空闲空间(只留有一点用来对齐空间,注意StubQueue实际上是一片连续的内存空间,所有Stub都在该空间上进行分配) 
随后初始化该InterpreterCodelet的描述部分和对应字节码,并以该CodeBuffer为参数构造了一个编译器对象InterpreterMacroAssembler

分析到这里,就应该明白编译器的_code_pos指的就是生成代码在CodeBuffer中的当前写位值 
还需一提的就是CodeletMark的析构函数,这里确认编译器的生产代码完全写入到CodeBuffer中后,就会调用StubQueue的commit()将占用的空间划分为当前Stub(InterpreterCodelet)所有

~CodeletMark() {
    // align so printing shows nop's instead of random code at the end (Codelets are aligned)
    (*_masm)->align(wordSize);
    // make sure all code is in code buffer
    (*_masm)->flush();


    // commit Codelet
    AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size());
    // make sure nobody can use _masm outside a CodeletMark lifespan
    *_masm = NULL;
  }

  

猜你喜欢

转载自www.cnblogs.com/mazhimazhi/p/11430717.html