Schemeの可変個引数の大事な部分 - Scheme VM を書く

可変個引数をサポートする場合はここを押さえましょう。
もはや誰向けの記事だか分かったものではない。

(proc 1 2)

というコードがあったとしてコンパイラはこの proc が何個の引数をとるかは知ることが出来ない。


例えば

(define (proc a b) ...) ; (1)
(define (proc a . b) ...) ; (2)
(define proc (lambda a ...)) ; (3)

のどの場合もありうる。
なのでコンパイラは引数を評価してスタックに積むというコードを吐くだけにとどまる。


で、proc 側のコードは(1)であれば、
a を参照するときは refer-local 0 でスタックにある値を参照する。
手続きの最後に、スタックの後かたづけとして引数2個分お掃除してね(適当表現)とお願いしてスタックにあった戻り先に帰っていく。


(1)の場合は簡単なのだけど (2) の場合は少し面倒。
例えば (proc 1 2 3) と呼ばれた場合、a => 1, b=> (2 3) と束縛されなければいけないから。
でもコンパイラはそんな事情を知らないので、スタックに引数を3つ積んでしまう。
一方、proc のコードは引数は a と b の2つであることが前提のコードが吐かれているのでこのまま実行すると

  • a => 1, b => 2 と参照してしまう
  • 引数は3つ積まれているのに2個分のお掃除しかしない

という状態になってしまう。


これを1つずつ VM 側が実行時に面倒を見てあげなければならない。
具体的には

  1. b のスタックの位置に(2 3)を置く(上書き)
  2. 引数が3つ積まれた状態を2つにする(スタックをずらす)

ということをする。


これらを行うには、

  • 手続き呼出し時に引数がいくつ渡されたかという情報
  • 手続きがとる引数の数
  • 手続きが可変個数の引数をとるかどうか?

の情報が必要でこれらはコンパイル時に決まる。


この逆のパターンもあって、(2) で (proc 3) とされた場合はスタックを逆方向にずらして '() を積む必要がある。

蛇足

最初の実装では、「引数は3つ積まれているのに2個分のお掃除しかしない」をどうにかするために、コードの動的な書き換えで対応していた。
(return 2) を (return 3) にするイメージ。
これはひどいと思っていたので今日実装しなおした。
1つ dirty な部分が完全解決したので一安心。