[ 目次, 前節, 次節, 索引 ]

5.5.2 式の翻訳



本節と次節でcompile手続きが振り分けるコード生成器を実装する.
接続コードの翻訳
一般に各コード生成器の出力は, 要求された接続を実装する---手続きcompile-linkageで生成した---命令で終る. 接続がreturnなら, 命令(goto (reg continue))を生成しなければならない. これはcontinueレジスタを必要とし, レジスタの修正はない. 接続がnextなら追加の命令は必要ない. それ以外は接続はラベルで, レジスタを必要とせず, 修正もせず, そのラベルへのgoto命令を生成する:36

(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)))))))
接続コードは, return接続はcontinueレジスタを要求するので, continueレジスタをpreservingして命令列に連結される: 与えられた命令列がcontinueを修正し, 接続コードがそれを必要とするなら, continueは退避され回復される.

(define (end-with-linkage linkage instruction-sequence)
  (preserving '(continue)
   instruction-sequence
   (compile-linkage linkage)))
単純式の翻訳
自己評価式, クォート式, 変数に対するコード生成器は, 要求された値を標的レジスタへ代入し, 接続記述の指定通りに進む命令列を構成する.

(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))))))
これらの代入命令は標的レジスタを修正し, 変数を探索するものは, envレジスタを必要とする.

   代入と定義は, 解釈系でと同じように扱う. 変数に代入される値を計算するコードを再帰的に生成し, それを実際に変数を設定したり定義したりし, 式全体の値(記号ok)を標的レジスタに代入する二命令列と連結する. 再帰的翻訳は, コードが結果をvalに置き, その後に連結されるコードへ続行するよう, 標的valと接続nextを持つ. この連結は, 変数を設定, 定義するのに環境が必要だし, また変数の値のコードはレジスタを何らかの方法で修正するかもしれない複雑な式の翻訳であり得るので, envを保存して行う.


(define (compile-assignment exp target linkage)
  (let ((var (assignment-variable exp))
        (get-value-code
         (compile (assignment-value exp) 'val 'next)))
    (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-definition exp target linkage)
  (let ((var (definition-variable exp))
        (get-value-code
         (compile (definition-value exp) 'val 'next)))
    (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))))))))
連結した二命令列は, envvalを必要とし, 標的を修正する. この列でenvは保存したが, get-value-codeはこの列で使うため, 結果を積極的にvalに置くよう設計してあるので, valは保存しないことに注意しよう. (実際, valを保存すると, get-value-code が走った後, valの前の内容を回復しようとするので, 虫になる.)
条件式の翻訳
与えられた標的と接続を持って翻訳したif式のコードは
 ⟨標的 val, 接続 next で述語の翻訳⟩
 (test (op false?) (reg val))
 (branch (label false-branch))
true-branch
 ⟨与えられた標的と与えられた接続か  after-if の接続で帰結部の翻訳⟩
false-branch
 ⟨与えられた標的と接続で代替部の翻訳⟩
after-if
の形になる.

   このコードを生成するには, 述語, 帰結部, 代替部を翻訳し, 結果のコードを述語の結果をテストする命令, および真と偽の枝と条件式の終了を印つける新たに生成したラベルと組み合せる.37 このようなコードの配置では, テストが偽なら真の枝を飛び越さなければならない. 多少の複雑化は真の枝の接続をどう扱うかにある. 条件式の接続がreturnかラベルなら, 真の枝も偽の枝も同じ接続を使う. 接続がnext なら, 真の枝は偽の枝のコードを, 条件式の終りのラベルへ飛び越して終る.


(define (compile-if exp target linkage)
  (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))
            (c-code
             (compile
              (if-consequent exp) target consequent-linkage))
            (a-code
             (compile (if-alternative exp) target linkage)))
        (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))))))
envは真か偽の枝で必要かも知れないので, 述語コードで保存し, continueもこれらの枝の接続コードで必要かも知れないので保存する. (逐次的には実行しない)真と偽の枝のコードは, 5.5.4節に述べる特別な組合せ手続きparallel-instruction-sequencesで連結する.

   condは導出された式なので, 翻訳系がそれを扱うのに必要なのは, (4.1.2節の) cond->if変換器を作用させ, 結果のif式を翻訳することである.

並びの翻訳
(手続き本体やbegin式の)並びの翻訳は, その評価と対応する. 並びの各式を, ---最後の式は並びが指定された接続を持ち, その他の式は(並びの残りを実行するため) nextの接続を持って---翻訳する. 個々の式の命令列は, 単一の命令列を形成すべく(並びの残りに必要な)envと(並びの最後での接続で必要になるかも知れない) continueを保存しつつ連結する.

(define (compile-sequence seq target linkage)
  (if (last-exp? seq)
      (compile (first-exp seq) target linkage)
      (preserving '(env continue)
       (compile (first-exp seq) target 'next)
       (compile-sequence (rest-exps seq) target linkage))))
lambda式の翻訳
lambda式は手続きを構成する. lambda式の目的コードは
⟨手続きオブジェクトを構成しそれを標的レジスタへ代入⟩
⟨接続⟩
の形にならなければならない. lambda式を翻訳する時, 手続き本体のコードも生成する. 本体は手続き構成時には実行されないが, それを目的コードにしてlambdaのコードの直後に挿入するのが便利である. lambda式の接続がラベルかreturnならありがたい. しかし接続がnextなら, 本体の後に挿入したラベルへ飛ぶ接続を使って手続き本体のコードの前後を飛び越す必要がある. そこで目的コードは
 ⟨手続きオブジェクトを構成しそれを標的レジスタへ代入⟩
 ⟨与えられた接続のコード⟩ または (goto (label after-lambda))
 ⟨手続き本体の翻訳⟩
after-lambda
の形になる.

   compile-lambdaは手続きオブジェクトを構成するコードに, 手続き本体のコードが続いたものを生成する. 手続きオブジェクトは, 現在の環境(定義の場所での環境)と, 翻訳した手続き本体への入り口(新しく生成したラベル)を組み合せ, 実行時に構成される.38


(define (compile-lambda exp target linkage)
  (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))
       after-lambda))))
compile-lambdaは, 手続き本体をlambda式のコードに連結するのに, append-instruction-sequencesではなく, 特別な組合せ手続きtack-on-instruction-sequence (5.5.4節)を使う. それは本体は組み合せた列に入った時に実行する命令の部分ではないからである; むしろそれを置くのが便利というだけで列の中に置いてある.

   compile-lambda-bodyは, 手続き本体に対するコードを構成する. このコードは, 入り口のラベルで始る. 次には実行時の評価環境を, 手続き本体を評価する正しい環境---つまり仮パラメタの, 手続きが呼び出された時の引数との束縛を含むように拡張した, 手続きの定義環境---に切替える命令が来る. その後に手続き本体を形成する式の並びに対するコードが来る. この列は, 最後に手続きの結果をvalに置いて手続きから戻るように, 接続returnと標的valを持って翻訳される.

(define (compile-lambda-body exp proc-entry)
  (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 (lambda-body exp) 'val 'return))))



36 この手続きは, リストの構成に便利なLispのバッククォート(backquote) (または準クォート(quasiquote))の機能を使う. リストに先行するバッククォート記号は, リストをクォートするのに似ているが, リストの中でコンマで印つけられたものは評価される.

   例えばlinkageの値がbranch25なら, 式`((goto (label ,linkage)))はリスト((goto (label branch25)))に評価する. 同様にxの値がリスト(a b c)なら`(1 2 ,(car x))はリスト(1 2 a)に評価する.

37 プログラムには複数のifがあろうから, 上に示したようなラベルtrue-branch, false-branchおよびafter-ifをそのまま使うわけにはいかない. 翻訳系はラベルを生成するのに手続き make-labelを使う. make-label は記号を引数としてとり, その記号で始る新しい記号を返す. 例えば(make-label 'a)を繰り返し呼び出すとa1, a2などが返る. make-labelは質問言語で一意的変数名を生成したのと同様に次のように実装出来る.

(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)))))


38 4.1.3節に述べた合成手続きの構造に似た, 翻訳した手続きを表現するデータ構造を実装する機械命令が必要である.

(define (make-compiled-procedure entry env)
  (list 'compiled-procedure entry env))


(define (compiled-procedure? proc)
  (tagged-list? proc 'compiled-procedure))


(define (compiled-procedure-entry c-proc) (cadr c-proc))


(define (compiled-procedure-env c-proc) (caddr c-proc))


[ 目次, 前節, 次節, 索引 ]