svm.c で動かすための階乗再帰版のハンド・コンパイル

svm.c には階乗の再帰呼び出しとループの2通りで計算する手続きを、それぞれハンド・コンパイルとハンド・アセンブルして C 言語のソースコードに埋め込みました。

ここでは再帰呼び出し版のハンド・コンパイル手順をまとめます。

(define (factrec n)
  (if (<= n 1)
    1
    (* n (factrec (- n 1)))))
(factrec 5)

まずは、トップレベルです。factrec のラムダ記述をコンパイルした結果が factrec-codeseg に得られているとして、それを使って手続きを作り変数 factrec を束縛します。続いて、その手続き適用をおこないます。

(svm-execute
  (let ((factrec '(0 0)))
;@< factrec をアセンブルして factrec-codeseg を求める@>
    (svm-assemble `(
    ; (define (factrec n) . factrec-body)
        (PROC   ,factrec-codeseg 1)
        (STORE  ,@factrec)
    ; (factrec 5)
        (EXTEND ,@factrec)
        (LIT    5)
        (ARG    0)
        (APPL   ,@factrec))))
  1 ; 開始オフセット
  (default-environment)) 

次に factrec のラムダ記述をコンパイルします。何はともあれ A 正規形にします。この程度のソースコードなら手作業ですぐに変換できます。

(lambda (n)
  (let ((t0 (<= n 1)))
    (if t0
      1
      (let ((t1 (- n 1)))
        (let ((t2 (factrec t1)))
          (* n t2))))))

一時変数の割付をします。一時変数は val レジスタと環境フレームのスロットへ割り付けます。

(lambda (n)
  (let ((val (<= n 1)))
    (if val
      1
      (let ((val (- n 1)))
        (let ((val (factrec val)))
          (* n val))))))

コードを生成します。A 正規形向けに命令を決めたので、val レジスタの破壊操作があることに注意していさえすれば、単純作業でコードを書き下すことができます。

;@<factrec をアセンブルして factrec-codeseg を求める@>=
(define factrec-codeseg
  (let ((n '(0 0)) (factrec '(1 0)))
    (svm-assemble `(
    ; (lambda (n) ...)
        (LIT    1)
    ; (let ((t0 (<= n 1)))
        (LOAD   ,@n)
        (AR0)
        (LIT    1)
        (AR1)
        (LEI)
    ; (if val
        (UNLESS L1)
    ; 1
        (LIT    1)
        (CONT)
    ; (let ((val (- n 1)))
    L1  (LOAD   ,@n)
        (AR0)
        (LIT    1)
        (AR1)
        (MINUSI)
    ; (let ((val (factrec val)))
        (EXTEND ,@factrec)
        (ARG    0)
        (CALL   ,@factrec)
    ; (* n val)))))))
        (AR1)           ; LOAD 命令が val レジスタを破壊するので、その前に r1 へ書き込む。
        (LOAD   ,@n)
        (AR0)
        (TIMESI)
        (CONT)))))

これをハンド・アセンブルして、svm.c の factrec 関数に埋め込みました。