Toy VM(5)
SECD マシンをちょっと変形してみよう。
スタックだけなのが元々の SECD マシンだ。SECD マシンを何となく動かすことはできた。簡単だけどとても面白いVM-仮想マシンだ。こいつに V レジスタ - 計算結果を保持するようなレジスタを足してみよう。gauche VM に少しだけ近づけてみるんだ。V(VAL0) レジスタがどんなふうに(高速化に?)役に立っているかはまだ分からないけど、まずはマネをして少しずつ理解しよう。
当然だけどVM命令もちょっと変える必要がある。例えば元々の SECD マシンの LDC 命令- スタックに定数を積む基本命令 - は、V に値を積む CONST 命令と、V の値をスタックに積む PUSH 命令に分解される。パターンマッチの match があるから、実装は非常にシンプルだ。
disasm を使ってVM命令を出力してみる。実現したいのはこれだ。
gosh> (disasm (lambda () (+ 13 5))) main_code (name=#f, code=0xc5730, size=10, const=1, stack=5): args: #f 0 CONST 13 2 PUSH 3 CONST 5 5 PUSH 6 GREF #<identifier secd#+>; + 8 TAIL-CALL(2) ; (+ 13 5) 9 RET #<undef>
CONST, PUSH, CONST, PUSH。これはSとVを使うだけだ。GREF, (TAIL)CALL, RET。GREF はVレジスタにプロシージャを置くものとしよう(Reading Gauche 参照)。CALL n はスタックから n 個の引数を取り出して V レジスタのプロシージャを適用しよう。RET は継続の扱いと関係するから、ちょっと怪しいけどとりあえずSECDのRTNをマネしておこう。
適当にコンパイラを作って、動かしてみた。
gosh> (compile-g () '(+ 13 5)) (CONST 13 PUSH CONST 5 PUSH GREF + CALL 2)
gosh> (secdv 's 'e (compile-g () '(+ 13 5)) 'd 'v) (s e (CONST 13 PUSH CONST 5 PUSH GREF + CALL 2) d v) (s e (PUSH CONST 5 PUSH GREF + CALL 2) d 13) ((13 . s) e (CONST 5 PUSH GREF + CALL 2) d ()) ((13 . s) e (PUSH GREF + CALL 2) d 5) ((5 13 . s) e (GREF + CALL 2) d ()) ((5 13 . s) e (CALL 2) d #<subr +>) (s e () d 18) (s e () d 18) stop
;; SECD + V register machine (define (secdv s e c d v) (match (list s e c d v) ((s e ('PUSH . c) d v) (secdv (cons v s) e c d ())) ((s e ('CONST val . c) d v) (secdv s e c d val)) ;; s e (PRE-CALL . c) d v TODO ;;((s e ('PRE-CALL location . c) d v) ;; (secdv s e c (append (list s e location) d) v)) ;; RET TODO; ((s2 e2 ('RET . cx) (s e c . d) v) (secdv s e c d v)) ;; GREF + ((s e ('GREF '+ . c) d v) (secdv s e c d +)) ;; GREF - ((s e ('GREF '- . c) d v) (secdv s e c d -)) ;; CALL argc ((s e ('CALL 0 . c) d (? procedure? v)) (secdv s e c d (v))) (((a0 . s) e ('CALL 1 . c) d (? procedure? v)) (secdv s e c d (v a0))) (((a1 a0 . s) e ('CALL 2 . c) d (? procedure? v)) (secdv s e c d (v a0 a1))) ;; base condition ((s e c d v) (values (list s e c d v) 'stop)))) (define (compile-g env exp) (cond ;;((eq? exp 'NIL) ;; '(NIL)) ((number? exp) `(CONST ,exp)) (else (match exp (('+ a b) `(,@(compile-g env a) PUSH ,@(compile-g env b) PUSH GREF + CALL 2)) (('- a b) `(,@(compile-g env a) PUSH ,@(compile-g env b) PUSH GREF - CALL 2)) ))))
次は PRE-CALL 相当をやる。継続だ。あんまり自信はないけど、SECD マシンの AP 命令とそんなに違いはないはずだ。
多分続く。