天天看點

第8篇-dispatch_next()函數分派位元組碼

在generate_normal_entry()函數中會調用generate_fixed_frame()函數為Java方法的執行生成對應的棧幀,接下來還會調用dispatch_next()函數執行Java方法的位元組碼。generate_normal_entry()函數調用的dispatch_next()函數之前一些寄存器中儲存的值如下:

rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的位址
r14:本地變量表第1個參數的位址           

dispatch_next()函數的實作如下:

// 從generate_fixed_frame()函數生成Java方法調用棧幀的時候,
// 如果目前是第一次調用,那麼r13指向的是位元組碼的首位址,
// 即第一個位元組碼,此時的step參數為0
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {

  load_unsigned_byte(rbx, Address(r13, step)); 

  // 在目前位元組碼的位置,指針向前移動step寬度,
  // 擷取位址上的值,這個值是Opcode(範圍1~202),存儲到rbx
  // step的值由位元組碼指令和它的操作數共同決定
  // 自增r13供下一次位元組碼分派使用
  increment(r13, step);

  // 傳回目前棧頂狀态的所有位元組碼入口點
  dispatch_base(state, Interpreter::dispatch_table(state)); 
}           

r13指向位元組碼的首位址,當第1次調用時,參數step的值為0,那麼load_unsigned_byte()函數從r13指向的記憶體中取一個位元組的值,取出來的是位元組碼指令的操作碼。增加r13的步長,這樣下次執行時就會取出來下一個位元組碼指令的操作碼。

調用的dispatch_table()函數的實作如下:

static address*   dispatch_table(TosState state)  {
   return _active_table.table_for(state); 
}           

在_active_table中擷取對應棧頂緩存狀态的入口位址,_active_table變量定義在TemplateInterpreter類中,如下:

static DispatchTable  _active_table;             

DispatchTable類及table_for()等函數的定義如下:

DispatchTable  TemplateInterpreter::_active_table;

class DispatchTable VALUE_OBJ_CLASS_SPEC {
 public:
  enum { 
    length = 1 << BitsPerByte 
  }; // BitsPerByte的值為8

 private:
  // number_of_states=9,length=256
  // _table是位元組碼分發表 
  address  _table[number_of_states][length];   

 public:
  // ...
  address*   table_for(TosState state){ 
    return _table[state]; 
  }

  address*   table_for(){ 
    return table_for((TosState)0); 
  }
  // ...
};            

address為u_char*類型的别名。_table是一個二維數組的表,次元為棧頂狀态(共有9種)和位元組碼(最多有256個),存儲的是每個棧頂狀态對應的位元組碼的入口點。這裡由于還沒有介紹棧頂緩存,是以了解起來并不容易,不過後面會詳細介紹棧頂緩存和位元組碼分發表的相關内容,等介紹完了再看這部分邏輯就比較容易了解了。

InterpreterMacroAssembler::dispatch_next()函數中調用的dispatch_base()函數的實作如下:

void InterpreterMacroAssembler::dispatch_base(
  TosState  state, // 表示棧頂緩存狀态
  address*  table,
  bool verifyoop
) {
  // ...
  // 擷取目前棧頂狀态位元組碼轉發表的位址,儲存到rscratch1
  lea(rscratch1, ExternalAddress((address)table));
  // 跳轉到位元組碼對應的入口執行機器碼指令
  // address = rscratch1 + rbx * 8
  jmp(Address(rscratch1, rbx, Address::times_8));
}            

比如取一個位元組大小的指令(如iconst_0、aload_0等都是一個位元組大小的指令),那麼InterpreterMacroAssembler::dispatch_next()函數生成的彙編代碼如下 :

// 在generate_fixed_frame()函數中
// 已經讓%r13存儲了bcp
// %ebx中存儲的是位元組碼的Opcode,也就是操作碼
movzbl 0x0(%r13),%ebx  

// $0x7ffff73ba4a0這個位址指向的
// 是對應state狀态下的一維數組,長度為256
movabs $0x7ffff73ba4a0,%r10

// 注意%r10中存儲的是常量,根據計算公式
// %r10+%rbx*8來擷取指向存儲入口位址的位址,
// 通過*(%r10+%rbx*8)擷取到入口位址,
// 然後跳轉到入口位址執行
jmpq *(%r10,%rbx,8)           

%r10指向的是對應棧頂緩存狀态state下的一維數組,長度為256,其中存儲的值為opcode,如下圖所示。

第8篇-dispatch_next()函數分派位元組碼

下面的函數顯示了對每個位元組碼的每個棧頂狀态都設定入口位址。

void DispatchTable::set_entry(int i, EntryPoint& entry) {
  assert(0 <= i && i < length, "index out of bounds");
  assert(number_of_states == 9, "check the code below");
  _table[btos][i] = entry.entry(btos);
  _table[ctos][i] = entry.entry(ctos);
  _table[stos][i] = entry.entry(stos);
  _table[atos][i] = entry.entry(atos);
  _table[itos][i] = entry.entry(itos);
  _table[ltos][i] = entry.entry(ltos);
  _table[ftos][i] = entry.entry(ftos);
  _table[dtos][i] = entry.entry(dtos);
  _table[vtos][i] = entry.entry(vtos);
}           

其中的參數i就是opcode,各個位元組碼及對應的opcode可參考https://docs.oracle.com/javase/specs/jvms/se8/html/index.html。

是以_table表如下圖所示。

第8篇-dispatch_next()函數分派位元組碼

_table的一維為棧頂緩存狀态,二維為Opcode,通過這2個次元能夠找到一段機器指令,這就是根據目前的棧頂緩存狀态定位到的位元組碼需要執行的機器指令片段。