next up previous
Next: 10 リード,コンパイル,実行ループの実装 Up: 9 生成コードの実行 Previous: 9.1 基本組み込み関数の定義

9.2 コード・インタプリタ

コード生成されたものを実行する部分を考えます.

(defun arg1 (instr) (if (listp instr) (cadr instr)))
(defun arg2 (instr) (if (listp instr) (caddr instr)))

(defun scheme-machine (f)
  (let* ((code (fn-code f))
         (pc 0)
         (env nil)
         (stack nil)
         (n-args 0)
         (instr))
    (loop
     (setf instr (elt code pc))  ;; fetch
     (incf pc)
     (case
      (car instr)
      (lvar (push (lvar instr env) stack))
      (lset
       (setf (lvar instr env) (car stack)))
      (gvar 
       (push (gvar instr) stack))
      (gset 
       (setf (gvar instr) (car stack)))
      (pop  (pop stack))
      (const (push (arg1 instr) stack))
      
      (jump (setf pc (arg1 instr)))
      (fjump (if (null (pop stack))
                 (setf pc (arg1 instr))))
      (return
       (let
           ((val (pop stack)) (ra (pop stack)))
         (setf
          f (return-address-fn ra)
          code (fn-code f)
          env (return-address-env ra)
          pc (return-address-pc ra))
         (push val stack)))
      (call
       (let ((ra (make-return-address
                  :pc pc :fn f :env env)))
         (setf f (pop stack)
               code (fn-code f)
               env (fn-env f)
               n-args (arg1 instr)
               pc 0)
         (push ra stack)))
      (args 
       (let ((ra (pop stack)))
         (push (make-array (arg1 instr)) env)
         (do
          ((i (1- n-args) (1- i)))
          ((< i 0) nil)
          (setf (elt (car env) i) (pop stack)))
         (push ra stack)))

      (fn 
       (push (make-fn :code (fn-code (arg1 instr))
                      :env env)
             stack))
      (prim
       (let ((args nil) (ra (pop stack)))
         (dotimes (i n-args) (push (pop stack) args))
         (push ra stack)
         (push (apply (arg1 instr) args)
               stack)
         ))
      (otherwise (error "unknown opcode: ~a" instr))
      )))
  )
局所変数と大域変数をとってくる 関数とその変数の場所への代入を setfで行うためのdefsetfの定義は以下のようになります.

(defun lvar (instr env)
  (elt
   (elt env (arg1 instr))
   (arg2 instr)))

(defsetf lvar (instr env) (val)
  `(setf 
       (elt
        (elt ,env (arg1 ,instr))
        (arg2 ,instr))
     ,val))

(defun gvar (instr)
  (get (arg1 instr) 'scheme-global-value
       "unbound"))

(defsetf gvar (instr) (val)
  `(setf (get (arg1 ,instr)
              'scheme-global-value "unbound")
     ,val))
サブルーチンを呼び出す側は,サブルーチンに渡す引数と 戻り番地をスタックにいれてそのサブルーチンをcallします. call命令は,戻り番地データを戻り番地スタックにつみ, return命令は,戻り番地データを戻します. 戻り番地データというのは,現在処理している関数f, そのfの中でのプログラムコードアドレスpc, その関数fの中での環境変数リストenvになります. これらをまとめたデータ構造return-address を構造体で用意しています.

(defstruct return-address fn pc env)
呼び出されたサブルーチンは,引数を配列に入れ, その配列を環境変数リスト(env)に追加し, 内部処理を行った後,return命令で戻ります. argsコードは引数用のスタックから引数配列をつくりenvリストに 追加することを行います. argsコードの第一引数がenvリストの先頭からの場所の数で, 第二引数がその引数配列の中の順番をさします. サブルーチンの内部処理で変数が出てきた場合に, 大域変数は,そのシンボルのscheme-global-value属性値を使いますが, 局所変数は,この環境変数リストenvの中に記憶されていて, その配列の要素へアクセスします. 環境変数envには,新しいサブルーチンに入るたびに, そのサブルーチンの引数配列がpushされています. 局所変数は,envのリストの先頭から数えた段数が 現在のサブルーチンの外への段数になります.

generated through LaTeX2HTML. M.Inaba 平成18年5月6日