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

5.5.2 Compiling Expressions

In this section and the next we implement the code generators to which the

compile procedure dispatches.

Subheading: Compiling linkage code

本节及下一节将实现 compile 过程所分派的各个代码生成器。

小节标题:编译连接码

In general, the output of each code generator will end with

instructions—generated by the procedure compile-linkage—that

implement the required linkage. If the linkage is return then we must

generate the instruction (goto (reg continue)). This needs the

continue register and does not modify any registers. If the linkage is

next, then we needn’t include any additional instructions. Otherwise,

the linkage is a label, and we generate a goto to that label, an

instruction that does not need or modify any registers.

一般而言,每个代码生成器的输出都将以一段指令结尾——这段指令由过程 compile-linkage 生成,用于实现所需的连接。若连接为 return,则必须生成指令 `(goto (reg continue))`,它需要使用 continue 寄存器,且不修改任何寄存器。若连接为 next,则无需附加任何指令。否则,连接是一个标号,我们生成一条跳转到该标号的 goto 指令,该指令既不需要也不修改任何寄存器。

The linkage code is appended to an instruction sequence by preserving

the continue register, since a return linkage will require the

continue register: If the given instruction sequence modifies

continue and the linkage code needs it, continue will be saved

and restored.

连接码在追加到指令序列时,需要保护 continue 寄存器——因为 return 连接将用到 continue 寄存器:如果给定的指令序列修改了 continue,而连接码又需要它,则 continue 将会被保存和恢复。

Subheading: Compiling simple expressions

小节标题:编译简单表达式

The code generators for self-evaluating expressions, quotations, and variables

construct instruction sequences that assign the required value to the target

register and then proceed as specified by the linkage descriptor.

针对自求值表达式、引用和变量的代码生成器,所构造的指令序列会将所需的值赋给目标寄存器,然后按照连接描述符的规定继续执行。

All these assignment instructions modify the target register, and the one that

looks up a variable needs the env register.

所有这些赋值指令都会修改目标寄存器,而查找变量的那条指令还需要 env 寄存器。

Assignments and definitions are handled much as they are in the interpreter.

We recursively generate code that computes the value to be assigned to the

variable, and append to it a two-instruction sequence that actually sets or

defines the variable and assigns the value of the whole expression (the symbol

ok) to the target register. The recursive compilation has target

val and linkage next so that the code will put its result into

val and continue with the code that is appended after it. The appending

is done preserving env, since the environment is needed for setting or

defining the variable and the code for the variable value could be the

compilation of a complex expression that might modify the registers in

arbitrary ways.

赋值和定义的处理方式与解释器中的处理方式大体相同。我们递归地生成代码来计算待赋给变量的值,然后追加一条两指令序列,实际执行变量的设置或定义,并将整个表达式的值(符号 ok)赋给目标寄存器。递归编译的目标为 val,连接为 next,使代码将结果置入 val,并继续执行其后追加的代码。追加时需保护 env,因为设置或定义变量时需要环境,而计算变量值的代码可能是一段复杂表达式的编译结果,它可能以任意方式修改各个寄存器。

The appended two-instruction sequence requires env and val and

modifies the target. Note that although we preserve env for this

sequence, we do not preserve val, because the get-value-code is

designed to explicitly place its result in val for use by this sequence.

(In fact, if we did preserve val, we would have a bug, because this

would cause the previous contents of val to be restored right after the

get-value-code is run.)

Subheading: Compiling conditional expressions

追加的两指令序列需要 env 和 val,并修改目标寄存器。注意,虽然我们为该序列保护了 env,却没有保护 val——因为 get-value-code 被设计为明确地将其结果置入 val,供该序列使用。(实际上,若保护了 val,则会引入一个错误:这会导致 get-value-code 运行完毕后,val 的先前内容被恢复。)

小节标题:编译条件表达式

The code for an if expression compiled with a given target and linkage

has the form

以给定目标和连接编译 if 表达式所得的代码,具有如下形式

To generate this code, we compile the predicate, consequent, and alternative,

and combine the resulting code with instructions to test the predicate result

and with newly generated labels to mark the true and false branches and the end

of the conditional. In this arrangement of code, we must branch around the true branch if the

test is false. The only slight complication is in how the linkage for the true

branch should be handled. If the linkage for the conditional is return

or a label, then the true and false branches will both use this same linkage.

If the linkage is next, the true branch ends with a jump around the code

for the false branch to the label at the end of the conditional.

为生成这段代码,我们分别编译谓词、后件和替代件,然后将所得代码与检验谓词结果的指令,以及新生成的用于标记真分支、假分支和条件表达式结尾的标号组合在一起。在此代码安排中,若测试结果为假,我们必须绕过真分支跳转。唯一稍显复杂之处在于如何处理真分支的连接。若条件式的连接为 return 或某个标号,则真分支和假分支都使用同一连接。若连接为 next,则真分支末尾需跳过假分支的代码,跳转到条件式结尾处的标号。

Env is preserved around the predicate code because it could be needed by

the true and false branches, and continue is preserved because it could

be needed by the linkage code in those branches. The code for the true and

false branches (which are not executed sequentially) is appended using a

special combiner parallel-instruction-sequences described in

5.5.4.

在谓词代码周围保护 env,是因为真分支和假分支可能都会用到它;保护 continue,是因为这两个分支中的连接码可能会用到它。真分支和假分支(二者不按顺序执行)的代码使用特殊的组合器 parallel-instruction-sequences 来追加,该组合器将在 5.5.4 中描述。

Note that cond is a derived expression, so all that the compiler needs

to do handle it is to apply the cond->if transformer (from

4.1.2) and compile the resulting if expression.

Subheading: Compiling sequences

注意,cond 是一种派生表达式,因此编译器对它的处理仅需应用 cond->if 变换器(来自 4.1.2),再编译所得的 if 表达式即可。

小节标题:编译序列

The compilation of sequences (from procedure bodies or explicit begin

expressions) parallels their evaluation. Each expression of the sequence is

compiled—the last expression with the linkage specified for the sequence, and

the other expressions with linkage next (to execute the rest of the

sequence). The instruction sequences for the individual expressions are

appended to form a single instruction sequence, such that env (needed

for the rest of the sequence) and continue (possibly needed for the

linkage at the end of the sequence) are preserved.

序列(来自过程体或显式的 begin 表达式)的编译方式与其求值方式相对应。序列中的每个表达式都被单独编译——最后一个表达式使用为该序列指定的连接,其余表达式的连接均为 next(以便执行序列的其余部分)。各个表达式的指令序列被追加合并为一条单一的指令序列,在此过程中保护 env(后续表达式的求值需要它)和 continue(序列末尾的连接可能需要它)。

Subheading: Compiling lambda expressions

小节标题:编译 lambda 表达式

Lambda expressions construct procedures. The object code for a

lambda expression must have the form

lambda 表达式用于构造过程。lambda 表达式的目标代码必须具有如下形式

When we compile the lambda expression, we also generate the code for the

procedure body. Although the body won’t be executed at the time of procedure

construction, it is convenient to insert it into the object code right after

the code for the lambda. If the linkage for the lambda

expression is a label or return, this is fine. But if the linkage is

next, we will need to skip around the code for the procedure body by

using a linkage that jumps to a label that is inserted after the body. The

object code thus has the form

编译 lambda 表达式时,我们同时生成过程体的代码。虽然过程体在过程构造时不会被执行,但将它紧接在 lambda 的代码之后插入目标代码是很方便的。若 lambda 表达式的连接是标号或 return,这样做完全没有问题。但若连接为 next,则需要使用一个跳转到过程体之后所插入标号的连接,以绕过过程体的代码。目标代码因此具有如下形式

Compile-lambda generates the code for constructing the procedure object

followed by the code for the procedure body. The procedure object will be

constructed at run time by combining the current environment (the environment

at the point of definition) with the entry point to the compiled procedure body

(a newly generated label).

compile-lambda 生成用于构造过程对象的代码,其后紧跟过程体的代码。过程对象将在运行时通过将当前环境(即定义点处的环境)与编译后过程体的入口点(一个新生成的标号)组合而构造出来。

Compile-lambda uses the special combiner

tack-on-instruction-sequence rather than

append-instruction-sequences (5.5.4) to append the procedure body to the

lambda expression code, because the body is not part of the sequence of

instructions that will be executed when the combined sequence is entered;

rather, it is in the sequence only because that was a convenient place to put

it.

compile-lambda 使用特殊组合器 tack-on-instruction-sequence,而非 append-instruction-sequences(5.5.4),将过程体追加到 lambda 表达式代码之后——因为过程体并不属于进入该组合序列时将被顺次执行的指令序列的一部分;它之所以出现在序列中,只是因为这里是放置它的一个方便位置。

Compile-lambda-body constructs the code for the body of the procedure.

This code begins with a label for the entry point. Next come instructions that

will cause the run-time evaluation environment to switch to the correct

environment for evaluating the procedure body—namely, the definition

environment of the procedure, extended to include the bindings of the formal

parameters to the arguments with which the procedure is called. After this

comes the code for the sequence of expressions that makes up the procedure

body. The sequence is compiled with linkage return and target

val so that it will end by returning from the procedure with the

procedure result in val.

compile-lambda-body 构造过程体的代码。该代码以一个入口点标号开始,接下来是若干指令,将使运行时求值环境切换到对过程体求值所需的正确环境——即该过程的定义环境,并扩展了形式参数与调用时所传实参之间的绑定。此后是构成过程体的表达式序列的代码。该序列以连接 return 和目标 val 编译,使其在过程结束时返回,并将过程结果存于 val 中。

Racket #lang sicp
(define (compile-linkage linkage)
 (cond ((eq? linkage 'return)
 (make-instruction-sequence
 '(continue)
 '()
 '((goto (reg continue)))))
 ((eq? linkage 'next)
 (empty-instruction-sequence))
 (else
 (make-instruction-sequence '() '()
 `((goto (label ,linkage)))))))
Racket #lang sicp
(define (end-with-linkage
 linkage instruction-sequence)
 (preserving '(continue)
 instruction-sequence
 (compile-linkage linkage)))
Racket #lang sicp
(define (compile-self-evaluating
 exp target linkage)
 (end-with-linkage
 linkage (make-instruction-sequence
 '()
 (list target)
 `((assign ,target (const ,exp))))))

(define (compile-quoted exp target linkage)
 (end-with-linkage
 linkage
 (make-instruction-sequence
 '()
 (list target)
 `((assign
 ,target
 (const ,(text-of-quotation exp)))))))

(define (compile-variable
 exp target linkage)
 (end-with-linkage
 linkage
 (make-instruction-sequence
 '(env)
 (list target)
 `((assign ,target
 (op lookup-variable-value)
 (const ,exp)
 (reg env))))))
Racket #lang sicp
(define (compile-assignment
 exp target linkage)
 (let ((var (assignment-variable exp))
 (get-value-code
 (compile (assignment-value exp)
 'val
 'next)))
 (end-with-linkage
 linkage
 (preserving
 '(env)
 get-value-code
 (make-instruction-sequence
 '(env val)
 (list target)
 `((perform (op set-variable-value!)
 (const ,var)
 (reg val)
 (reg env))
 (assign ,target (const ok))))))))

(define (compile-definition
 exp target linkage)
 (let ((var (definition-variable exp))
 (get-value-code
 (compile (definition-value exp)
 'val
 'next)))
 (end-with-linkage
 linkage
 (preserving
 '(env)
 get-value-code
 (make-instruction-sequence
 '(env val)
 (list target)
 `((perform (op define-variable!)
 (const ,var)
 (reg val)
 (reg env))
 (assign ,target (const ok))))))))
Racket #lang sicp
⟨compilation of predicate,
 target val, linkage next⟩
 (test (op false?) (reg val))
 (branch (label false-branch))
true-branch
 ⟨compilation of consequent with given
 target and given linkage or after-if⟩
false-branch
 ⟨compilation of alternative
 with given target and linkage⟩
after-if
Racket #lang sicp
(define (compile-if exp target linkage)
 (let ((t-branch (make-label 'true-branch))
 (f-branch (make-label 'false-branch))
 (after-if (make-label 'after-if)))
 (let ((consequent-linkage
 (if (eq? linkage 'next)
 after-if
 linkage)))
 (let ((p-code
 (compile (if-predicate exp)
 'val
 'next))
 (c-code
 (compile (if-consequent exp)
 target
 consequent-linkage))
 (a-code
 (compile (if-alternative exp)
 target
 linkage)))
 (preserving
 '(env continue)
 p-code
 (append-instruction-sequences
 (make-instruction-sequence
 '(val)
 '()
 `((test (op false?) (reg val))
 (branch (label ,f-branch))))
 (parallel-instruction-sequences
 (append-instruction-sequences
 t-branch c-code)
 (append-instruction-sequences
 f-branch a-code))
 after-if))))))
Racket #lang sicp
(define (compile-sequence seq target linkage)
 (if (last-exp? seq)
 (compile (first-exp seq) target linkage)
 (preserving '(env continue)
 (compile (first-exp seq) target 'next)
 (compile-sequence (rest-exps seq)
 target
 linkage))))
Racket #lang sicp
⟨construct procedure object
 and assign it to target register⟩
⟨linkage⟩
Racket #lang sicp
⟨construct procedure object
 and assign it to target register⟩
 ⟨code for given linkage⟩ or
 (goto (label after-lambda))
 ⟨compilation of procedure body⟩
after-lambda
Racket #lang sicp
(define (compile-lambda exp target linkage)
 (let ((proc-entry
 (make-label 'entry))
 (after-lambda
 (make-label 'after-lambda)))
 (let ((lambda-linkage
 (if (eq? linkage 'next)
 after-lambda
 linkage)))
 (append-instruction-sequences
 (tack-on-instruction-sequence
 (end-with-linkage
 lambda-linkage
 (make-instruction-sequence
 '(env)
 (list target)
 `((assign
 ,target
 (op make-compiled-procedure)
 (label ,proc-entry)
 (reg env)))))
 (compile-lambda-body exp proc-entry))
 after-lambda))))
Racket #lang sicp
(define (compile-lambda-body exp proc-entry)
 (let ((formals (lambda-parameters exp)))
 (append-instruction-sequences
 (make-instruction-sequence
 '(env proc argl)
 '(env)
 `(,proc-entry
 (assign env
 (op compiled-procedure-env)
 (reg proc))
 (assign env
 (op extend-environment)
 (const ,formals)
 (reg argl)
 (reg env))))
 (compile-sequence (lambda-body exp)
 'val
 'return))))