スタックとローカル変数
メソッドを呼び出すと,その呼び出し用のフレームが作られます。フレームには,計算中の値を置くオペランドスタックと,引数や途中結果を保存するローカル変数配列があります。
スタックから値を取り出して計算する
スタックは最後に積んだ値から取り出します。次の表では右端を TOP とします。
public static subtract(II)I {
iload_0
iload_1
isub
ireturn
}
引数を a と b とすると,状態は次のように変わります。
| 命令 | 実行前 | 実行後 |
|---|---|---|
iload_0 | 空 | a |
iload_1 | a | a, b |
isub | a, b | a - b |
ireturn | a - b | 呼び出し元へ値を返し,このフレームを破棄 |
isub は先に積んだ値から TOP の値を引きます。加算では順序を入れ替えても結果は同じですが,減算や除算では順序が結果に影響します。
ireturn の後に「このメソッドの空のスタック」が残るわけではありません。実行中のフレームは破棄され,呼び出し元のスタックに戻り値が積まれます。
ローカル変数は番号で指定する
iload_0 はスロット 0 の int をスタックへコピーします。istore_1 はスタックの TOP を取り出してスロット 1 に保存します。ロードしてもローカル変数の値は消えません。
| メソッド | スロットの配置 |
|---|---|
static add(II)I | 0 に第1引数,1 に第2引数 |
add(II)I | 0 に this,1 に第1引数,2 に第2引数 |
static mix(JI)I | 0–1 に long 引数,2 に int 引数 |
mix(JI)I | 0 に this,1–2 に long 引数,3 に int 引数 |
long と double は連続する二つのローカル変数スロットを使います。読み書きには先頭の番号を指定します。それ以外の引数は一つのスロットを使います。
this と引数以外のスロットは,読み出す前に値を保存してください。Java のフィールドや配列要素とは異なり,未設定のローカル変数を 0 として読めるわけではありません。
名前を付けて参照する
public static ten()I {
iconst_0
istore_0 [I -> count]
iinc 0 10
iload count
ireturn
}
[I -> count] はスロットに型と名前の情報を添える記法です。後続の iload count は,対応するスロットを参照します。実行時の保存先が名前付きの領域に変わるわけではありません。
一つの値と一つのスロットを区別する
スタック操作では,long と double をカテゴリ2,それ以外の通常の値をカテゴリ1として扱います。カテゴリ2の値はスタックの深さの計算では二単位ですが,一つの値です。
| 命令 | 使用できる値 |
|---|---|
dup | TOP のカテゴリ1の値を複製 |
dup2 | TOP のカテゴリ2の値一つ,またはカテゴリ1の値二つを複製 |
swap | TOP 側のカテゴリ1の値二つを交換 |
pop | TOP のカテゴリ1の値を破棄 |
pop2 | TOP のカテゴリ2の値一つ,またはカテゴリ1の値二つを破棄 |
たとえば double の戻り値を使わない場合は pop2 を使います。pop で半分だけ捨てることはできません。
Jaspera ではブレークポイントで停止してフレームを確認できます。分岐で状態が合わない場合は StackMapFrame と検証 を参照してください。