海野秀之(うんのひでゆき)の外部記憶
Twitter (twilog) / RSS / アンテナ / ぶくま
4.5 章の Stack-Based 版を。 Gauche 上で動くようにするには、ただ写してくるだけではダメで、いくつか修正・追加が必要だった。
; 3imp_syntax_extensions.scm
; record and record-case are syntax exptensions described in 3imp p.27 and 28.
;
; I couldn't implement with define-syntax but I found a useful article:
; http://chobitscheme.blog87.fc2.com/?mode=m&no=3
;
; These code are from the article.
; The record syntax extension binds a set of variables to the element of a list
; (this list, or record, must contain as many elements as there are variables;
; the variables name the fields of the record).
;
; (record (var ...) val exp ...) ->
; (apply (lambda (var ...) exp ...) val)
;
(define-syntax record
(syntax-rules ()
((_ (var ...) val exp ...)
(apply (lambda (var ...) exp ...) val))))
; The record-case syntax extension is a special combination of cond and record.
; It is useful for destructuring a record based on the "key" that appears as
; the record's first element:
;
; (record-case exp1
; (key vars exp2 ...)
; :
; :
; (else exp3 ...)) ->
;
; (let ((r exp1))
; (cond
; ((eq? (car r) 'key)
; (record vars (cdr r) exp2 ...))
; (else exp3 ...)))
;
; The variable r is introduced so that exp is only evaluated once. Care must
; be taken to prevent r from capturing any free variables.
;
(define-syntax record-case
(syntax-rules (else)
((_ exp1 (else exp2 ...))
(begin exp2 ...))
((_ exp1 (key vars exp2 ...))
(let ((r exp1))
(if (eq? (car r) 'key)
(record vars (cdr r) exp2 ...))))
((_ exp1 (key vars exp2 ...) clause ...)
(let ((r exp1))
(if (eq? (car r) 'key)
(record vars (cdr r) exp2 ...)
(record-case r clause ...))))))
; The rec syntactic form allows the creation of self-recursive closures.
; The binding of var to () by let encloses the expression exp to which var is
; assigned. (The original vlue () is never references.)
(define-syntax rec
(syntax-rules ()
((_ var exp)
(let ((var '()))
(set! var exp)))))
; Quite often, a rec expression whose body is lambda is directly applied to
; arguments, usually to implement a loop. The syntactic form recur makes the
; code more readable
(define-syntax recur
(syntax-rules ()
((_ f ((var init) ...) exp ...)
((rec f (lambda (var ...) exp ...))
init ...))))
;; stack-operations.scm
; 3imp section 4.1.4
(define stack (make-vector 1000))
(define push
(lambda (x s)
(vector-set! stack s x)
(+ s 1)))
(define index
(lambda (s i)
(vector-ref stack (- (- s i) 1))))
(define index-set!
(lambda (s i v)
(vector-set! stack (- (- s i) 1) v)))
;; compile451.scm
; Translator from section 4.5.1 of 3imp.
(load "3imp_syntax_extensions.scm")
; p.95: compile-lookup, compile-refer
(define compile-lookup
(lambda (x e return-local return-free)
(recur nxtlocal ((locals (car e)) (n 0))
(if (null? locals)
(recur nxtfree ((free (cdr e)) (n 0))
(if (eq? (car free) x)
(return-free n)
(nxtfree (cdr free) (+ n 1))))
(if (eq? (car locals) x)
(return-local n)
(nxtlocal (cdr locals) (+ n 1)))))))
(define compile-refer
(lambda (x e next)
(compile-lookup x e
(lambda (n) (list 'refer-local n next))
(lambda (n) (list 'refer-free n next)))))
; p.93: help functions; set-member?, set-cons, set-union, set-minus, and set-intersect
(define set-member?
(lambda (x s)
(cond
((null? s) #f) ; modified from 3imp, '() can't be used as false.
((eq? x (car s)) #t)
(else (set-member? x (cdr s))))))
(define set-cons
(lambda (x s)
(if (set-member? x s)
s
(cons x s))))
(define set-union
(lambda (s1 s2)
(if (null? s1)
s2
(set-union (cdr s1) (set-cons (car s1) s2)))))
(define set-minus
(lambda (s1 s2)
(if (null? s1)
'()
(if (set-member? (car s1) s2)
(set-minus (cdr s1) s2)
(cons (car s1) (set-minus (cdr s1) s2))))))
(define set-intersect
(lambda (s1 s2)
(if (null? s1)
'()
(if (set-member? (car s1) s2)
(cons (car s1) (set-intersect (cdr s1) s2))
(set-intersect (cdr s1) s2)))))
; p.104: find-free
(define find-free
(lambda (x b)
(cond
((symbol? x) (if (set-member? x b) '() (list x)))
((pair? x)
(record-case x
(quote (obj) '())
(lambda (vars body)
(find-free body (set-union vars b)))
(if (test then else)
(set-union (find-free test b)
(set-union (find-free then b)
(find-free else b))))
(set! (var exp)
(set-union (if (set-member? var b) '() (list var))
(find-free exp b)))
(call/cc (exp) (find-free exp b))
(else
(recur next ((x x))
(if (null? x)
'()
(set-union (find-free (car x) b)
(next (cdr x))))))))
(else '()))))
; p.95: collect-free
(define collect-free
(lambda (vars e next)
(if (null? vars)
next
(collect-free (cdr vars) e
(compile-refer (car vars) e
(list 'argument next))))))
; p.101
(define find-sets
(lambda (x v)
(cond
((symbol? x) '())
((pair? x)
(record-case x
(quote (obj) '())
(lambda (vars body)
(find-sets body (set-minus v vars)))
(if (test then else)
(set-union (find-sets test v)
(set-union (find-sets then v)
(find-sets else v))))
(set! (var x)
(set-union (if (set-member? var v) (list var) '())
(find-sets x v)))
(call/cc (exp) (find-sets exp v))
(else
(recur next ((x x))
(if (null? x)
'()
(set-union (find-sets (car x) v)
(next (cdr x))))))))
(else '()))))
; p.102: make-boxes
(define make-boxes
(lambda (sets vars next)
(recur f ((vars vars) (n 0))
(if (null? vars)
next
(if (set-member? (car vars) sets)
(list 'box n (f (cdr vars) (+ n 1)))
(f (cdr vars) (+ n 1)))))))
; p.103
(define compile
(lambda (x e s next)
(cond
((symbol? x)
(compile-refer x e
(if (set-member? x s)
(list 'indirect next)
next)))
((pair? x)
(record-case x
(quote (obj) (list 'constant obj next))
(lambda (vars body)
(let ((free (find-free body vars))
(sets (find-sets body vars)))
(collect-free free e
(list 'close
(length free)
(make-boxes sets vars
(compile body
(cons vars free)
(set-union
sets
(set-intersect s free))
(list 'return (length vars))))
next))))
(if (test then else)
(let ((thenc (compile then e s next))
(elsec (compile else e s next)))
(compile test e s (list 'test thenc elsec))))
(set! (var x)
(compile-lookup var e
(lambda (n)
(compile x e s (list 'assign-local n next)))
(lambda (n)
(compile x e s (list 'assign-free n next)))))
(call/cc (x)
(list 'frame
next
(list 'conti
(list 'argument
(compile x e s '(apply))))))
(else
(recur loop ((args (cdr x))
(c (compile (car x) e s '(apply))))
(if (null? args)
(list 'frame next c)
(loop (cdr args)
(compile (car args)
e
s
(list 'argument c))))))))
(else (list 'constant x next)))))
set-member? が、Gauche 上で動かすには問題があった。 いまは、null は False じゃないんですね。
;; vm452.scm
; VM from section 4.5.2 of 3imp.
(load "3imp_syntax_extensions")
(load "stack-operations")
(load "compile451")
; box/unbox/set-box!, by higepon
; http://d.hatena.ne.jp/higepon/20071102/1193995703
(define (box v)
(cons 'box v))
(define (set-box! b v)
(set-cdr! b v))
(define (unbox b)
(cdr b))
; p.97; closure
(define closure
(lambda (body n s)
(let ((v (make-vector (+ n 1))))
(vector-set! v 0 body)
(recur f ((i 0))
(unless (= i n)
(vector-set! v (+ i 1) (index s i))
(f (+ i 1))))
v)))
; p.98: closure-body, index-closure
(define closure-body
(lambda (c)
(vector-ref c 0)))
(define index-clusure
(lambda (c n)
(vector-ref c (+ n 1))))
; p.83: save-stack, restore-stack
(define save-stack
(lambda (s)
(let ((v (make-vector s)))
(recur copy ((i 0))
(unless (= i s)
(vector-set! v i (vector-ref stack i))
(copy (+ i 1))))
v)))
(define restore-stack
(lambda (v)
(let ((s (vector-length v)))
(recur copy ((i 0))
(unless (= i s)
(vector-set! stack i (vector-ref v i))
(copy (+ i 1))))
s)))
; continuation, not shown in 3imp.
; http://cadr.g.hatena.ne.jp/mokehehe/20080510/1210388340
(define continuation
(lambda (s)
(closure
(list 'refer-local 0 (list 'nuate (save-stack s) '(return 0)))
0
'())))
; p.105
(define VM
(lambda (a x f c s)
(record-case x
(halt () a)
(refer-local (n x)
(VM (index f n) x f c s))
(refer-free (n x)
(VM (index-clusure c n) x f c s))
(indirect (x)
(VM (unbox a) x f c s))
(constant (obj x)
(VM obj x f c s))
(close (n body x)
(VM (closure body n s) x f c (- s n)))
(box (n x)
(index-set! s n (box (index s n)))
(VM a x f c s))
(test (then else)
(VM a (if a then else) f c s))
(assign-local (n x)
(set-box! (index f n) a)
(VM a x f c s))
(assign-free (n x)
(set-box! (index-closure c n) a)
(VM a x f c s))
(conti (x)
(VM (continuation s) x f c s))
(nuate (stack x)
(VM a x f c (restore-stack stack)))
(frame (ret x)
(VM a x f c (push ret (push f (push c s)))))
(argument (x)
(VM a x f c (push a s)))
(apply ()
(VM a (closure-body a) s a s))
(return (n)
(let ((s (- s n)))
(VM a (index s 0) (index s 1) (index s 2) (- s 3)))))))
3imp 中では、ここで使われている box/unbox と continuation のコードが明に示されていません。 いくつかのサイトにお世話になりました(URL はコメント中に)。
#!/usr/local/bin/gosh
(add-load-path ".")
(load "vm452")
(define banner
(lambda ()
(display "3imp Section 4.5 Compiler & VM")))
(define evaluate
(lambda (x)
(VM '() (compile x '() '() '(halt)) 0 '() 0)))
(define (repl)
(let loop ((s '()))
(display "\n>>> ")
(set! s (read))
(display (compile s '() '() '(halt)))
(newline)
(display "\n==> ")
(display (evaluate s))
(newline)
(loop '())))
(define (main args)
(banner)
(repl))
% ./repl4.5.scm 3imp Section 4.5 Compiler & VM >>> 1 (constant 1 (halt)) ==> 1 >>> (if #t 1 2) (constant #t (test (constant 1 (halt)) (constant 2 (halt)))) ==> 1 >>> (if #f 1 2) (constant #f (test (constant 1 (halt)) (constant 2 (halt)))) ==> 2 >>> (lambda (x) (lambda () x)) (close 0 (refer-local 0 (argument (close 1 (refer-free 0 (return 0)) (return 1)))) (halt)) ==> #((refer-local 0 (argument (close 1 (refer-free 0 (return 0)) (return 1))))) >>> ((lambda (x) (lambda () x)) 10) (frame (halt) (constant 10 (argument (close 0 (refer-local 0 (argument (close 1 (refer-free 0 (return 0)) (return 1)))) (apply))))) ==> #((refer-free 0 (return 0)) 10) >>> (((lambda (x) (lambda () x)) 10)) (frame (halt) (frame (apply) (constant 10 (argument (close 0 (refer-local 0 (argument (close 1 (refer-free 0 (return 0)) (return 1)))) (apply)))))) ==> 10 >>> ((lambda (x) (set! x (call/cc (lambda (cont) (cont 20))))) '()) (frame (halt) (constant () (argument (close 0 (box 0 (frame (assign-local 0 (return 1)) (conti (argument (close 0 (frame (return 1) (constant 20 (argument (refer-local 0 (apply))))) (apply)))))) (apply))))) ==> 20 >>>
まだ写経して、ちょっと動かしてみただけなので、きちんと理解できているわけじゃない。 この写経版を動かしたり改造したりしながら、 Stack-Based モデルを理解していきたい。 (改造しないと、できることが少なくて、「いろいろ動かす」のがムズいの)
だいたい、以下のような改造をやっていくつもり:
最初の3つは、だいたいこんな感じかなという想像がついているのだけど、 マクロはどうやるのがいいんだろうなぁ。
でも、begin くらいは使えないと、しんどい。なので、マクロもはやめにほしい。
んで、動作を理解してから、末尾呼び出しを含む最適化に取り組む。