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的暫存器的儲存與恢復,如上所示,在事實上,
是生成的,因為這些暫存器在判斷式的程式呼叫中被修改了,為程式呼叫所需要,和在
分支中的返回的連線符。