在 第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數組來保存,這樣就可以使用方法類型做為數組下標獲取對應的方法入口了。
