# 5.5.5编译代码示例

> 译者： [https://sicp.comp.nus.edu.sg/chapters/119](https://sicp.comp.nus.edu.sg/chapters/119)

<subsection wip="yes">Note: this section is a work in progress!

既然我们已经了解了编译器的所有元素，那么让我们检查一下已编译代码的示例，看一看如何将它们组合在一起。 我们将通过调用`compile`来编译递归`factorial`函数的定义：

```js
compile(parse("                \
function factorial(n) {        \
    return n === 1             \
        ? 1"                   \
        : n * factorial(n - 1);\
}                              "),
       "val",
       "next");
```

我们指定了`define`表达式的值应放在`val`寄存器中。 我们不在乎执行`define`之后编译的代码会做什么，因此我们选择`next`作为链接描述符是任意的。

`Compile`确定表达式是一个定义，因此它调用`compile_definition`来编译代码以计算要分配的值（以`val`为目标），然后是安装定义的代码，然后是放置值的代码。 `define`的符号（即符号`ok`）放入目标寄存器，最后是链接代码。 `Env`保留在值的计算周围，因为安装定义需要它。 因为链接是`next`，所以在这种情况下没有链接代码。 因此，已编译代码的框架为`![%5Clangle%20%5Ctextit%7Bsave%7D](img/ff6189b16b1a0a444b1e86cc8c0ed8b3.jpg) env ![%5Ctextit%7Bif%20modified%20by%20code%20to%20compute%20value%7D%5Crangle](img/ec6fd0d684a24dab11a610555e20f68a.jpg) ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20definition%20value%2C%20target%7D](img/3e71cd436ae932d960ca25993daa79d1.jpg) val![%2C%20%5Ctextit%7Blinkage%7D](img/7b056c0adc9a8dd0f036bc8e49cd1a5a.jpg) next![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) ![%5Clangle%20%5Ctextit%7Brestore%7D](img/6af0617ab6e9711c73da0c5b6d2a7ee1.jpg) env ![%5Ctextit%7Bif%20saved%20above%7D%5Crangle](img/a96126d7bf92098a90714b73bcf2c3f6.jpg) perform( op("define_variable"), constant("factorial"), reg("val"), reg("env")); assign("val", constant(ok));`

将被编译以生成变量`factorial`的值的表达式是`lambda`表达式，其值是计算阶乘的函数。 `Compile`通过调用`compile_lambda`进行处理，该函数会编译函数体，将其标记为新的入口点，并生成将新入口点处的函数体与运行时环境结合起来并分配结果的指令 到`val`。 然后该序列跳过编译后的功能代码，该功能代码将在此时插入。 函数代码本身首先通过将框架的形式参数`n`绑定到函数参数的框架扩展函数的定义环境。 然后是实际的功能主体。 由于此变量值代码不会修改`env`寄存器，因此不会生成上面显示的可选`save`和`restore`。 （此时`entry2`的功能代码尚未执行，因此与`env`的使用无关。）因此，已编译代码的框架变为`assign("val", list(op("make_compiled_function"), label(entry2), reg("env"))), go_to(label("after_lambda1")), "entry2", assign("env", list(op("compiled_function_env"), reg("fun"))), assign("env", list(op("extend_environment"), constant(n), reg("argl"), reg("env"))), ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20procedure%20body%7D%20%5Crangle](img/5627ccc9676de43f8036ab5c2f90d90c.jpg) "after_lambda1", perform(op("define-variable"), constant("factorial"), reg("val"), reg("env")), assign("val", constant(ok)),`

始终将功能体编译（通过`compile_lambda_body`）为具有目标`val`和链接`return`的序列。 在这种情况下，序列由单个`if`表达式组成：

```js
n === 1 
    ? 1 
    : n * factorial(n - 1)
```

`Compile_if`生成代码，该代码首先计算谓词（以`val`为目标），然后检查结果，如果谓词为false，则在true分支附近分支。 `Env`和`continue`保留在谓词代码的周围，因为`if`表达式的其余部分可能需要它们。 由于`if`表达式是组成函数体的序列中的最终表达式（也是唯一的表达式），因此它的目标是`val`，其链接是`return`，因此，真假分支都使用目标`val`和链接`return`。 （也就是说，条件的值（由其两个分支中的任意一个分支计算得出的值就是函数的值）。）`![%5Clangle%20%5Ctextit%7Bsave%7D](img/ff6189b16b1a0a444b1e86cc8c0ed8b3.jpg) continue![%2C](img/81d9d10dcf0f38453fc636367c7cb95c.jpg) env ![%5Ctextit%7Bif%20modified%20by%20predicate%20and%20needed%20by%20branches%7D%20%5Crangle](img/d81dbe3aec8cc3ec914b429a5549e357.jpg) ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20predicate%2C%20target%7D](img/e5b5797ed3554c728eebba650230d67d.jpg) val![%2C%20%5Ctextit%7Blinkage%7D](img/7b056c0adc9a8dd0f036bc8e49cd1a5a.jpg) next![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) ![%5Clangle%20%5Ctextit%7Brestore%7D](img/6af0617ab6e9711c73da0c5b6d2a7ee1.jpg) continue![%2C](img/81d9d10dcf0f38453fc636367c7cb95c.jpg) env ![%5Ctextit%7Bif%20saved%20above%7D%5Crangle](img/a96126d7bf92098a90714b73bcf2c3f6.jpg) test(op("false"), reg("val")), branch(label("false_branch4")), "true_branch5", ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20true%20branch%2C%20target%7D](img/247164423541618822c25be0659f0eff.jpg) val![%2C%20%5Ctextit%7Blinkage%7D](img/7b056c0adc9a8dd0f036bc8e49cd1a5a.jpg) return![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) "false_branch4", ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20false%20branch%2C%20target%7D](img/be6726890355f1baaeefbb1f1ac2834a.jpg) val![%2C%20%5Ctextit%7Blinkage%7D](img/7b056c0adc9a8dd0f036bc8e49cd1a5a.jpg) return![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) "after_if3",`

谓词`n === 1`是一个函数调用。 这将查找运算符（符号`=`）并将该值放在`fun`中。 然后将参数`1`和`n`的值组合到`argl`中。 然后，它测试`fun`是否包含原始函数或复合函数，并相应地调度到原始分支或复合分支。 两个分支都以`after_call`标签恢复。 在运算符和操作数的评估周围保留寄存器的要求不会导致寄存器的任何保存，因为在这种情况下，这些评估不会修改相关的寄存器。

```js
assign("fun", list(op("lookup_variable_value"), constant("="), reg("env"))),
assign("val", constant(1)),
assign("argl", list(op("list"), reg("val"))),
assign("val", list(op("lookup_variable_value"), constant(n), reg("env"))),
assign("argl", list(op("cons"), reg("val"), reg("argl"))),
test(op("primitive_function"), reg("fun")),
branch(label("primitive_branch17")),
"compiled_branch16",
assign("continue", list(label("after_call15"))),
assign("val", list(op("compiled_function_entry"), reg("fun"))),
go_to(reg("val")),
"primitive_branch17",
assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))),
"after_call15",
```

真正的分支（即常量1）编译（具有目标`val`和链接`return`）为

```js
assign("val", constant(1)),
go_to(reg("continue")),
```

false分支的代码是另一个函数调用，其中函数是符号`*`的值，参数是`n`和另一个函数调用（对`factorial`的调用）的结果。 这些调用中的每一个都设置`fun`和`argl`以及它自己的原始分支和复合分支。 图 **图中未找到磅的标签：comp-factorial1** 显示了`factorial`函数定义的完整汇编。 请注意，实际上在谓词周围生成了`continue`和`env`的可能的`save`和`restore`，因为这些寄存器是由谓词中的函数调用修改的，而函数调用和 分支中的`return`链接。

<exercise>Consider the following definition of a factorial function, which is slightly different from the one given above:

```js
function factorial_alt(n) {
    return  n === 1  
        ? 1
        : factorial_alt(n - 1) * n;
}
```

Compile this function and compare the resulting code with that produced for `factorial`. Explain any differences you find. Does either program execute more efficiently than the other?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_119_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Compile the iterative factorial function

```js
function factorial(n) {
    function iter(product, counter) {
        return counter > n
            ? product
            : iter(product * counter, counter + 1);
    }

    return iter(1, 1);
}
```

Annotate the resulting code, showing the essential difference between the code for iterative and recursive versions of `factorial` that makes one process build up stack space and the other run in constant stack space.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_119_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>What order of evaluation does our compiler produce for operands of a combination? Is it left-to-right, right-to-left, or some other order? Where in the compiler is this order determined? Modify the compiler so that it produces some other order of evaluation. (See the discussion of order of evaluation for the explicit-control evaluator in section <ref name="sec:eceval-core">[5.4.1](110)</ref>.) How does changing the order of operand evaluation affect the efficiency of the code that constructs the argument list?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_119_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>One way to understand the compiler's `preserving` mechanism for optimizing stack usage is to see what extra operations would be generated if we did not use this idea. Modify `preserving` so that it always generates the `save` and `restore` operations. Compile some simple expressions and identify the unnecessary stack operations that are generated. Compare the code to that generated with the `preserving` mechanism intact.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_119_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Our compiler is clever about avoiding unnecessary stack operations, but it is not clever at all when it comes to compiling calls to the primitive functions of the language in terms of the primitive operations supplied by the machine. For example, consider how much code is compiled to compute `a + 1`: The code sets up an argument list in `argl`, puts the primitive addition function (which it finds by looking up the symbol `+` in the environment) into `fun`, and tests whether the function is primitive or compound. The compiler always generates code to perform the test, as well as code for primitive and compound branches (only one of which will be executed). We have not shown the part of the controller that implements primitives, but we presume that these instructions make use of primitive arithmetic operations in the machine's data paths. Consider how much less code would be generated if the compiler could _open-code_ primitives—that is, if it could generate code to directly use these primitive machine operations. The expression `a + 1` might be compiled into something as simple as[[1]](119#footnote-1)

```js
assign("val", op("lookup-variable-value"), constant("a"), reg("env")),
assign("val", op("+"), reg("val"), constant(1)),
```

In this exercise we will extend our compiler to support open coding of selected primitives. Special-purpose code will be generated for calls to these primitive functions instead of the general function-application code. In order to support this, we will augment our machine with special argument registers `arg1` and `arg2`. The primitive arithmetic operations of the machine will take their inputs from `arg1` and `arg2`. The results may be put into `val`, `arg1`, or `arg2`. The compiler must be able to recognize the application of an open-coded primitive in the source program. We will augment the dispatch in the `compile` function to recognize the names of these primitives in addition to the reserved words (the special forms) it currently recognizes.[[2]](119#footnote-2) For each special form our compiler has a code generator. In this exercise we will construct a family of code generators for the open-coded primitives.

1.  与特殊形式不同，开放编码原语都需要评估其操作数。 编写一个代码生成器`spread_arguments`，以供所有开放式代码生成器使用。 `Spread_arguments`应该获取一个操作数列表，并编译针对连续参数寄存器的给定操作数。 请注意，操作数可能包含对开放式编码原语的调用，因此在操作数求值期间必须保留参数寄存器。
2.  对于每个基本函数`=`，`*`，`-`和`+`，编写一个代码生成器，该生成器与该运算符结合使用，并结合一个目标和一个链接描述符，并生成代码以扩展 将参数传递给寄存器，然后使用给定的链接执行针对给定目标的操作。 您只需要处理带有两个操作数的表达式。 使`compile`分派给这些代码生成器。
3.  在`factorial`示例上尝试使用新的编译器。 将结果代码与没有开放代码的结果进行比较。
4.  扩展`+`和`*`的代码生成器，以便它们可以处理具有任意数量的操作数的表达式。 具有两个以上操作数的表达式将必须编译为一系列操作，每个操作只有两个输入。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_119_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

* * *

[[1]](119#footnote-link-1) We have used the same symbol `+` here to denote both the source-language function and the machine operation. In general there will not be a one-to-one correspondence between primitives of the source language and primitives of the machine.

[[2]](119#footnote-link-2) Making the primitives into reserved words is in general a bad idea, since a user cannot then rebind these names to different functions. Moreover, if we add reserved words to a compiler that is in use, existing programs that define functions with these names will stop working. See exercise <ref name="ex:cte-open-code">[5.43](120#ex_5.43)</ref> for ideas on how to avoid this problem.

