Mosh のスタックフレーム構造 - Mosh 開発

スタックフレームの構造を変更したのでメモ。
書いておかないと絶対忘れる自信がある。


スタックフレームは大きくわけて2種類。

  • 手続き呼び出しフレーム
  • let フレーム


let フレームは手続き呼び出しフレームよりも積むものが少ないので多少軽い。

手続き呼び出しフレーム

関連する命令は、FRAME, CALL, RETURN。


FRAME は以下の順で以下のものを push する。

  • CALL 後の行き先(Continuation)
  • current closure ポインタ(現在呼び出されているクロージャのポインタ)
  • frame ポインタ

このとき frame ポインタはまだ更新されていない。


FRAME 後は引数が積まれる、(proc arg0 arg1) なら arg0, arg1 の順に積まれる。


CALL ではオプショナル引数の調整などを行いつつ frame ポインタを引数を積む直前に設定する。
つまりこの時点でスタックは

========================================
            continuation
========================================
            curren closure
========================================
          prev frame pointer
======================================== <=== frame pointer
             arg0
========================================
             arg1
======================================== <=== stack pointer	

という状態。


CALL の時点で呼び出されるクロージャに突入し、引数には frame pointer からのオフセットでアクセスする。
arg1 であれば frame pointer + 1。


RETURN は簡単で、FRAME で積んだものを3つ元に戻して復帰。

let フレーム

関連する命令は、LET_FRAME, ENTER, LEAVE。
もうほとんど手続き呼び出しフレームと一緒。


LET_FRAME は以下の順で以下のものを push する。

  • current closure ポインタ(現在呼び出されているクロージャのポインタ)
  • frame ポインタ

このとき frame ポインタはまだ更新されていない。


ENTER 後は引数が積まれる、(let ([arg0 a] [arg1 b]) ...) なら arg0, arg1 の順に積まれる。


ENTER では frame ポインタを引数を積む直前に設定する。
つまりこの時点でスタックは

========================================
            curren closure
========================================
          prev frame pointer
======================================== <=== frame pointer
             arg0
========================================
             arg1
======================================== <=== stack pointer	

という状態。
ちなみにこの frame ポインタ設定のために、今回 ENTER に引数の個数オペランドを追加した。


ENTER の時点で呼び出される(display)クロージャに突入し、引数には frame pointer からのオフセットでアクセスする。
arg1 であれば frame pointer + 1。


LEAVE は簡単で、LET_FRAME で積んだものを2つ元に戻して復帰。

二つの frame に共通すること

エラーが起きたときに、その時点の frame ポインタから固定のオフセットで

  • 1つ前の frame ポインタ
  • 1つ前の closure ポインタ

にアクセスできるように配置の仕方を似せてあります。

補足

勘の良い方は気づいたかもしれないが、スタックフレームにはクロージャや let が見ている外の環境(free variables)は存在しない。
これらはすべてコンパイル時に検出されて、クロージャデータ構造に格納されている。