ボトムアップ
compile.scm を読むための準備として、少しずつ compile - disassemble を繰り返して、 Gauche VM の挙動を外から把握しよう。最終的に Gauche の VM への命令列が手で作れて、実行できると嬉しい。
http://www.practical-scheme.net/wiliki/wiliki.cgi?Gauche%3aYAGHG%3aIntroduction も参考に、gauche.internal.test を作った。
また compile.scm に関数を追加して、make しなおした。これは命令列を眺めて遊ぶ目的としては、どちらかは不要と思うけどとりあえず。
(define (compile-pass1 program) (pass1 program (make-bottom-cenv))) (define (compile-pass2 iform) (pass2 iform)) (define (compile-pass3 iform) (pass3 iform (make-compiled-code-builder 0 0 '%toplevel #f #f) '() 'tail))
まずは即値を試そう。コンパイラをインクリメンタルに作るときと同じように。
gosh> (compile-p1 42) ($const 42) #<undef> gosh> (compile-pass1 42) #(5 42)
なるほど、$const って唯の数値、 enum か。
gosh> (compile-pass3 (compile-pass1 42)) #<compiled-code %toplevel@0x12f4690> gosh> (vm-dump-code (compile-pass3 (compile-pass1 42))) main_code (name=%toplevel, code=0x12c64e0, size=2, const=0, stack=0): args: #f 0 CONSTI(42) 1 RET #<undef>
#t と #f だと命令が違っている。まだその違いの理由はまだ分からない。
gosh> (vm-dump-code (compile-pass3 (compile-pass1 #t))) main_code (name=%toplevel, code=0x12c64a0, size=2, const=0, stack=0): args: #f 0 CONST-RET #t #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 #f))) main_code (name=%toplevel, code=0x12c6470, size=1, const=0, stack=0): args: #f 0 CONSTF-RET
fixnum の最大値も試す。const=0 と const=1 の違いがある。
gosh> (vm-dump-code (compile-pass3 (compile-pass1 (greatest-fixnum)))) main_code (name=%toplevel, code=0x130be88, size=2, const=0, stack=0): args: #f 0 CONST-RET 536870911 #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 (+ 1 (greatest-fixnum))))) main_code (name=%toplevel, code=0x130be48, size=2, const=1, stack=0): args: #f 0 CONST-RET 536870912 #<undef>
次は unary primitive(一引数の組み込み手続き)。予想に反して、null?, integer?, zero? は微妙に命令列として違うようだ。Gauche の目的に即した理由があるに違いない。インタプリタであるので、いろいろな制約が掛かっているはず。
gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(null? 42)))) main_code (name=%toplevel, code=0xa2c10, size=3, const=0, stack=0): args: #f 0 CONSTI(42) 1 NULLP ; (null? 42) 2 RET #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(integer? #t)))) main_code (name=%toplevel, code=0x1220390, size=5, const=1, stack=4): args: #f 0 CONST-PUSH #t 2 GREF-TAIL-CALL(1) #<identifier gauche.internal#integer?>; (integer? #t) 4 RET #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(zero? 32)))) main_code (name=%toplevel, code=0xa2980, size=4, const=0, stack=1): args: #f 0 CONSTI-PUSH(32) 1 CONSTI(0) 2 NUMEQ2 ; (zero? 32) 3 RET #<undef>
次は binary primitive。=, eq? は期待するシンプルな命令、スタックにプッシュして命令実行。
だけど、+ は簡単になりすぎている。調べたところ pass1 の時点で足し算が実行されている。
gosh> (vm-dump-code (compile-pass3 (compile-pass2 (compile-pass1 '(eq? #\a 1))))) main_code (name=%toplevel, code=0x12200c0, size=5, const=0, stack=1): args: #f 0 CONST-PUSH #\a 2 CONSTI(1) 3 EQ ; (eq? #\a 1) 4 RET #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass2 (compile-pass1 '(= 7 7))))) main_code (name=%toplevel, code=0x5cdde0, size=4, const=0, stack=1): args: #f 0 CONSTI-PUSH(7) 1 CONSTI(7) 2 NUMEQ2 ; (= 7 7) 3 RET #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass2 (compile-pass1 '(+ 7 7))))) main_code (name=%toplevel, code=0x130b9b8, size=2, const=0, stack=0): args: #f 0 CONSTI(14) 1 RET #<undef>
local variables, let はもう少し調べる必要がある。LOCAL-ENV, CONST-RET, LREF0 がまだイメージが分からない。
gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(let ((x 3)) #t)))) main_code (name=%toplevel, code=0x5cdac0, size=4, const=0, stack=4): args: #f 0 CONSTI-PUSH(3) 1 LOCAL-ENV(1) ; (let ((x 3)) #t) 2 CONST-RET #t #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(let ((x 3)) x)))) main_code (name=%toplevel, code=0x5cda60, size=4, const=0, stack=4): args: #f 0 CONSTI-PUSH(3) 1 LOCAL-ENV(1) ; (let ((x 3)) x) 2 LREF0 ; x 3 RET #<undef> gosh> (vm-dump-code (compile-pass3 (compile-pass1 '(let ((x 3) (y 7)) #f)))) main_code (name=%toplevel, code=0x5cd9d0, size=4, const=0, stack=5): args: #f 0 CONSTI-PUSH(3) 1 CONSTI-PUSH(7) 2 LOCAL-ENV(2) ; (let ((x 3) (y 7)) #f) 3 CONSTF-RET #<undef>