程式碼生成器輔助類Stub、StubQueue與CodeletMark
- 2020 年 8 月 21 日
- 筆記
在解釋執行的情況下需要一些類來支援程式碼生成的過程。
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的源程式碼
13、類載入器
14、類的雙親委派機制
15、核心類的預裝載
16、Java主類的裝載
17、觸發類的裝載
18、類文件介紹
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、欄位解析(1)
24、欄位解析之偽共享(2)
25、欄位解析(3)
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引用類型
50、CallStub棧幀
52、generate_fixed_frame()方法生成Java方法棧幀
54、虛擬機執行模式
作者持續維護的個人部落格 classloading.com。
關注公眾號,有HotSpot源碼剖析系列文章!