メソッド呼び出し処理では以下のような作業が必要となる.
呼び出し先のアドレスの取得
レジスタの退避/復帰, スタックフレームの確保/破棄
リターンアドレスの設定, 呼び出し先へのジャンプ, 呼び出し先からのリターン
その他, 実行に必要となるレジスタ等の値の設定 (例えば Template Interpreter では いくつかのレジスタ(Llocalsなど)を特定の用途に決め打ちで使っている)
これらの作業は以下の処理の連携によって実現されている (なお, 引数の設定(オペランドスタックへのプッシュや register/stack への詰め込み)は「呼び出し以前」に行われるため, ここには記述していない).
1. 呼び出し元(caller側)での invoke 処理 ------------------> 2. スタブ処理 (i2c スタブ, c2i スタブ)
※スタブの必要が無いケースでは, この処理は行われない.
|
V
3. 呼び出し先(callee 側)での method entry 処理
|
V
4. 呼び出し先(callee 側)での return 処理
|
5. 呼び出し元(caller 側)での return 後の後片付け処理 <------------/
それぞれのフェーズで以下の処理が行われる.
* caller save レジスタの退避
* 呼び出すメソッドのエントリポイントとなるアドレスの取得
* リターンアドレスの設定 & エントリポイントの呼び出し
(これらのスタブでは, インタープリタ実行のメソッドから JIT コンパイルされたメソッドを呼び出す場合, あるいはその逆の場合に, 引数の渡し方など calling convention がずれていることがあるのでそれを調整する処理を行う)
* 引数の詰め直し (スタックフレーム内の引数領域の変更による SP の修正も含む)
* 実際の呼び先へのジャンプ (= 末尾呼び出し. リターンアドレスは設定しない)
* callee save レジスタの退避 (JIT コンパイルされたメソッドの場合は, 待避処理は必要になった箇所で定義行われることもある)
* スタックフレームの確保
(interpreter の場合には, この際に局所変数領域確保のために呼び出し元(caller)のフレームを拡張することがある)
* (template interpreter ならば) 特定用途のレジスタの値の設定
* スタックフレームの破棄
* callee save レジスタの復帰
* 呼び出し元へのリターン
* レジスタの復帰
* ...#TODO
"2." のスタブ処理や "3." のエントリ部での処理時に呼び出し元の SP がずらされてしまうことがある. SP がずれたままだとリターン後の処理が正常に行えないので戻しておく必要があるが, これについては全てインタープリタ側で対処している模様 (x86でもsparcでもインタープリタ側) (See: here and here for details).
This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.