SICP 読み (242) 5.1 レジスタ計算機の設計

問題 5.5

正に Hand Simulate な手法で試みてみます。
しかも数値を順に上げてみる。最初は 1 で (0 は略)

   (assign continue (label fib-done)) ;; 最初は fib-done を格納

   (test (op <) (reg n) (const 2))    ;; n は 1
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val に n の値 (1) が格納
   (goto (reg continue))	      ;; fib-done に jmp

次、n が 2 の場合

   (assign continue (label fib-done)) ;; 最初は fib-done を格納

   (test (op <) (reg n) (const 2))    ;; n は 2 なので branch はスルー

   (save continue)                    ;; push して afterfib-n-1 を設定
   (assign continue (label afterfib-n-1))
   (save n)                           ;; 2 な n を push
   (assign n (op -) (reg n) (const 1));; n に (- n 1) をセット
   (goto (label fib-loop))            ;; fib-loop に戻る

				      ;; (1)

   (test (op <) (reg n) (const 2))    ;; n の値は 1
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val に 1 を格納
   (goto (reg continue))              ;; continue は afterfib-n-1

   (restore n)                        ;; n を pop すると 2
   (restore continue)                 ;; continue は fib-done に

				      ;; (2)

   ;; set up to compute Fib(n - 2)
   (assign n (op -) (reg n) (const 2));; n に (- n 2) をセット
   (save continue)                    ;; 再度 fib-done を push
                                      ;; continue は afterfib-n-2 に
   (assign continue (label afterfib-n-2))
   (save val)                         ;; 1 な val を push
   (goto (label fib-loop))            ;; fib-loop に戻る

				      ;; (3)

   (test (op <) (reg n) (const 2))    ;; n は 0
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val が 0 に
   (goto (reg continue))              ;; afterfib-n-2 に jmp

   (assign n (reg val))               ;; n に 0 が格納
   (restore val)                      ;; val を pop (1 に)
   (restore continue)                 ;; continue は fib-done
   (assign val                        ;; (+ 1 0) を val にセット
           (op +) (reg val) (reg n))  
   (goto (reg continue))              ;; fib-done に jmp

stack の状態確認してみるか。ってか、このレジスタ計算機というヤツは stack は一つなんだよな。順番もおそらく意識されているはず。まず (1) の stack の状態。リストで表現しちゃれ。

(2 fib-done)

で、(2) の直前で restore してるから stack は空ッスか??

()

次に (3) の直前で val と fib-done を save しています。

(1 fib-done)

最後に val、continue の順で pop している。
これはサブルーチン呼び出し元のレジスタの値と戻り番地を stack に push しといてサブルーチンに制御を渡してる、と見て良いのかなぁ。で、上記の (fib 2) の場合は (fib 1) から戻って n と continue を復帰 (pop) しているのが分かる。
次に (fib 0) を呼び出すんだけど、そん時に退避してるのは continue と val だな。パターンとしては fib なツリーの left branch な呼び出しの時には n と continue を save して right branch な呼び出し時には val と continue を save しているのでしょうか。ソース見るにそんな感じですな。(fib 3) も見てみます。

   (assign continue (label fib-done)) ;; 最初は fib-done を格納

   (test (op <) (reg n) (const 2))    ;; n は 3 なので branch はスルー

   (save continue)                    ;; push して afterfib-n-1 を設定
   (assign continue (label afterfib-n-1))
   (save n)                           ;; 3 な n を push
   (assign n (op -) (reg n) (const 1));; n に (- n 1) をセット
   (goto (label fib-loop))            ;; fib-loop に戻る

                                      ;; stack condition is
                                      ;; (3 fib-done)

   (test (op <) (reg n) (const 2))    ;; n は 2 なので branch はスルー

   (save continue)                    ;; push されるのは afterfib-n-1
                                      ;; continue には afterfib-n-1 がセット
   (assign continue (label afterfib-n-1))
   (save n)                           ;; 2 な n を push
   (assign n (op -) (reg n) (const 1));; n に (- n 1) をセット
   (goto (label fib-loop))            ;; fib-loop に戻る

                                      ;; stack condition is
                                      ;; (2 afterfib-n-1 3 fib-done)

   (test (op <) (reg n) (const 2))    ;; n の値は 1
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val に 1 を格納
   (goto (reg continue))              ;; continue は afterfib-n-1

   (restore n)                        ;; n を pop すると 2
   (restore continue)                 ;; continue は afterfib-n-1

                                      ;; stack condition is
                                      ;; (3 fib-done)

   (assign n (op -) (reg n) (const 2));; n に (- n 2) をセット
   (save continue)                    ;; afterfib-n-1 を save
                                      ;; continue は afterfib-n-2
   (assign continue (label afterfib-n-2))
   (save val)                         ;; 1 な val を退避
   (goto (label fib-loop))

                                      ;; stack condition is
                                      ;; (1 afterfib-n-1 3 fib-done)

   (test (op <) (reg n) (const 2))    ;; n の値は 0
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val に 0 をセット
   (goto (reg continue))              ;; afterfib-n-2 に jmp

   (assign n (reg val))               ;; val の値を n に退避
   (restore val)                      ;; val を復帰 (1 に)
   (restore continue)                 ;; continue 復帰 (afterfib-n-1 に)
   (assign val                        ;; (+ val n) を val にセット
           (op +) (reg val) (reg n)) 
   (goto (reg continue))              ;; afterfib-n-1 に jmp
                                      ;; コメントに return to caller とある

                                      ;; stack condition is
                                      ;; (3 fib-done)

   (restore n)                        ;; n を復帰 (3 に)
   (restore continue)                 ;; continue を復帰 (fib-done に)
   ;; set up to compute Fib(n - 2)
   (assign n (op -) (reg n) (const 2));; n に (- n 2) をセット
   (save continue)                    ;; continue (fib-done) を退避して
                                      ;; continue を afterfib-n-2 に
   (assign continue (label afterfib-n-2))
   (save val)                         ;; val (1) を退避
   (goto (label fib-loop))

                                      ;; stack condition is
                                      ;; (1 fib-done)

   (test (op <) (reg n) (const 2))    ;; n の値は 1
   (branch (label immediate-answer))  ;; immediate-answer に jmp

   (assign val (reg n))               ;; val に 1 をセット
   (goto (reg continue))              ;; afterfib-n-2 に jmp

   (assign n (reg val))               ;; val の値を n に退避
   (restore val)                      ;; val を復帰 (1 に)
   (restore continue)                 ;; continue を復帰 (fib-done に)
   (assign val                        ;; val に (+ val n) をセット
           (op +) (reg val) (reg n))  ;; val の値は 2 になる
   (goto (reg continue))              ;; fib-done に jmp

よく木だけじゃなくって森を見れ、みたいなコトを言ってるんですが、直列な手続きでソレをやるのは結構キビしいなぁ。ってか慣れてないよ。ただ、left だの right だのという特性はビンゴに見える。
ただ、次の問題にある_余分な save と余分な restore_ってのはまだ見えてません。(immediate-answer??) とりあえず控えを投入しておく、という事で。

追記

テキストの 21p. にあるような fib のマンガを見つつポイントを列挙してみたので控えを以下に出力。

  • left-branch の前な処理 (fib-loop なブロック)
    • 全ての分岐で実行される
    • 末端も同様だが条件分岐で immediate-answer に jmp
    • push continue (基本的には afterfib-n-1)
    • push n
    • その後、n に (- n 1) をセットして fib-loop に jmp
  • right-branch の前 (left-branch の後) な処理 (afterfib-n-1 なブロック)
    • pop n
    • pop continue
    • push continue
    • push val
    • continue の取り扱いは微妙 (これも問題 5.6 な解の一つ??)
    • n に (- n 2) を、continue に afterfib-n-2 をセットして fib-loop に jmp
  • right-branch の後 (afterfib-n-2 なブロック)
    • n <- val
    • pop val
    • pop continue
    • val に (+ val n) をセットして continue に jmp
  • 末端の処理 (n が 0 又は 1 の場合)
    • val <- n (n は 0 又は 1)
    • continue に jmp

上記を見ても早くなり得る余分なソレは afterfib-n-1 にある continue の復帰とその直後の退避だけに見えます。他にもあるのかなぁ。無いハズなんですが ...

追記

以下の一連の手続きについてメモ。

(define (assemble controller-text machine)
  (extract-labels controller-text
    (lambda (insts labels)
      (update-insts! insts labels machine)
      insts)))

(define (extract-labels text receive)
  (if (null? text)
      (receive '() '())
      (extract-labels (cdr text)
       (lambda (insts labels)
         (let ((next-inst (car text)))
           (if (symbol? next-inst)
               (receive insts
                        (cons (make-label-entry next-inst
                                                insts)
                              labels))
               (receive (cons (make-instruction next-inst)
                              insts)
                        labels)))))))

合ってるかどうかは微妙ですが。
extract-labels の中で再帰的に呼び出されてる extract-labels に渡されている手続きオブジェクトが持ってる receive は assemble の中で extract-labels に渡されている

    (lambda (insts labels)
      (update-insts! insts labels machine)
      insts)

てーコトになると見てるんですがダウト??
てか、脚注で_継続_な単語が出ているんですが何だこれは。