Scheme Compiler の勉強(38) - closure impl
ようやくクロージャを実装できた(はず)。ただし lambda を code/closure に変換する部分は未だ。やたら時間が掛かったのは、3.9 Closures で1/2ページくらいでさらりと書いてある内容を咀嚼してしかも全く分かっていない LLVM に変換するのに難儀したため。久しぶりに夢の中でも考えるという経験をした。が、分かってしまうと closure 自体はそんなに難しいものではなかった‥。
本当に今更ではあるけど、最初から Intel-X86 (手元に無いけど)向けか PowerPC 向けにやれば、もともとそういうターゲットを想定した論文なのでもっとずっと分かりやすかった‥。
以下が例。
gosh> (run-program '(labels ((f-code (code () (y) y))) (let ((f (closure f-code 3))) (procedure? f)))) "#t\n" gosh> (run-program '(labels ((f-code (code (x) (y) (+ x y)))) (let ((f (closure f-code 3))) (funcall f 12)))) "15\n"
(code (var ...) (free-var ...) body ...) は自由変数を明示したもので、 lambda を分解したもの。この部分は scheme で書く必要があるが未だ作っていない。
;; これを上の形に変換する必要がある (let ((y 3)) (let ((f (lambda (x) (+ x y)))) (f 12)))
自由変数がある場合、仮引数に %closure を足すようにした。元論文と少し構成が異なる。元論文はスタックを明示的に操作して関数呼び出し時の引数を制御するが、LLVM では直接はスタックを操作せず明示的に関数の引数を指定する、ため。code をコンパイルする時、自由変数の数はもう分かっているので実行時の情報は必要ない。
define i32 @Label (i32 %arg1, i32 %closure) { ... ;; %arg1 と x, %closure に保存された値と y を関連付ける ;; (+ x y) をコンパイル }
(funcall f args ...) は closure f から関数ポインタを取り出し、args とともに closure そのものを実引数として関数を呼び出す。