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 中。
(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))))))) (define (end-with-linkage
linkage instruction-sequence)
(preserving '(continue)
instruction-sequence
(compile-linkage linkage))) (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)))))) (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)))))))) ⟨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 (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)))))) (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)))) ⟨construct procedure object
and assign it to target register⟩
⟨linkage⟩ ⟨construct procedure object
and assign it to target register⟩
⟨code for given linkage⟩ or
(goto (label after-lambda))
⟨compilation of procedure body⟩
after-lambda (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)))) (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))))