1. 程式人生 > 實用技巧 >程式碼生成器輔助類Stub、StubQueue與CodeletMark

程式碼生成器輔助類Stub、StubQueue與CodeletMark

在解釋執行的情況下需要一些類來支援程式碼生成的過程。

1、InterpreterCodelet與Stub類

Stub類的定義如下:

class Stub VALUE_OBJ_CLASS_SPEC {
 public:

  // General info/converters
  int     size() const       { ShouldNotCallThis(); return 0; }      // must return the size provided by initialize

  // Code info
  address code_begin() const { ShouldNotCallThis(); return NULL; }   // points to the first byte of    the code
  address code_end() const   { ShouldNotCallThis(); return NULL; }   // points to the first byte after the code
};

InterpreterCodelet類的定義如下:

class InterpreterCodelet: public Stub {
 private:
  int                _size;         // the size in bytes
  const char*        _description;  // a description of the codelet, for debugging & printing
  Bytecodes::Code    _bytecode;     // associated bytecode if any

 public:

  // Code info
  address code_begin() const  {
     return (address)this + round_to(sizeof(InterpreterCodelet), CodeEntryAlignment);
  }
  address code_end() const {
     return (address)this + size();
  }
  // ...
  int code_size() const { 
     return code_end() - code_begin();  
  }
  // ...
};

定義了3個屬性及一些方法,其記憶體結構如下:在對齊至CodeEntryAlignment後,緊接著InterpreterCodelet的就是生成的目的碼。如下圖所示。

2、StubQueue類

StubQueue是用來儲存生成的原生代碼的Stub佇列,佇列每一個元素對應一個InterpreterCodelet物件,InterpreterCodelet物件繼承自抽象基類Stub,包含了位元組碼對應的原生代碼以及一些除錯和輸出資訊。

在TemplateInterpreter::initialize()方法中會建立StubQueue物件,如下:

原始碼位置:/src/share/vm/interpreter/templateInterpreter.cpp
  
void TemplateInterpreter::initialize() {
  if (_code != NULL) 
       return;
  
  // 抽象直譯器AbstractInterpreter的初始化,AbstractInterpreter是基於彙編模型的直譯器的共同基類,
  // 定義瞭解釋器和直譯器生成器的抽象介面
  AbstractInterpreter::initialize();
  
  // 模板表TemplateTable的初始化,模板表TemplateTable儲存了各個位元組碼的模板
  TemplateTable::initialize();
  
  // generate interpreter
  {
     ResourceMark rm;

     int code_size = InterpreterCodeSize;
     // CodeCache的Stub佇列StubQueue的初始化
     _code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,"Interpreter");

     //  例項化模板直譯器生成器物件TemplateInterpreterGenerator
     InterpreterGenerator g(_code);
  }
  
  // initialize dispatch table
  _active_table = _normal_table;
}

由於TemplateInterpreter繼承自AbstractInterpreter,所以在TemplateInterpreter中初始化的_code屬性其實就是AbstractInterpreter類中定義的_code屬性:

StubQueue* _code 

StubQueue類的定義如下:

class StubQueue: public CHeapObj<mtCode> {
 private:
  StubInterface* _stub_interface;     // the interface prototype
  address        _stub_buffer;        // where all stubs are stored

  int            _buffer_size;       // the buffer size in bytes
  int            _buffer_limit;      // the (byte) index of the actual buffer limit (_buffer_limit <= _buffer_size)

  int            _queue_begin;       // the (byte) index of the first queue entry (word-aligned)
  int            _queue_end;         // the (byte) index of the first entry after the queue (word-aligned)

  int            _number_of_stubs;   // the number of buffered stubs

  Mutex* const   _mutex;             // the lock used for a (request, commit) transaction

  void check_index(int i) const {
	  assert(0 <= i && i < _buffer_limit && i % CodeEntryAlignment == 0, "illegal index");
  }
  bool is_contiguous() const {
	  return _queue_begin <= _queue_end;
  }
  int index_of(Stub* s) const {
	  int i = (address)s - _stub_buffer;
	  check_index(i);
	  return i;
  }
  Stub* stub_at(int i) const {
	  check_index(i);
	  return (Stub*)(_stub_buffer + i);
  }
  Stub* current_stub() const {
	  return stub_at(_queue_end);
  }

  // ...
}

這個類的建構函式如下:

StubQueue::StubQueue(
	 StubInterface* stub_interface,
	 int            buffer_size,
	 Mutex*         lock,
	 const char*    name) : _mutex(lock)
{
  intptr_t     size = round_to(buffer_size, 2*BytesPerWord);
  BufferBlob*  blob = BufferBlob::create(name, size); // 在StubQueue中建立BufferBlob

  _stub_interface  = stub_interface;

  _buffer_size     = blob->content_size();
  _buffer_limit    = blob->content_size();
  _stub_buffer     = blob->content_begin();

  _queue_begin     = 0;
  _queue_end       = 0;
  _number_of_stubs = 0;
  register_queue(this);
}

首先建立一個BufferBlob物件,然後對StubQueue中的屬性進行初始化。呼叫的register_queue()方法的實現如下:

enum {  StubQueueLimit = 10  };  // there are only a few in the world
static StubQueue* registered_stub_queues[StubQueueLimit]; // 長度為10的StubQueue陣列

void StubQueue::register_queue(StubQueue* sq) {
  for (int i = 0; i < StubQueueLimit; i++) {
     if (registered_stub_queues[i] == NULL) {
        registered_stub_queues[i] = sq;
        return;
     }
  }
  ShouldNotReachHere();
}

StubQueue如下:

佇列中的InterpreterCodelet表示一個小例程,比如iconst_1對應的程式碼,invokedynamic對應的程式碼,異常處理對應的程式碼,方法入口點對應的程式碼,這些程式碼都是一個個InterpreterCodelet...整個直譯器都是由這些小塊程式碼例程組成的,每個小塊例程完成直譯器的部分功能,以此實現整個直譯器。

3、CodeletMark類

InterpreterCodelet依賴CodeletMark完成自動創始和初始化。CodeletMark繼承自ResourceMark,允許自動析構,可對臨時分配的程式碼快取空間或彙編器記憶體空間自動回收。這個類的定義如下:

// A CodeletMark serves as an automatic creator/initializer for Codelets
// (As a subclass of ResourceMark it automatically GC's the allocated
// code buffer and assemblers).

class CodeletMark: ResourceMark {
 private:
  InterpreterCodelet*           _clet; // InterpreterCodelet繼承自Stub
  InterpreterMacroAssembler**   _masm;
  CodeBuffer                    _cb;

 public:
  // 建構函式
  CodeletMark(
     InterpreterMacroAssembler*&    masm,
     const char*                    description,
     Bytecodes::Code                bytecode = Bytecodes::_illegal):
	  // AbstractInterpreter::code()獲取的是StubQueue*型別的值,呼叫request()方法獲取的
          // 是Stub*型別的值,呼叫的request()方法實現在vm/code/stubs.cpp檔案中
	  _clet( (InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size()) ),
	  _cb(_clet->code_begin(), _clet->code_size()) 
  {

     // initialize Codelet attributes
     _clet->initialize(description, bytecode);

     // InterpreterMacroAssembler->MacroAssembler->Assembler->AbstractAssembler
     // 通過傳入的cb.insts屬性的值來初始化AbstractAssembler的_code_section與_oop_recorder屬性的值
     // create assembler for code generation
     masm  = new InterpreterMacroAssembler(&_cb); // 在建構函式中,初始化r13指向bcp、r14指向本地區域性變量表
     _masm = &masm;
  }

  // 解構函式
  ~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(), (*_masm)->code()->strings());

      // make sure nobody can use _masm outside a CodeletMark lifespan
      *_masm = NULL;
  }
};

在建構函式中主要完成2個任務:

(1)初始化InterpreterCodelet物件_clet。對InterpreterCodelet物件中的3個屬性賦值。

(2)建立一個InterpreterMacroAssembler並賦值給masm與_masm,此物件會被用來生成程式碼。

通常在程式碼塊結束時會自動呼叫解構函式,在解構函式中完成InterpreterCodelet的提交併清理相關變數的值。

在初始化_clet變數時,呼叫AbstractInterpreter::code()方法返回AbstractInterpreter類的_code屬性的值,這個值在之前TemplateInterpreter::initialize()方法中已經初始化了。繼續呼叫StubQueue類中的request()方法,傳遞的就是要求分配的用來儲存code的大小,通過呼叫codelet_size()方法來獲取,如下:

  int codelet_size() {
    // Request the whole code buffer (minus a little for alignment).
    // The commit call below trims it back for each codelet.
    int codelet_size = AbstractInterpreter::code()->available_space() - 2*K;

    return codelet_size;
  }

request()方法的實現如下:

Stub* StubQueue::request(int requested_code_size) {
  assert(requested_code_size > 0, "requested_code_size must be > 0");

  if (_mutex != NULL){
	  _mutex->lock();
  }

  Stub* s = current_stub();
  int requested_size = round_to(stub_code_size_to_size(requested_code_size), CodeEntryAlignment);
  if (requested_size <= available_space()) {
    if (is_contiguous()) {
      // Queue: |...|XXXXXXX|.............|
      //        ^0  ^begin  ^end          ^size = limit
      assert(_buffer_limit == _buffer_size, "buffer must be fully usable");
      if (_queue_end + requested_size <= _buffer_size) {
         // code fits in at the end => nothing to do
         CodeStrings strings;
         stub_initialize(s, requested_size, strings);
         return s; // 如果夠的話就直接返回
      } else {
         // stub doesn't fit in at the queue end
         // => reduce buffer limit & wrap around
         assert(!is_empty(), "just checkin'");
         _buffer_limit = _queue_end;
         _queue_end = 0;
      }
    }
  }

  if (requested_size <= available_space()) {
    assert(!is_contiguous(), "just checkin'");
    assert(_buffer_limit <= _buffer_size, "queue invariant broken");
    // Queue: |XXX|.......|XXXXXXX|.......|
    //        ^0  ^end    ^begin  ^limit  ^size
    s = current_stub();
    CodeStrings strings;
    stub_initialize(s, requested_size, strings);
    return s;
  }

  // Not enough space left
  if (_mutex != NULL){
	  _mutex->unlock();
  }

  return NULL;
}

呼叫的stub_code_size_to_size()方法的實現如下:

// StubQueue類中的方法
int stub_code_size_to_size(int code_size) const {  
	  return _stub_interface->code_size_to_size(code_size);
}
// InterpreterCodeletInterface類中的方法
virtual int  code_size_to_size(int code_size) const { 
    	return InterpreterCodelet::code_size_to_size(code_size);
}
// InterpreterCodelet類中的方法
static  int code_size_to_size(int code_size) { 
	  // CodeEntryAlignment = 32
	  // sizeof(InterpreterCodelet)  = 32
	  return round_to(sizeof(InterpreterCodelet), CodeEntryAlignment) + code_size;
}

通過如上的分配記憶體大小的方式可知記憶體結構如下:

呼叫的available_space()方法的實現如下:

// StubQueue類中定義的方法
int available_space() const {
	  int d = _queue_begin - _queue_end - 1;
	  return d < 0 ? d + _buffer_size : d;
}

is_contiguous()方法的實現如下:

  bool is_contiguous() const {
	  return _queue_begin <= _queue_end;
  }

呼叫的stub_initialize()方法的實現如下:

// 下面都是通過stubInterface來操作Stub的
// Stub functionality accessed via interface
// 在StubQueue類中定義
void  stub_initialize(Stub* s, int size,CodeStrings& strings)    {
	 assert(size % CodeEntryAlignment == 0, "size not aligned");
	 // 通過_stub_interface來操作Stub,會呼叫s的initialize()方法
	 _stub_interface->initialize(s, size, strings);
}

// 定義在InterpreterCodeletInterface類中
virtual void    initialize(Stub* self, int size,CodeStrings& strings){
    	cast(self)->initialize(size, strings);
}  

// 定義在InterpreterCodelet類中
void initialize(int size,CodeStrings& strings) {
	_size = size;
}

下面來看一下CodeletMark等類的在HotSpot中的具體使用。

在TemplateInterpreter::initialize()方法中初始化InterpreterGenerator物件時,呼叫的建構函式如下:

InterpreterGenerator::InterpreterGenerator(StubQueue* code)
  : TemplateInterpreterGenerator(code) {
    generate_all(); // down here so it can be "virtual"
}

在TemplateInterpreterGenerator::generate_all()方法中的實現非常重要,這個方法生成了許多位元組碼指令以及一些虛擬機器輔助執行的程式碼片段,如下:

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

生成丟擲空指標的程式碼片段。

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()));


  address end =  __ pc();
  Disassembler::decode(entry, end);
  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。

向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、在Ubuntu 16.04上編譯OpenJDK8的原始碼

2、除錯HotSpot原始碼

3、HotSpot專案結構 

4、HotSpot的啟動過程

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)

7、HotSpot的類模型(3)

8、HotSpot的類模型(4)

9、HotSpot的物件模型(5)

10、HotSpot的物件模型(6)

11、操作控制代碼Handle(7)

12、控制代碼Handle的釋放(8)

13、類載入器

14、類的雙親委派機制

15、核心類的預裝載

16、Java主類的裝載

17、觸發類的裝載

18、類檔案介紹

19、檔案流

20、解析Class檔案

21、常量池解析(1)

22、常量池解析(2)

23、欄位解析(1)

24、欄位解析之偽共享(2)

25、欄位解析(3)

26、欄位解析之OopMapBlock(4)

27、方法解析之Method與ConstMethod介紹

28、方法解析

29、klassVtable與klassItable類的介紹

30、計算vtable的大小

31、計算itable的大小

32、解析Class檔案之建立InstanceKlass物件

33、欄位解析之欄位注入

34、類的連線

35、類的連線之驗證

36、類的連線之重寫(1)

37、類的連線之重寫(2)

38、方法的連線

39、初始化vtable

40、初始化itable

41、類的初始化

42、物件的建立

43、Java引用型別

44、Java引用型別之軟引用(1)

45、Java引用型別之軟引用(2)

46、Java引用型別之弱引用與幻像引用

47、Java引用型別之最終引用

48、HotSpot的垃圾回收演算法

49、HotSpot的垃圾回收器

50、CallStub棧幀

51、entry point棧幀

52、generate_fixed_frame()方法生成Java方法棧幀

53、dispatch_next()方法的實現

54、虛擬機器執行模式

55、JVM的方法執行引擎-模板表

作者持續維護的個人部落格classloading.com

關注公眾號,有HotSpot原始碼剖析系列文章!