灯下 登录
计算机科学 / SICP / subsection 中英对照

5.5.5 An Example of Compiled Code

Now that we have seen all the elements of the compiler, let us examine an

example of compiled code to see how things fit together. We will compile the

definition of a recursive factorial procedure by calling compile:

现在我们已经了解了编译器的所有组成部分,让我们通过一个编译代码的实例来看看各部分是如何协同工作的。我们将通过调用 compile 来编译一个递归型阶乘过程的定义:

We have specified that the value of the define expression should be

placed in the val register. We don’t care what the compiled code does

after executing the define, so our choice of next as the linkage

descriptor is arbitrary.

我们指定了 define 表达式的值应被置于 val 寄存器中。至于编译代码在执行完 define 之后做什么,我们并不关心,因此将 next 作为连接描述符是任意的选择。

Compile determines that the expression is a definition, so it calls

compile-definition to compile code to compute the value to be assigned

(targeted to val), followed by code to install the definition, followed

by code to put the value of the define (which is the symbol ok)

into the target register, followed finally by the linkage code. Env is

preserved around the computation of the value, because it is needed in order to

install the definition. Because the linkage is next, there is no

linkage code in this case. The skeleton of the compiled code is thus

`compile` 判断该表达式是一个定义式,于是调用 `compile-definition` 来编译代码,依次生成:计算待赋值(目标为 val)的值的代码、安装该定义的代码、将 define 的值(即符号 ok)放入目标寄存器的代码,最后是连接代码。由于在计算该值时需要用到 env 以便安装定义,所以 env 在计算过程中会被保护。又因为连接为 next,本例中不存在连接代码。编译代码的框架因此为

The expression that is to be compiled to produce the value for the variable

factorial is a lambda expression whose value is the procedure

that computes factorials. Compile handles this by calling

compile-lambda, which compiles the procedure body, labels it as a new

entry point, and generates the instruction that will combine the procedure body

at the new entry point with the run-time environment and assign the result to

val. The sequence then skips around the compiled procedure code, which

is inserted at this point. The procedure code itself begins by extending the

procedure’s definition environment by a frame that binds the formal parameter

n to the procedure argument. Then comes the actual procedure body.

Since this code for the value of the variable doesn’t modify the env

register, the optional save and restore shown above aren’t

generated. (The procedure code at entry2 isn’t executed at this point,

so its use of env is irrelevant.) Therefore, the skeleton for the

compiled code becomes

待编译以产生变量 factorial 之值的表达式是一个 lambda 表达式,其值是计算阶乘的过程。`compile` 通过调用 `compile-lambda` 来处理它:`compile-lambda` 编译过程体,将其标记为新的入口点,并生成将过程体与运行时环境在新入口点处组合并将结果赋给 val 的指令。随后,代码序列跳过插入于此处的已编译过程代码。过程代码本身首先通过一个将形参 n 绑定到过程实参的框架来扩展过程的定义环境,接下来才是实际的过程体。由于计算该变量值的代码并不修改 env 寄存器,上面所示的可选 save 和 restore 不会被生成。(entry2 处的过程代码此时并未执行,所以其对 env 的使用无关紧要。)因此,编译代码的框架变为

A procedure body is always compiled (by compile-lambda-body) as a

sequence with target val and linkage return. The sequence in

this case consists of a single if expression:

过程体总是由 `compile-lambda-body` 以目标 val、连接 return 的方式编译为一个序列。本例中该序列由单个 if 表达式构成:

Compile-if generates code that first computes the predicate (targeted to

val), then checks the result and branches around the true branch if the

predicate is false. Env and continue are preserved around the

predicate code, since they may be needed for the rest of the if

expression. Since the if expression is the final expression (and only

expression) in the sequence making up the procedure body, its target is

val and its linkage is return, so the true and false branches are

both compiled with target val and linkage return. (That is, the

value of the conditional, which is the value computed by either of its

branches, is the value of the procedure.)

`compile-if` 生成的代码首先计算谓词(目标为 val),然后检查结果,若谓词为假则跳过真分支。由于 env 和 continue 在谓词代码执行后可能仍被 if 表达式的后续部分用到,它们在谓词代码周围会受到保护。由于该 if 表达式是构成过程体的序列中的最后一个(也是唯一一个)表达式,其目标为 val,连接为 return,因此真假两个分支都以目标 val、连接 return 进行编译。(即条件式的值——由其中某个分支计算得到——就是过程的值。)

The predicate (= n 1) is a procedure call. This looks up the operator

(the symbol =) and places this value in proc. It then assembles

the arguments 1 and the value of n into argl. Then it

tests whether proc contains a primitive or a compound procedure, and

dispatches to a primitive branch or a compound branch accordingly. Both

branches resume at the after-call label. The requirements to preserve

registers around the evaluation of the operator and operands don’t result in

any saving of registers, because in this case those evaluations don’t modify

the registers in question.

谓词 `(= n 1)` 是一次过程调用。它查找运算符(符号 `=`)并将其值置于 proc 中,然后将参数 `1` 和 n 的值组装到 argl 中,接着测试 proc 中存放的是基本过程还是复合过程,并据此分派到基本分支或复合分支。两个分支都在 after-call 标号处恢复执行。在对运算符和运算对象求值时保护寄存器的要求并不会导致任何寄存器的实际保存,因为在本例中那些求值操作并不修改相关寄存器。

The true branch, which is the constant 1, compiles (with target val and

linkage return) to

真分支是常量 `1`,以目标 val、连接 return 编译后得到

The code for the false branch is another procedure call, where the procedure

is the value of the symbol *, and the arguments are n and the

result of another procedure call (a call to factorial). Each of these

calls sets up proc and argl and its own primitive and compound

branches. Figure 5.17 shows the complete compilation of the definition

of the factorial procedure. Notice that the possible save and

restore of continue and env around the predicate, shown

above, are in fact generated, because these registers are modified by the

procedure call in the predicate and needed for the procedure call and the

return linkage in the branches.

Figure 5.17: ↓ Compilation of the definition of the factorial procedure.

假分支的代码是又一次过程调用,其中过程是符号 `*` 的值,参数是 n 以及另一次过程调用(对 factorial 的调用)的结果。这两次调用各自设置 proc 和 argl,并各自拥有基本分支和复合分支。图 5.17 给出了 factorial 过程定义的完整编译结果。注意,上面提到的对谓词周围 continue 和 env 的可能保存与恢复确实被生成了,因为这些寄存器会被谓词中的过程调用修改,而分支中的过程调用以及返回连接又需要用到它们。

图 5.17:↓ factorial 过程定义的编译结果。

Racket #lang sicp
(compile
 '(define (factorial n)
 (if (= n 1)
 1
 (* (factorial (- n 1)) n)))
 'val
 'next)
Racket #lang sicp
⟨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))
Racket #lang sicp
(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))
Racket #lang sicp
(if (= n 1)
 1
 (* (factorial (- n 1)) n))
Racket #lang sicp
⟨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
Racket #lang sicp
(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
Racket #lang sicp
(assign val (const 1))
(goto (reg continue))
Racket #lang sicp
;; construct the procedure and skip over code
;; for the procedure body
 (assign val
 (op make-compiled-procedure)
 (label entry2)
 (reg env))
 (goto (label after-lambda1))
entry2 ; calls to factorial will enter here
 (assign env
 (op compiled-procedure-env)
 (reg proc))
 (assign env
 (op extend-environment)
 (const (n))
 (reg argl)
 (reg env))
;; begin actual procedure body
 (save continue)
 (save env)
;; compute (= n 1)
 (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 ; val now contains result of (= n 1)
 (restore env)
 (restore continue)
 (test (op false?) (reg val))
 (branch (label false-branch4))
true-branch5 ; return 1
 (assign val (const 1))
 (goto (reg continue))

false-branch4
;; compute and return (* (factorial (- n 1)) n)
 (assign proc
 (op lookup-variable-value)
 (const *)
 (reg env))
 (save continue)
 (save proc) ; save * procedure
 (assign val
 (op lookup-variable-value)
 (const n)
 (reg env))
 (assign argl (op list) (reg val))
 (save argl) ; save partial argument list for *
;; compute (factorial (- n 1)),
;; which is the other argument for *
 (assign proc
 (op lookup-variable-value)
 (const factorial)
 (reg env))
 (save proc) ; save factorial procedure
;; compute (- n 1), which is the argument for factorial
 (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-branch8))
compiled-branch7
 (assign continue (label after-call6))
 (assign val
 (op compiled-procedure-entry)
 (reg proc))
 (goto (reg val))
primitive-branch8
 (assign val
 (op apply-primitive-procedure)
 (reg proc)
 (reg argl))

after-call6 ; val now contains result of (- n 1)
 (assign argl (op list) (reg val))
 (restore proc) ; restore factorial
;; apply factorial
 (test (op primitive-procedure?) (reg proc))
 (branch (label primitive-branch11))
compiled-branch10
 (assign continue (label after-call9))
 (assign val
 (op compiled-procedure-entry)
 (reg proc))
 (goto (reg val))
primitive-branch11
 (assign val
 (op apply-primitive-procedure)
 (reg proc)
 (reg argl))
after-call9 ; val now contains result
 ; of (factorial (- n 1))
 (restore argl) ; restore partial argument list for *
 (assign argl (op cons) (reg val) (reg argl))
 (restore proc) ; restore *
 (restore continue)
;; apply * and return its value
 (test (op primitive-procedure?) (reg proc))
 (branch (label primitive-branch14))
compiled-branch13
;; note that a compound procedure here
;; is called tail-recursively
 (assign val
 (op compiled-procedure-entry)
 (reg proc))
 (goto (reg val))
primitive-branch14
 (assign val
 (op apply-primitive-procedure)
 (reg proc)
 (reg argl))
 (goto (reg continue))
after-call12
after-if3
after-lambda1
;; assign the procedure to the variable factorial
 (perform (op define-variable!)
 (const factorial)
 (reg val)
 (reg env))
 (assign val (const ok))