1. 程式人生 > 實用技巧 >程式碼生成器輔助類StubCodeGenerator與StubCodeMark

程式碼生成器輔助類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 指令)確定。