程式碼生成器輔助類StubCodeGenerator與StubCodeMark
StudCodeGenerator類的繼承體系如下:
主要看子類ICacheStubGenerator與StubGenerator。
1、StubGenerator
StubGenerator繼承自StubCodeGenerator。
StubGenerator顧名思義就是用來生成Stub的,這裡的Stub實際是一段可執行的彙編程式碼,具體來說就是生成StubRoutines中定義的多個public static的函式呼叫點,呼叫方可以將其作為一個經過優化後的函式直接使用。
void StubRoutines::initialize1() { if (_code1 == NULL) { // ResourceMark的作用類似於HandleMark,兩者mark的區域不同,一個是ResourceArea,一個是HandleArea ResourceMark rm; // 建立一個儲存不會重定位的原生代碼的Blob _code1 = BufferBlob::create("StubRoutines (1)", code_size1); CodeBuffer buffer(_code1); // 生成位元組碼解釋模板 StubGenerator_generate(&buffer, false); } }
這裡涉及到一個2個非常重要的類BufferBlob與CodeBuffer,生成的所有可執行機器碼片段Stub都是經過CodeBuffer向BufferBlob中寫入的,所以BufferBlob是最終儲存程式碼片段的地方,在後面將詳細介紹程式碼快取及程式碼儲存相關的類。
呼叫的StubGenerator_generate()函式的實現如下:
void StubGenerator_generate(CodeBuffer* code, bool all) { StubGenerator g(code, all); } // 呼叫StubGenerator的建構函式 StubGenerator(CodeBuffer* code, bool all) : StubCodeGenerator(code) { // generate_initial和generate_all兩個方法都是給StubRoutines中的static public的函式呼叫地址賦值,即生成stub if (all) { generate_all(); } else { generate_initial(); // 如果傳入false執行的是initial相關的程式碼 } } // 呼叫StubCodeGenerator的建構函式 StubCodeGenerator::StubCodeGenerator(CodeBuffer* code, bool print_code) { // 構造一個新的MacroAssembler例項 _masm = new MacroAssembler(code); _first_stub = _last_stub = NULL; _print_code = print_code; }
呼叫的generate_initial()函式將生成StubRoutines中定義的多個public static的函式呼叫點。
StubCodeGenerator類的定義如下:
// The base class for all stub-generating code generators. // Provides utility functions. class StubCodeGenerator: public StackObj { protected: MacroAssembler* _masm; // 用來生成彙編程式碼 StubCodeDesc* _first_stub; StubCodeDesc* _last_stub; bool _print_code; // ... };
這個類中有個非常重要的屬性_masm,型別為MacroAssembler*。MacroAssembler是生成機器碼的地方,相關類中提供了許多機器碼生成相關的方法。在創始MacroAssembler物件時傳入了CodeBuffer物件,所以會將生成的機器碼通過CodeBuffer寫入BufferBlob中。
呼叫的generate_initial()函式的實現如下:
// Initialization void generate_initial() { // Generates all stubs and initializes the entry points // This platform-specific settings are needed by generate_call_stub() create_control_words(); // entry points that exist in all platforms Note: This is code // that could be shared among different platforms - however the // benefit seems to be smaller than the disadvantage of having a // much more complicated generator structure. See also comment in // stubRoutines.hpp. StubRoutines::_forward_exception_entry = generate_forward_exception(); StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address); // is referenced by megamorphic call StubRoutines::_catch_exception_entry = generate_catch_exception(); // atomic calls StubRoutines::_atomic_xchg_entry = generate_atomic_xchg(); StubRoutines::_atomic_xchg_ptr_entry = generate_atomic_xchg_ptr(); StubRoutines::_atomic_cmpxchg_entry = generate_atomic_cmpxchg(); StubRoutines::_atomic_cmpxchg_long_entry = generate_atomic_cmpxchg_long(); StubRoutines::_atomic_add_entry = generate_atomic_add(); StubRoutines::_atomic_add_ptr_entry = generate_atomic_add_ptr(); StubRoutines::_fence_entry = generate_orderaccess_fence(); StubRoutines::_handler_for_unsafe_access_entry = generate_handler_for_unsafe_access(); // platform dependent StubRoutines::x86::_get_previous_fp_entry = generate_get_previous_fp(); StubRoutines::x86::_get_previous_sp_entry = generate_get_previous_sp(); StubRoutines::x86::_verify_mxcsr_entry = generate_verify_mxcsr(); // Build this early so it's available for the interpreter. StubRoutines::_throw_StackOverflowError_entry = generate_throw_exception( "StackOverflowError throw_exception", CAST_FROM_FN_PTR(address, SharedRuntime:: throw_StackOverflowError)); if (UseCRC32Intrinsics) { // set table address before stub generation which use it StubRoutines::_crc_table_adr = (address)StubRoutines::x86::_crc_table; StubRoutines::_updateBytesCRC32 = generate_updateBytesCRC32(); } }
可以看到對_call_stub_entry等的初始化,_call_stub_entry初始化呼叫的generate_call_stub()函式在之前已經詳細介紹過,這裡不再介紹。還有許多的Stub,這裡暫時不介紹,後面如果有涉及會詳細介紹。這裡需要重點理解生成的程式碼如何儲存到之前介紹的Stub佇列中的。例如generate_forward_exception()函式中有如下呼叫:
address start = __ pc();
就是Stub程式碼的入口地址。呼叫的是AbstraceAssembler類中的pc()方法,如下:
address pc() const { return code_section()->end(); }
後面就會呼叫movptr()等各種方法將機器碼寫入AbstractAssembler類的_code_section中,也就是寫入InterpreterCodelet物件中,如下圖所示。
2、StubCodeDesc
在StubCodeMark中定義的_cdesc屬性的型別為StubCodeDesc類。StubCodeDesc用來描述一段生成的Stub,StubCodeDesc儲存的資訊通常用於除錯和列印日誌。目前所有的StubCodeDesc都是鏈式儲存的,如果查詢比較慢就可能會改變。StubCodeDesc類的定義如下:
// A StubCodeDesc describes a piece of generated code (usually stubs). // This information is mainly useful for debugging and printing. // Currently, code descriptors are simply chained in a linked list, // this may have to change if searching becomes too slow. class StubCodeDesc: public CHeapObj<mtCode> { protected: static StubCodeDesc* _list; // the list of all descriptors static int _count; // length of list StubCodeDesc* _next; // the next element in the linked list const char* _group; // the group to which the stub code belongs const char* _name; // the name assigned to the stub code int _index; // serial number assigned to the stub address _begin; // points to the first byte of the stub code (included) address _end; // points to the first byte after the stub code (excluded) // ... public: StubCodeDesc(const char* group, const char* name, address begin) { assert(name != NULL, "no name specified"); //_list相當於連結串列頭的StubCodeDesc指標,每建立一個新的StubCodeDesc例項則插入到連結串列的頭部 // 將原來的頭部例項作為當前例項的的_next _next = _list; _group = group; _name = name; _index = ++_count; // (never zero) _begin = begin; _end = NULL; _list = this; }; // ... };
3、StubCodeMark
在generate_initial()函式中呼叫的generate_forward_exception()、generate_call_stub()等函式開始時會建立一個StubCodeMark物件,在函式返回時會呼叫這個物件的解構函式釋放相關資源。例如:
StubCodeMark mark(this, "StubRoutines", "forward exception");
StubCodeMark類的定義如下:
// Stack-allocated helper class used to assciate a stub code with a name. // All stub code generating functions that use a StubCodeMark will be registered // in the global StubCodeDesc list and the generated stub code can be identified // later via an address pointing into it. // StubCodeMark是一個工具類,用於將一個生成的stub同其名稱關聯起來,StubCodeMark會給當前stub // 建立一個新的StubCodeDesc例項,並將其註冊到全域性的StubCodeDesc連結串列中,stub可以通過地址查詢到 // 對應的StubCodeDesc例項。 class StubCodeMark: public StackObj { protected: StubCodeGenerator* _cgen; StubCodeDesc* _cdesc; // ... };
建構函式與解構函式如下:
// Implementation of CodeMark StubCodeMark::StubCodeMark(StubCodeGenerator* cgen, const char* group, const char* name) { _cgen = cgen; // _cgen->assembler()->pc()返回的是StubCodeDesc的start屬性,即stub code的起始地址 MacroAssembler* ma = _cgen->assembler(); _cdesc = new StubCodeDesc(group, name, ma->pc()); _cgen->stub_prolog(_cdesc); // define the stub's beginning (= entry point) to be after the prolog: // 重置stub code的起始地址,避免stub_prolog中改變了起始地址 _cdesc->set_begin(ma->pc()); } StubCodeMark::~StubCodeMark() { // flush方法將生成的彙編程式碼寫入到CodeBuffer中 _cgen->assembler()->flush(); // 設定end屬性 _cdesc->set_end(_cgen->assembler()->pc()); // 校驗當前StubCodeDesc處於連結串列頭部,即在StubCodeMark構造完成到析構前沒有建立一個新的StubCodeDesc例項 assert(StubCodeDesc::_list == _cdesc, "expected order on list"); _cgen->stub_epilog(_cdesc); // 將生成的stub註冊到作業系統中,相當於作業系統載入了某個函式的實現到當前程序的程式碼區 Forte::register_stub(_cdesc->name(), _cdesc->begin(), _cdesc->end()); }
StubCodeDesc用來描述一段生成的Stub,StubCodeDesc儲存的資訊通常用於除錯和列印日誌。
在建構函式中呼叫的stub_prolog()函式是個空實現。
在解構函式中呼叫的stub_epilog()函式的實現如下:
void StubCodeGenerator::stub_epilog(StubCodeDesc* cdesc) { // default implementation - record the cdesc if (_first_stub == NULL) { _first_stub = cdesc; } _last_stub = cdesc; }
在解構函式中呼叫的AbstractAssembler類的flush()函式的實現如下:
void AbstractAssembler::flush() { address pos = addr_at(0); int offst = offset(); ICache::invalidate_range(pos,offst ); } // Code emission & accessing address addr_at(int pos) const { return code_section()->start() + pos; } int offset() const { return code_section()->size(); } csize_t size() const { return (csize_t)(_end - _start); }
呼叫的invalidate_range()函式的實現如下:
void AbstractICache::invalidate_range(address start, int nbytes) { static bool firstTime = true; if (firstTime) { guarantee(start == CAST_FROM_FN_PTR(address, _flush_icache_stub),"first flush should be for flush stub"); firstTime = false; return; } if (nbytes == 0) { return; } // Align start address to an icache line boundary and transform // nbytes to an icache line count. const uint line_offset = mask_address_bits(start, ICache::line_size-1); if (line_offset != 0) { start -= line_offset; nbytes += line_offset; } intptr_t temp = round_to(nbytes, ICache::line_size); int lines = temp >> ICache::log2_line_size; call_flush_stub(start, lines); } void AbstractICache::call_flush_stub(address start, int lines) { // The business with the magic number is just a little security. // We cannot call the flush stub when generating the flush stub // because it isn't there yet. So, the stub also returns its third // parameter. This is a cheap check that the stub was really executed. static int magic = 0xbaadbabe; int auto_magic = magic; // Make a local copy to avoid race condition int r = (*_flush_icache_stub)(start, lines, auto_magic); guarantee(r == auto_magic, "flush stub routine did not execute"); ++magic; }
_flush_icache_stub是函式指標,在ICacheStubGenerator類中的ICacheStubGenerator::generate_icache_flush()函式初始化。
4、ICacheStubGenerator
呼叫ICacheStubGenerator::generate_icache_flush()函式的呼叫棧如下所示。
ICacheStubGenerator::generate_icache_flush() icache_x86.cpp AbstractICache::initialize() icache.cpp icache_init() icache.cpp CodeCache::initialize() codeCache.cpp codeCache_init() codeCache.cpp init_globals() init.cpp
ICacheStubGenerator類的定義如下:
class ICacheStubGenerator : public StubCodeGenerator { public: ICacheStubGenerator(CodeBuffer *c) : StubCodeGenerator(c) {} // Generate the icache flush stub. // // Since we cannot flush the cache when this stub is generated, // it must be generated first, and just to be sure, we do extra // work to allow a check that these instructions got executed. // // The flush stub has three parameters (see flush_icache_stub_t). // // addr - Start address, must be aligned at log2_line_size // lines - Number of line_size icache lines to flush // magic - Magic number copied to result register to make sure // the stub executed properly // // A template for generate_icache_flush is // // #define __ _masm-> // // void ICacheStubGenerator::generate_icache_flush( // ICache::flush_icache_stub_t* flush_icache_stub // ) { // StubCodeMark mark(this, "ICache", "flush_icache_stub"); // // address start = __ pc(); // // // emit flush stub asm code // // // Must be set here so StubCodeMark destructor can call the flush stub. // *flush_icache_stub = (ICache::flush_icache_stub_t)start; // }; // // #undef __ // // The first use of flush_icache_stub must apply it to itself. The // StubCodeMark destructor in generate_icache_flush will call Assembler::flush, // which in turn will call invalidate_range (see asm/assembler.cpp), which // in turn will call the flush stub *before* generate_icache_flush returns. // The usual method of having generate_icache_flush return the address of the // stub to its caller, which would then, e.g., store that address in // flush_icache_stub, won't work. generate_icache_flush must itself set // flush_icache_stub to the address of the stub it generates before // the StubCodeMark destructor is invoked. void generate_icache_flush(ICache::flush_icache_stub_t* flush_icache_stub); };
generate_icache_flush()函式的實現如下:
void ICacheStubGenerator::generate_icache_flush(ICache::flush_icache_stub_t* flush_icache_stub) { StubCodeMark mark(this, "ICache", "flush_icache_stub"); address start = __ pc(); const Register addr = c_rarg0; const Register lines = c_rarg1; const Register magic = c_rarg2; Label flush_line, done; __ testl(lines, lines); __ jcc(Assembler::zero, done); // Force ordering wrt cflush. // Other fence and sync instructions won't do the job. __ mfence(); __ bind(flush_line); __ clflush(Address(addr, 0)); __ addptr(addr, ICache::line_size); __ decrementl(lines); __ jcc(Assembler::notZero, flush_line); __ mfence(); __ bind(done); __ movptr(rax, magic); // Handshake with caller to make sure it happened! __ ret(0); // Must be set here so StubCodeMark destructor can call the flush stub. *flush_icache_stub = (ICache::flush_icache_stub_t)start; }
生成的彙編程式碼如下:
0x00007fffe1000060: test %esi,%esi 0x00007fffe1000062: je 0x00007fffe1000079 // 當lines為0時,直接跳轉到done 0x00007fffe1000068: mfence // -- flush_line -- 0x00007fffe100006b: clflush (%rdi) 0x00007fffe100006e: add $0x40,%rdi // 加一個line_size,值為64 0x00007fffe1000072: dec %esi 0x00007fffe1000074: jne 0x00007fffe100006b // 如果lines不為0,則跳轉到flush_line 0x00007fffe1000076: mfence // -- done --
// Handshake with caller to make sure it happened! 0x00007fffe1000079: mov %rdx,%rax 0x00007fffe100007c: retq
其中的clflush指令說明如下:
clflush--- Flushes and invalidates a memory operand and its associated cache line from all levels of the processor's cache hierarchy
在處理器快取層次結構(資料與指令)的所有級別中,使包含源運算元指定的線性地址的快取線失效。失效會在整個快取一致性域中傳播。如果快取層次結構中任何級別的快取線與記憶體不一致(汙損),則在使之失效之前將它寫入記憶體。源運算元是位元組記憶體位置。
mfence可以序列化載入與儲存操作。
對 MFENCE 指令之前發出的所有載入與儲存指令執行序列化操作。此序列化操作確保:在全域性範圍內看到 MFENCE 指令後面(按程式順序)的任何載入與儲存指令之前,可以在全域性範圍內看到 MFENCE 指令前面的每一條載入與儲存指令。MFENCE 指令的順序根據所有的載入與儲存指令、其它 MFENCE 指令、任何 SFENCE 與 LFENCE 指令以及任何序列化指令(如 CPUID 指令)確定。