在前一篇 第3篇-CallStub新棧幀的建立 中我們介紹了generate_call_stub()函式的部分實現,完成了向CallStub棧幀中壓入引數的操作,此時的狀態如下圖所示。

繼續看generate_call_stub()函式的實現,接來下會載入執行緒暫存器,程式碼如下:

// Load up thread register
__ movptr(r15_thread, thread);
__ reinit_heapbase();

生成的彙編程式碼如下:

mov    0x18(%rbp),%r15
mov 0x1764212b(%rip),%r12 # 0x00007fdf5c6428a8

對照著上面的棧幀可看一下0x18(%rbp)這個位置儲存的是thread,將這個引數儲存到%r15暫存器中。

如果在呼叫函式時有引數的話需要傳遞引數,程式碼如下:

// pass parameters if any
BLOCK_COMMENT("pass parameters if any"); // _masm-> block_comment("pass parameters if any")
Label parameters_done;
// parameter_size拷貝到c_rarg3即rcx暫存器中
__ movl(c_rarg3, parameter_size);
// 校驗c_rarg3的數值是否合法。兩運算元作與運算,僅修改標誌位,不回送結果
__ testl(c_rarg3, c_rarg3);
// 如果不合法則跳轉到parameters_done分支上
__ jcc(Assembler::zero, parameters_done); // 如果執行下面的邏輯,那麼就表示parameter_size的值不為0,也就是需要為
// 呼叫的java方法提供引數
Label loop;
// 將地址parameters包含的資料即引數物件的指標拷貝到c_rarg2暫存器中
__ movptr(c_rarg2, parameters); // parameter pointer rdx
// 將c_rarg3中值拷貝到c_rarg1中,即將引數個數複製到c_rarg1中
__ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1
__ BIND(loop);
// 將c_rarg2指向的記憶體中包含的地址複製到rax中
__ movptr(rax, Address(c_rarg2, 0));// get parameter
// c_rarg2中的引數物件的指標加上指標寬度8位元組,即指向下一個引數
__ addptr(c_rarg2, wordSize); // advance to next parameter
// 將c_rarg1中的值減一
__ decrementl(c_rarg1); // decrement counter
// 傳遞方法呼叫引數
__ push(rax); // pass parameter
// 如果引數個數大於0則跳轉到loop繼續
__ jcc(Assembler::notZero, loop);

這裡是個迴圈,用於傳遞引數,相當於如下程式碼:

while(%esi){
rax = *arg
push_arg(rax)
arg++; // ptr++
%esi--; // counter--
}

生成的彙編程式碼如下:

mov    0x10(%rbp),%ecx    // 將棧中parameter size送到%ecx中
test %ecx,%ecx // 做與運算,只有當%ecx中的值為0時才等於0
je 0x00007fdf4500079a // 沒有引數需要傳遞,直接跳轉到parameters_done即可
// -- loop --
// 彙編執行到這裡,說明paramter size不為0,需要傳遞引數
mov -0x8(%rbp),%rdx
mov %ecx,%esi
mov (%rdx),%rax
add $0x8,%rdx
dec %esi
push %rax
jne 0x00007fdf4500078e // 跳轉到loop

因為要呼叫Java方法,所以會為Java方法壓入實際的引數,也就是壓入parameter size個從parameters開始取的引數。壓入引數後的棧如下圖所示。

當把需要呼叫Java方法的引數準備就緒後,接下來就會呼叫Java方法。這裡需要重點提示一下Java解釋執行時的方法呼叫約定,不像C/C++在x86下的呼叫約定一樣,不需要通過暫存器來傳遞引數,而是通過棧來傳遞引數的,說的更直白一些,是通過區域性變量表來傳遞引數的,所以上圖CallStub()函式棧幀中的argument word1 ... argument word n其實是​被呼叫的Java方法區域性變量表的一部分。

下面接著看呼叫Java方法的程式碼,如下:

// 呼叫Java方法
// -- parameters_done -- __ BIND(parameters_done); // bind(parameters_done); _masm-> block_comment("parameters_done" ":")
// 將method地址包含的資料接Method*拷貝到rbx中
__ movptr(rbx, method); // get Method*
// 將直譯器的入口地址拷貝到c_rarg1暫存器中
__ movptr(c_rarg1, entry_point); // get entry_point
// 將rsp暫存器的資料拷貝到r13暫存器中
__ mov(r13, rsp); // set sender sp
BLOCK_COMMENT("call Java function"); // _masm-> block_comment("call Java function")
// 呼叫直譯器的解釋函式,從而呼叫Java方法
__ call(c_rarg1); // 呼叫的時候傳遞c_rarg1,也就是直譯器的入口地址

生成的彙編程式碼如下:

mov     -0x18(%rbp),%rbx  // 將Method*送到%rbx中
mov -0x10(%rbp),%rsi // 將entry_point送到%rsi中
mov %rsp,%r13 // 將呼叫者的棧頂指標儲存到%r13中
callq *%rsi // 呼叫Java方法

注意呼叫callq指令後,會將callq指令的下一條指令的地址壓棧,再跳轉到第1運算元指定的地址,也就是*%rsi表示的地址。壓入下一條指令的地址是為了讓函式能通過跳轉到棧上的地址從子函式返回。 

callq指令呼叫的是entry point。entry point在後面會詳細介紹。

推薦閱讀:

第1篇-關於JVM執行時,開篇說的簡單些

第2篇-JVM虛擬機器這樣來呼叫Java主類的main()方法

第3篇-CallStub新棧幀的建立

如果有問題可直接評論留言或加作者微信mazhimazh

關注公眾號,有HotSpot原始碼剖析系列文章!