SICP 読み (347) 5.5 翻訳系

問題 5.44

と言いつつ 5.43 なんですが、昨晩の解はダウト。lexical-address-lookup なソレが出力されておりません。日曜囲碁を見つつ問題処理。
ちなみに 5.42 な解では

gosh> (compile '(lambda (x) x) 'val 'return '())
((env continue) (val) 
 ((assign val (op make-compiled-procedure) (label entry23) (reg env)) 
  #0=(goto (reg continue)) 
  entry23 
  (assign env (op compiled-procedure-env) (reg proc)) 
  (assign env (op extend-environment) (const (x)) (reg argl) (reg env)) 
  (assign val (op lexical-address-lookup) (const (0 0)) (reg env))
  #0# 
  after-lambda24))
gosh>

なんですが、昨晩な処理系だと

gosh> (compile '(lambda (x) x) 'val 'return '())
((env continue) (val) 
 ((assign val (op make-compiled-procedure) (label entry28) (reg env)) 
  #0=(goto (reg continue)) 
  entry28 
  (assign env (op compiled-procedure-env) (reg proc)) 
  (assign env (op extend-environment) (const (x)) (reg argl) (reg env)) 
  (assign val (op lookup-variable-value) (const x) (op get-global-environment)) 
  #0# 
  after-lambda29))
gosh> 

で、確認してみたら compile-lambda-body の末端部分のナニが

     (compile-sequence (scan-out-defines (lambda-body exp)) 'val 'return cenv))))

となっているのを発見。ここで cenv 拡張しないと駄目。これでは lexical address な命令は死んでも出てこない。
これで 5.43 の最終版が出来たのだろうか。

gosh> (compile '(define (length items)
  (define (length-iter a count)
    (if (null? a)
        count
        (length-iter (cdr a) (+ 1 count))))
  (length-iter items 0)) 'val 'return '())
((env continue) (val) 
 ((assign val #0=(op make-compiled-procedure) (label entry3) . #1=((reg env))) 
  (goto (label after-lambda4)) 
  entry3 
  #2=(assign env (op compiled-procedure-env) (reg proc)) 
  (assign env #3=(op extend-environment) (const (items)) . #4=((reg argl) (reg env))) 
  (assign proc #0# (label entry5) . #1#) 
  (goto (label after-lambda6)) 
  entry5 
  #2# 
  (assign env #3# (const (length-iter)) . #4#) 
  (assign val #0# (label entry7) . #1#) 
  (goto (label after-lambda8)) 
  entry7 
  #2# 
  (assign env #3# (const (a count)) . #4#) 
  (save continue) 
  (save env) 
  (assign proc #5=(op lookup-variable-value) (const null?) . #6=((op get-global-environment))) 
  (assign val #7=(op lexical-address-lookup) (const (0 0)) . #8=((reg env))) 
  #9=(assign argl (op list) (reg val)) 
  #10=(test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch12)) 
  compiled-branch13 
  (assign continue (label after-call14)) 
  #11=(assign val (op compiled-procedure-entry) (reg proc)) 
  #12=(goto (reg val)) 
  primitive-branch12 
  (assign val . #13=((op apply-primitive-procedure) (reg proc) (reg argl))) 
  after-call14 
  (restore env) 
  (restore continue) 
  (test (op false?) (reg val)) 
  (branch (label false-branch10)) 
  true-branch9 
  (assign val #7# (const (0 1)) . #8#) 
  #14=(goto (reg continue)) 
  false-branch10 
  (assign proc #7# (const (1 0)) . #8#) 
  (save continue) 
  (save proc) 
  (save env) 
  (assign proc #5# (const +) . #6#) 
  (assign val #7# (const (0 1)) . #8#) 
  #9# 
  (assign val (const 1)) 
  #15=(assign argl (op cons) (reg val) (reg argl)) 
  #10# 
  (branch (label primitive-branch18)) 
  compiled-branch19 
  (assign continue (label after-call20)) 
  #11# 
  #12# 
  primitive-branch18 
  (assign val . #13#) 
  after-call20 
  #9# 
  (restore env) 
  (save argl) 
  (assign proc #5# (const cdr) . #6#) 
  (assign val #7# (const (0 0)) . #8#) 
  #9# 
  #10# 
  (branch (label primitive-branch15)) 
  compiled-branch16 
  (assign continue (label after-call17)) 
  #11# 
  #12# 
  primitive-branch15 
  (assign val . #13#) 
  after-call17 
  (restore argl) 
  #15# 
  (restore proc) 
  (restore continue) 
  #10# 
  (branch (label primitive-branch21)) 
  compiled-branch22 
  #16=(assign val (op compiled-procedure-entry) (reg proc)) 
  #17=(goto (reg val)) 
  primitive-branch21 
  (assign val . #13#) 
  #14# 
  after-call23 
  after-if11 
  after-lambda8 
  (perform (op lexicall-address-set!) (const (0 0)) (reg val) (reg env)) 
  (assign val (const ok)) 
  (assign proc #7# (const (0 0)) . #8#) 
  (assign val (const 0)) 
  #9# 
  (assign val #7# (const (1 0)) . #8#) 
  #15# 
  #10# 
  (branch (label primitive-branch24)) 
  compiled-branch25 
  #16# 
  #17# 
  primitive-branch24 
  (assign val . #13#) 
  #14# 
  after-call26 
  after-lambda6 
  (assign val (const *unassigned*)) 
  #9# 
  #10# 
  (branch (label primitive-branch27)) 
  compiled-branch28 
  #16# 
  #17# 
  primitive-branch27 
  (assign val . #13#) 
  #14# 
  after-call29 
  after-lambda4 
  (perform (op define-variable!) (const length) (reg val) (reg env)) 
  (assign val (const ok)) 
  #14#))
gosh> 

整形は略 (を
一応 5.43 の最終版とゆーコトで ch5-compiler.scm も以下に。

;;;;COMPILER FROM SECTION 5.5 OF
;;;; STRUCTURE AND INTERPRETATION OF COMPUTER PROGRAMS

;;;;Matches code in ch5.scm

;;;;This file can be loaded into Scheme as a whole.
;;;;**NOTE**This file loads the metacircular evaluator's syntax procedures
;;;;  from section 4.1.2
;;;;  You may need to change the (load ...) expression to work in your
;;;;  version of Scheme.

;;;;Then you can compile Scheme programs as shown in section 5.5.5

;;**implementation-dependent loading of syntax procedures
(load "ch5-syntax.scm")			;section 4.1.2 syntax procedures

(define (scan-out-defines exp)
  (define (make-set init set exp)
    (if (null? exp)
        (if (null? init)
            '()
            (append (list 'let init) set))
        (if (definition? (car exp))
            (make-set (append init (list (list (definition-variable (car exp))
                                               ''*unassigned*)))
                      (append set (list (list 'set!
                                              (definition-variable (car exp))
                                              (definition-value (car exp)))))
                      (cdr exp))
            (make-set init set (cdr exp)))))
  (define (remove-define l exp)
    (cond ((null? exp) l)
          ((definition? (car exp))
           (remove-define l (cdr exp)))
          (else
           (remove-define (append l (list (car exp))) (cdr exp)))))
  (let ((result (make-set '() '() exp)))
    (if (null? result) 
        exp
        (list (append result (remove-define '() exp))))))

;;;SECTION 5.5.1

(define (lexical-address-lookup add env)
  (let ((result (list-ref (cdr (list-ref env (car add))) (cadr add))))
    (if (eq? '*unassigned* result)
	(error "variable is unassigned -- " result)
	result)))

(define (lexical-address-set! add val env)
  (define (scan n l)
    (if (= n 0)
	(set-car! l val)
	(scan (- n 1) (cdr l))))
  (scan (cadr add) (cdr (list-ref env (car add)))))

(define (find-variable var env)
  (define (f-search i env)
    (define (lexical-add-out j l)
      (cond ((null? l) (f-search (+ i 1) (cdr env)))
	    ((eq? var (car l)) (list i j))
	    (else
	     (lexical-add-out (+ j 1) (cdr l)))))
    (if (null? env)
	'not-found
	(lexical-add-out 0 (car env))))
  (f-search 0 env))

(define (compile exp target linkage cenv)
  (cond ((self-evaluating? exp)
         (compile-self-evaluating exp target linkage))
        ((quoted? exp) (compile-quoted exp target linkage))
        ((variable? exp)
         (compile-variable exp target linkage cenv))
        ((assignment? exp)
         (compile-assignment exp target linkage cenv))
        ((definition? exp)
         (compile-definition exp target linkage cenv))
        ((if? exp) (compile-if exp target linkage cenv))
        ((lambda? exp) (compile-lambda exp target linkage cenv))
        ((begin? exp)
         (compile-sequence (begin-actions exp)
                           target
                           linkage
			   cenv))
        ((cond? exp) (compile (cond->if exp) target linkage cenv))
        ((let? exp) (compile (let->combination exp) target linkage cenv))
        ((application? exp)
         (compile-application exp target linkage cenv))
        (else
         (error "Unknown expression type -- COMPILE" exp))))

(define (make-instruction-sequence needs modifies statements)
  (list needs modifies statements))

(define (empty-instruction-sequence)
  (make-instruction-sequence '() '() '()))


;;;SECTION 5.5.2

;;;linkage code

(define (compile-linkage linkage)
  (cond ((eq? linkage 'return)
         (make-instruction-sequence '(continue) '()
          '((goto (reg continue)))))
        ((eq? linkage 'next)
         (empty-instruction-sequence))
        (else
         (make-instruction-sequence '() '()
          `((goto (label ,linkage)))))))

(define (end-with-linkage linkage instruction-sequence)
  (preserving '(continue)
   instruction-sequence
   (compile-linkage linkage)))


;;;simple expressions

(define (compile-self-evaluating exp target linkage)
  (end-with-linkage linkage
   (make-instruction-sequence '() (list target)
    `((assign ,target (const ,exp))))))

(define (compile-quoted exp target linkage)
  (end-with-linkage linkage
   (make-instruction-sequence '() (list target)
    `((assign ,target (const ,(text-of-quotation exp)))))))

(define (compile-variable exp target linkage)
  (end-with-linkage linkage
   (make-instruction-sequence '(env) (list target)
    `((assign ,target
              (op lookup-variable-value)
              (const ,exp)
              (reg env))))))

(define (compile-variable exp target linkage cenv)
  (let ((lexical-add (find-variable exp cenv)))
    (end-with-linkage 
     linkage
     (make-instruction-sequence 
      '(env) 
      (list target)
      (if (eq? 'not-found lexical-add)
	  `((assign ,target
		    (op lookup-variable-value)
		    (const ,exp)
		    (op get-global-environment)))
	  `((assign ,target
		    (op lexical-address-lookup)
		    (const ,lexical-add)
		    (reg env))))))))

(define (compile-assignment exp target linkage cenv)
  (let ((var (assignment-variable exp))
        (get-value-code
         (compile (assignment-value exp) 'val 'next cenv)))
    (end-with-linkage linkage
     (preserving '(env)
      get-value-code
      (make-instruction-sequence '(env val) (list target)
       `((perform (op set-variable-value!)
                  (const ,var)
                  (reg val)
                  (reg env))
         (assign ,target (const ok))))))))

(define (compile-assignment exp target linkage cenv)
  (let ((var (assignment-variable exp))
        (get-value-code
         (compile (assignment-value exp) 'val 'next cenv)))
    (let ((lexical-add (find-variable var cenv)))
      (end-with-linkage 
       linkage
       (preserving 
	'(env)
	get-value-code
	(make-instruction-sequence 
	 '(env val) 
	 (list target)
	 (if (eq? 'not-found lexical-add)
	     `((perform (op set-variable-value!)
			(const ,var)
			(reg val)
			(reg env))
	       (assign ,target (const ok)))
	     `((perform (op lexicall-address-set!)
			(const ,lexical-add)
			(reg val)
			(reg env))
	       (assign ,target (const ok))))))))))

(define (compile-definition exp target linkage cenv)
  (let ((var (definition-variable exp))
        (get-value-code
         (compile (definition-value exp) 'val 'next cenv)))
    (end-with-linkage linkage
     (preserving '(env)
      get-value-code
      (make-instruction-sequence '(env val) (list target)
       `((perform (op define-variable!)
                  (const ,var)
                  (reg val)
                  (reg env))
         (assign ,target (const ok))))))))


;;;conditional expressions

;;;labels (from footnote)
(define label-counter 0)

(define (new-label-number)
  (set! label-counter (+ 1 label-counter))
  label-counter)

(define (make-label name)
  (string->symbol
    (string-append (symbol->string name)
                   (number->string (new-label-number)))))
;; end of footnote

(define (compile-if exp target linkage cenv)
  (let ((t-branch (make-label 'true-branch))
        (f-branch (make-label 'false-branch))                    
        (after-if (make-label 'after-if)))
    (let ((consequent-linkage
           (if (eq? linkage 'next) after-if linkage)))
      (let ((p-code (compile (if-predicate exp) 'val 'next cenv))
            (c-code
             (compile
              (if-consequent exp) target consequent-linkage cenv))
            (a-code
             (compile (if-alternative exp) target linkage cenv)))
        (preserving '(env continue)
         p-code
         (append-instruction-sequences
          (make-instruction-sequence '(val) '()
           `((test (op false?) (reg val))
             (branch (label ,f-branch))))
          (parallel-instruction-sequences
           (append-instruction-sequences t-branch c-code)
           (append-instruction-sequences f-branch a-code))
          after-if))))))

;;; sequences

(define (compile-sequence seq target linkage cenv)
  (if (last-exp? seq)
      (compile (first-exp seq) target linkage cenv)
      (preserving '(env continue)
       (compile (first-exp seq) target 'next cenv)
       (compile-sequence (rest-exps seq) target linkage cenv))))

;;;lambda expressions

(define (compile-lambda exp target linkage cenv)
  (let ((proc-entry (make-label 'entry))
        (after-lambda (make-label 'after-lambda)))
    (let ((lambda-linkage
           (if (eq? linkage 'next) after-lambda linkage)))
      (append-instruction-sequences
       (tack-on-instruction-sequence
        (end-with-linkage lambda-linkage
         (make-instruction-sequence '(env) (list target)
          `((assign ,target
                    (op make-compiled-procedure)
                    (label ,proc-entry)
                    (reg env)))))
        (compile-lambda-body exp proc-entry cenv))
       after-lambda))))

(define (compile-lambda-body exp proc-entry cenv)
  (let ((formals (lambda-parameters exp)))
    (append-instruction-sequences
     (make-instruction-sequence '(env proc argl) '(env)
      `(,proc-entry
        (assign env (op compiled-procedure-env) (reg proc))
        (assign env
                (op extend-environment)
                (const ,formals)
                (reg argl)
                (reg env))))
     (compile-sequence (scan-out-defines (lambda-body exp)) 'val 'return (cons formals cenv)))))

;;;SECTION 5.5.3

;;;combinations

(define (compile-application exp target linkage cenv)
  (let ((proc-code (compile (operator exp) 'proc 'next cenv))
        (operand-codes
         (map (lambda (operand) (compile operand 'val 'next cenv))
              (operands exp))))
    (preserving '(env continue)
     proc-code
     (preserving '(proc continue)
      (construct-arglist operand-codes)
      (compile-procedure-call target linkage)))))

(define (construct-arglist operand-codes)
  (let ((operand-codes (reverse operand-codes)))
    (if (null? operand-codes)
        (make-instruction-sequence '() '(argl)
         '((assign argl (const ()))))
        (let ((code-to-get-last-arg
               (append-instruction-sequences
                (car operand-codes)
                (make-instruction-sequence '(val) '(argl)
                 '((assign argl (op list) (reg val)))))))
          (if (null? (cdr operand-codes))
              code-to-get-last-arg
              (preserving '(env)
               code-to-get-last-arg
               (code-to-get-rest-args
                (cdr operand-codes))))))))

(define (code-to-get-rest-args operand-codes)
  (let ((code-for-next-arg
         (preserving '(argl)
          (car operand-codes)
          (make-instruction-sequence '(val argl) '(argl)
           '((assign argl
              (op cons) (reg val) (reg argl)))))))
    (if (null? (cdr operand-codes))
        code-for-next-arg
        (preserving '(env)
         code-for-next-arg
         (code-to-get-rest-args (cdr operand-codes))))))

;;;applying procedures

(define (compile-procedure-call target linkage)
  (let ((primitive-branch (make-label 'primitive-branch))
        (compiled-branch (make-label 'compiled-branch))
        (after-call (make-label 'after-call)))
    (let ((compiled-linkage
           (if (eq? linkage 'next) after-call linkage)))
      (append-instruction-sequences
       (make-instruction-sequence '(proc) '()
        `((test (op primitive-procedure?) (reg proc))
          (branch (label ,primitive-branch))))
       (parallel-instruction-sequences
        (append-instruction-sequences
         compiled-branch
         (compile-proc-appl target compiled-linkage))
        (append-instruction-sequences
         primitive-branch
         (end-with-linkage linkage
          (make-instruction-sequence '(proc argl)
                                     (list target)
           `((assign ,target
                     (op apply-primitive-procedure)
                     (reg proc)
                     (reg argl)))))))
       after-call))))

;;;applying compiled procedures

(define (compile-proc-appl target linkage)
  (cond ((and (eq? target 'val) (not (eq? linkage 'return)))
         (make-instruction-sequence '(proc) all-regs
           `((assign continue (label ,linkage))
             (assign val (op compiled-procedure-entry)
                         (reg proc))
             (goto (reg val)))))
        ((and (not (eq? target 'val))
              (not (eq? linkage 'return)))
         (let ((proc-return (make-label 'proc-return)))
           (make-instruction-sequence '(proc) all-regs
            `((assign continue (label ,proc-return))
              (assign val (op compiled-procedure-entry)
                          (reg proc))
              (goto (reg val))
              ,proc-return
              (assign ,target (reg val))
              (goto (label ,linkage))))))
        ((and (eq? target 'val) (eq? linkage 'return))
         (make-instruction-sequence '(proc continue) all-regs
          '((assign val (op compiled-procedure-entry)
                        (reg proc))
            (goto (reg val)))))
        ((and (not (eq? target 'val)) (eq? linkage 'return))
         (error "return linkage, target not val -- COMPILE"
                target))))

;; footnote
(define all-regs '(env proc val argl continue))


;;;SECTION 5.5.4

(define (registers-needed s)
  (if (symbol? s) '() (car s)))

(define (registers-modified s)
  (if (symbol? s) '() (cadr s)))

(define (statements s)
  (if (symbol? s) (list s) (caddr s)))

(define (needs-register? seq reg)
  (memq reg (registers-needed seq)))

(define (modifies-register? seq reg)
  (memq reg (registers-modified seq)))


(define (append-instruction-sequences . seqs)
  (define (append-2-sequences seq1 seq2)
    (make-instruction-sequence
     (list-union (registers-needed seq1)
                 (list-difference (registers-needed seq2)
                                  (registers-modified seq1)))
     (list-union (registers-modified seq1)
                 (registers-modified seq2))
     (append (statements seq1) (statements seq2))))
  (define (append-seq-list seqs)
    (if (null? seqs)
        (empty-instruction-sequence)
        (append-2-sequences (car seqs)
                            (append-seq-list (cdr seqs)))))
  (append-seq-list seqs))

(define (list-union s1 s2)
  (cond ((null? s1) s2)
        ((memq (car s1) s2) (list-union (cdr s1) s2))
        (else (cons (car s1) (list-union (cdr s1) s2)))))

(define (list-difference s1 s2)
  (cond ((null? s1) '())
        ((memq (car s1) s2) (list-difference (cdr s1) s2))
        (else (cons (car s1)
                    (list-difference (cdr s1) s2)))))

(define (preserving regs seq1 seq2)
  (if (null? regs)
      (append-instruction-sequences seq1 seq2)
      (let ((first-reg (car regs)))
        (if (and (needs-register? seq2 first-reg)
                 (modifies-register? seq1 first-reg))
            (preserving (cdr regs)
             (make-instruction-sequence
              (list-union (list first-reg)
                          (registers-needed seq1))
              (list-difference (registers-modified seq1)
                               (list first-reg))
              (append `((save ,first-reg))
                      (statements seq1)
                      `((restore ,first-reg))))
             seq2)
            (preserving (cdr regs) seq1 seq2)))))

(define (tack-on-instruction-sequence seq body-seq)
  (make-instruction-sequence
   (registers-needed seq)
   (registers-modified seq)
   (append (statements seq) (statements body-seq))))

(define (parallel-instruction-sequences seq1 seq2)
  (make-instruction-sequence
   (list-union (registers-needed seq1)
               (registers-needed seq2))
   (list-union (registers-modified seq1)
               (registers-modified seq2))
   (append (statements seq1) (statements seq2))))

'(COMPILER LOADED)

次の 5.38 な機能盛り込みは別途。