天天看點

第37篇-Interpreter::_invoke_return_entry等例程

我們在之前介紹過return位元組碼指令的執行邏輯,這個位元組碼指令隻會執行釋放鎖和退出目前棧幀的操作,但是當控制權轉移給調用者時,還需要恢複調用者的棧幀狀态,如讓%r13指向bcp、%r14指向局部變量表等,另外還需要彈出壓入的實參、跳轉到調用者的下一個位元組碼指令繼續執行,而這一切操作都是由Interpreter::_return_entry例程負責的。這個例程在之前介紹invokevirtual和invokeinterface等位元組碼指令時介紹過,當使用這些位元組碼指令調用方法時,會根據方法的傳回類型壓入Interpreter::_return_entry一維數組中儲存的對應例程位址,這樣return位元組碼指令執行完成後就會執行這段例程。

在invokevirtual和invokeinterface等位元組碼指令中通過調用如下函數擷取對應的例程入口:

address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {
  switch (code) {
  case Bytecodes::_invokestatic:
  case Bytecodes::_invokespecial:
  case Bytecodes::_invokevirtual:
  case Bytecodes::_invokehandle:
    return Interpreter::invoke_return_entry_table();
  case Bytecodes::_invokeinterface:
    return Interpreter::invokeinterface_return_entry_table();
  default:
    fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
    return NULL;
  }
}      

可以看到invokeinterface位元組碼從Interpreter::_invokeinterface_return_entry數組中擷取對應的例程,而其它的從Interpreter::_invoke_return_entry一維數組中擷取。如下:

address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokedynamic_return_entry[TemplateInterpreter::number_of_return_addrs];      

當傳回一維數組後,會根據方法傳回類型進一步确定例程入口位址。下面我們就看一下這些例程的生成過程。 

TemplateInterpreterGenerator::generate_all()函數中會生成Interpreter::_return_entry入口,如下:

{
    CodeletMark cm(_masm, "invoke return entry points");
    const TosState states[]           = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
    const int invoke_length           = Bytecodes::length_for(Bytecodes::_invokestatic);     // invoke_length=3
    const int invokeinterface_length  = Bytecodes::length_for(Bytecodes::_invokeinterface);  // invokeinterface=5

    for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
       TosState state = states[i]; // TosState是枚舉類型
       Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2)); 
       Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));
    }
  }      

除invokedynamic位元組碼指令外,其它的方法調用指令在解釋執行完成後都需要調用由generate_return_entry_for()函數生成的例程,生成例程的generate_return_entry_for()函數實作如下:

address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {

  // Restore stack bottom in case萬一 i2c adjusted stack
  __ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
  // and NULL it as marker that esp is now tos until next java call
  __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);

  __ restore_bcp();
  __ restore_locals();

  // ...

  const Register cache = rbx;
  const Register index = rcx;
  __ get_cache_and_index_at_bcp(cache, index, 1, index_size);

  const Register flags = cache;
  __ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
  __ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
  __ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale())   ); // 棧元素标量為8
  __ dispatch_next(state, step);

  return entry;
}      

根據state的不同(方法的傳回類型的不同),會在選擇執行調用者方法的下一個位元組碼指令時,決定要從位元組碼指令的哪個入口處開始執行。我們看一下,當傳遞的state為itos(也就是當方法的傳回類型為int時)時生成的彙編代碼如下:

// 将-0x10(%rbp)存儲到%rsp後,置空-0x10(%rbp)
0x00007fffe1006ce0: mov    -0x10(%rbp),%rsp   // 更改rsp
0x00007fffe1006ce4: movq   $0x0,-0x10(%rbp)   // 更改棧中特定位置的值
// 恢複bcp和locals,使%r14指向本地變量表,%r13指向bcp
0x00007fffe1006cec: mov    -0x38(%rbp),%r13
0x00007fffe1006cf0: mov    -0x30(%rbp),%r14
 // 擷取ConstantPoolCacheEntry的索引并加載到%ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx     

 // 擷取棧中-0x28(%rbp)的ConstantPoolCache并加載到%ecx
0x00007fffe1006cf9: mov    -0x28(%rbp),%rbx   
// shl是邏輯左移,擷取字偏移
0x00007fffe1006cfd: shl    $0x2,%ecx           
// 擷取ConstantPoolCacheEntry中的_flags屬性值
0x00007fffe1006d00: mov    0x28(%rbx,%rcx,8),%ebx
// 擷取_flags中的低8位中儲存的參數大小
0x00007fffe1006d04: and    $0xff,%ebx          

// lea指令将位址加載到記憶體寄存器中,也就是恢複調用方法之前棧的樣子
0x00007fffe1006d0a: lea    (%rsp,%rbx,8),%rsp  

// 跳轉到下一指令執行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx  
0x00007fffe1006d13: add    $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq   *(%r10,%rbx,8)      

如上彙編代碼的邏輯非常簡單,這裡不再過多介紹。  

公衆号 深入剖析Java虛拟機HotSpot 已經更新虛拟機源代碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛拟機群交流