第7篇-為Java方法創建棧幀


在 第6篇-Java方法新棧幀的創建 介紹過局部變量表的創建,創建完成后的棧幀狀態如下圖所示。

各個寄存器的狀態如下所示。

// %rax寄存器中存儲的是返回地址
rax: return address     
// 要執行的Java方法的指針
rbx: Method*          
// 本地變量表指針  
r14: pointer to locals 
// 調用者的棧頂
r13: sender sp 

注意rax中保存的返回地址,因為在generate_call_stub()函數中通過__ call(c_rarg1) 語句調用了由generate_normal_entry()函數生成的entry_point,所以當entry_point執行完成后,還會返回到generate_call_stub()函數中繼續執行__ call(c_rarg1) 語句下面的代碼,也就是

第5篇-調用Java方法后彈出棧幀及處理返回結果 涉及到的那些代碼。

調用的generate_fixed_frame()函數的實現如下:

源代碼位置:openjdk/hotspot/src/cpu/x86/vm/templateInterpreter_x86_64.cpp
 
void TemplateInterpreterGenerator::generate_fixed_frame(bool native_call) {
  // 把返回地址緊接着局部變量區保存
  __ push(rax);     
  // 為Java方法創建棧幀       
  __ enter();      
  // 保存調用者的棧頂地址        
  __ push(r13);           
   // 暫時將last_sp屬性的值設置為NULL_WORD 
  __ push((int)NULL_WORD); 
  // 獲取ConstMethod*並保存到r13中
  __ movptr(r13, Address(rbx, Method::const_offset()));     
  // 保存Java方法字節碼的地址到r13中
  __ lea(r13, Address(r13, ConstMethod::codes_offset()));    
  // 保存Method*到堆棧上
  __ push(rbx);             
 
  // ProfileInterpreter屬性的默認值為true,
  // 表示需要對解釋執行的方法進行相關信息的統計
  if (ProfileInterpreter) {
    Label method_data_continue;
    // MethodData結構基礎是ProfileData,
    // 記錄函數運行狀態下的數據
    // MethodData里面分為3個部分,
    // 一個是函數類型等運行相關統計數據,
    // 一個是參數類型運行相關統計數據,
    // 還有一個是extra擴展區保存着
    // deoptimization的相關信息
    // 獲取Method中的_method_data屬性的值並保存到rdx中
    __ movptr(rdx, Address(rbx,
           in_bytes(Method::method_data_offset())));
    __ testptr(rdx, rdx);
    __ jcc(Assembler::zero, method_data_continue);
    // 執行到這里,說明_method_data已經進行了初始化,
    // 通過MethodData來獲取_data屬性的值並存儲到rdx中
    __ addptr(rdx, in_bytes(MethodData::data_offset()));
    __ bind(method_data_continue);
    __ push(rdx);      
  } else {
    __ push(0);
  }
  
  // 獲取ConstMethod*存儲到rdx
  __ movptr(rdx, Address(rbx, 
        Method::const_offset()));          
  // 獲取ConstantPool*存儲到rdx
  __ movptr(rdx, Address(rdx, 
         ConstMethod::constants_offset())); 
 // 獲取ConstantPoolCache*並存儲到rdx
  __ movptr(rdx, Address(rdx, 
         ConstantPool::cache_offset_in_bytes())); 
  // 保存ConstantPoolCache*到堆棧上
  __ push(rdx); 
  // 保存第1個參數的地址到堆棧上
  __ push(r14); 
 
  if (native_call) {
   // native方法調用時,不需要保存Java
   // 方法的字節碼地址,因為沒有字節碼
    __ push(0); 
  } else {
   // 保存Java方法字節碼地址到堆棧上,
   // 注意上面對r13寄存器的值進行了更改
    __ push(r13);
  }
  
  // 預先保留一個slot,后面有大用處
  __ push(0); 
  // 將棧底地址保存到這個slot上
  __ movptr(Address(rsp, 0), rsp); 
}

對於普通的Java方法來說,生成的匯編代碼如下:  

push   %rax
push   %rbp
mov    %rsp,%rbp
push   %r13
pushq  $0x0
mov    0x10(%rbx),%r13
lea    0x30(%r13),%r13 // lea指令獲取內存地址本身
push   %rbx
mov    0x18(%rbx),%rdx
test   %rdx,%rdx
je     0x00007fffed01b27d
add    $0x90,%rdx
push   %rdx
mov    0x10(%rbx),%rdx
mov    0x8(%rdx),%rdx
mov    0x18(%rdx),%rdx
push   %rdx
push   %r14
push   %r13
pushq  $0x0
mov    %rsp,(%rsp)

匯編比較簡單,這里不再多說。執行完如上的匯編后生成的棧幀狀態如下圖所示。

調用完generate_fixed_frame()函數后一些寄存器中保存的值如下:

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

執行完generate_fixed_frame()函數后會繼續返回執行InterpreterGenerator::generate_normal_entry()函數,如果是為同步方法生成機器碼,那么還需要調用lock_method()函數,這個函數會改變當前棧幀的狀態,添加同步所需要的一些信息,在后面介紹鎖的實現時會詳細介紹。

InterpreterGenerator::generate_normal_entry()函數最終會返回生成機器碼的入口執行地址,然后通過變量_entry_table數組來保存,這樣就可以使用方法類型做為數組下標獲取對應的方法入口了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM