1. 程式人生 > >5.5.5 編譯後的程式碼的例子

5.5.5 編譯後的程式碼的例子

5.5.5 編譯後的程式碼的例子
現在我們已經看到了編譯器的所有的元素,讓我們檢查一個編譯過的程式碼的例子,
來看一看如何把它組合在一起的。通過呼叫compile,我們將編譯一個遞迴性的
階乘程式的定義:

(compile
 '(define (factorial n)
    (if (= n 1)
        1
        (* (factorial (- n 1)) n)))
 'val
 'next)

我們已經指定了定義的表示式的值應該被放在val暫存器中。我們沒關注執行了define之後,
編譯過的程式碼做什麼,所以我們選擇next作為連線的描述符。

compile確定了一個表示式是一個定義,所以它呼叫了compile-definition來編譯程式碼,
計算被賦值的值,接下來安裝定義的程式碼,再是把define的值放到目標暫存器的程式碼,
最後是連線描述符的程式碼。在值的計算前後,env暫存器被保留,因為為了安裝定義它是被需要的。
因為連線符是next,在這個例子中,沒有連線的程式碼。編譯過的程式碼的骨架如下:

<save env if modified by code to compute value>
  <compilation of definition value, target val, linkage next>
  <restore env if saved above>
  (perform (op define-variable!)
           (const factorial)
           (reg val)
           (reg env))
  (assign val (const ok))

被編譯的表示式生成了階乘的變數的值是一個lambda表示式,這個lambda表示式的值是
計算階乘的程式。compile通過呼叫compile-lambda處理這個程式,它編譯程式體,對它作
標籤作為一個新的入口點,並且生成了指令,這個指令將在新的入口點組合程式體和執行時環境,
並且把結果賦值給val. 序列然後跳過了被編譯的程式程式碼,它在這個點上被插入。程式程式碼本身
開始於通過擴充套件程式的定義的環境,這個環境是一個幀把形式引數與程式的實際引數繫結在一起。
然後,來到了實際的程式體。因為對於變數的值 沒有修改暫存器,程式碼沒有生成如上所示的可選的
儲存與恢復指令。(程式程式碼在entry2在這個點上沒有執行,所以env的使用是不相關的)因此,
這個編譯的程式碼的骨架是如下的樣子:

  (assign val (op make-compiled-procedure)
              (label entry2)
              (reg env))
  (goto (label after-lambda1))
entry2
  (assign env (op compiled-procedure-env) (reg proc))
  (assign env (op extend-environment)
              (const (n))
              (reg argl)
              (reg env))
  <compilation of procedure body>
after-lambda1
  (perform (op define-variable!)
           (const factorial)
           (reg val)
           (reg env))
  (assign val (const ok))

一個程式體總是被編譯為一個序列,帶有一個目標val和連線符返回。在這個情況下,一個單獨
的條件表示式的序列如下:

(if (= n 1)
    1
    (* (factorial (- n 1)) n))

compile-if生成了程式碼,它首先計算判斷式(目標是val)然後檢查結果與如果判斷式是假,分支繞過真值的分支。
env 和continue暫存器在判斷式的程式碼前後被保留,因為它們可能被條件表示式的其它部分所需要。因為條件表示式是
組成程式體的序列中的最後一個表示式(並且是唯一的表示式),它的目標是val,它的連線符是return,所以真與假的分支
都被編譯帶有目標是val,連線符是return。(也就是,條件的值,是任何一個分支的計算出來的值,也是程式的值)

 <save continue, env if modified by predicate and needed by branches>
  <compilation of predicate, target val, linkage next>
  <restore continue, env if saved above>
  (test (op false?) (reg val))
  (branch (label false-branch4))
true-branch5
  <compilation of true branch, target val, linkage return>
false-branch4
  <compilation of false branch, target val, linkage return>
after-if3

判斷式(=  n  1)是一個程式呼叫。這查詢操作符(符號=)和放這個值到proc.
它然後彙編實際引數1,把n的值放到arg1.然後它測試proc是否包括了一個原生
或者複合的程式,再相應地把它分發到一個原生分支或者是複合程式的分支。
兩個分支都在after-call標籤處返回了。在操作符與運算元的解釋前後保留暫存器
的需要沒有在任何暫存器的儲存中有結果, 因為在這個情況下,解釋沒有修改暫存器。

 (assign proc
          (op lookup-variable-value) (const =) (reg env))
  (assign val (const 1))
  (assign argl (op list) (reg val))
  (assign val (op lookup-variable-value) (const n) (reg env))
  (assign argl (op cons) (reg val) (reg argl))
  (test (op primitive-procedure?) (reg proc))
  (branch (label primitive-branch17))
compiled-branch16
  (assign continue (label after-call15))
  (assign val (op compiled-procedure-entry) (reg proc))
  (goto (reg val))
primitive-branch17
  (assign val (op apply-primitive-procedure)
              (reg proc)
              (reg argl))
after-call15

真值的分支,它是常數1,編譯(帶有目標val 和連線符 return)為

  (assign val (const 1))
  (goto (reg continue))

對於假值的分支的程式碼是另一個程式呼叫,它的程式是符號*的值,並且實際引數是n,
另一個程式呼叫的結果(一個對階乘的呼叫)。這些呼叫的每一個都安裝proc,arg1和
它自己的原生和複合的分支。圖5.17顯示了階乘程式的定義的完成的編譯。注意在判斷式
前後的可能的env和continue的暫存器的儲存與恢復,如上所示,在事實上,
是生成的,因為這些暫存器在判斷式的程式呼叫中被修改了,為程式呼叫所需要,和在
分支中的返回的連線符。