VM
眺めているだけではやっぱりよく分からないので、手を動かそう。要はスタックマシン ScmVM を真似してスタックマシンを作ってみれば基本的な部分は理解できると思う。
(define-class <toy-vm> () ((val0 :accessor val0-of :init-value #f) (pc :accessor pc-of :init-value 0) (code :accessor code-of :init-value '((ret)) :init-keyword :code) (stack :accessor stack-of :init-value ()))) (define-method pc++ ((vm <toy-vm>)) (set! (pc-of vm) (+ 1 (pc-of vm)))) (define (vm-code program) (vm-code->list (compile-pass3 (compile-pass1 program)))) (define-method loop ((vm <toy-vm>)) (cond ((>= (pc-of vm) (length (code-of vm))) 'halt) (else (let1 code (list-ref (code-of vm) (pc-of vm)) (match code (('CONSTI n) (set! (val0-of vm) n) (pc++ vm) (loop vm)) (('CONSTI-PUSH n) (push! (stack-of vm) n) (pc++ vm) (loop vm)) (('PUSH) (push! (stack-of vm) (val0-of vm)) (pc++ vm) (loop vm)) (('RET) ;; FIXME (values (val0-of vm) 'ret)) (('GREF-TAIL-CALL n) ;; FIXME (pc++ vm) (let ((id (list-ref (code-of vm) (pc-of vm))) (args ())) (dotimes (v n) (push! args (pop! (stack-of vm)))) (case (identifier->symbol id) ((+) (set! (val0-of vm) (apply + args)) (pc++ vm) (loop vm)) ((-) (set! (val0-of vm) (apply - args)) (pc++ vm) (loop vm)) (else (format #t "unknown: id: ~a" id)) ))) (_ (errorf "unknown instruction: ~a type: ~a" code (class-of code)))))))) ;;;; gosh> (vm-code '(+ 3 2 8 )) ((CONSTI-PUSH 3) (CONSTI-PUSH 2) (CONSTI-PUSH 8) (GREF-TAIL-CALL 3) #<identifier toy#+> (RET)) gosh> (loop (make-toy-vm (vm-code '(+ 3 2 8 ))))
これから理解するのが FIXME と書いた RET, GREF-TAIL-CALL などの部分や PRE-CALL とか。継続がからんでくるのでわたしには結構難しいかもしれない。
幾つかの最低限の命令に対応すると、Gauche のコンパイラと実行系を使った、Gauche ScmVM より何倍か(10倍以上?)*遅い* VM もどきが手に入る、はず。なんて無駄なしろものだと思われるかもしれないが、これを LLVM の命令列に変換して LLVM で最適化すると‥?
Gauche のライブラリが動く高速ネイティブコンパイラになるかな?それともさらに依存関係の多い、より役に立たないしろものが手に入るか?