# 5.5.4组合指令序列

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

本节描述有关如何表示和组合指令序列的详细信息。 从 [5.5.1](115) 节中回顾，指令序列表示为所需寄存器，已修改寄存器和实际指令的列表。 我们还将标签（符号）看作是指令序列的简写，不需要或修改任何寄存器。 因此，为了确定指令序列所需和需要修改的寄存器，我们使用选择器

```js
function registers_needed(s) {
     return is_symbol(s) ? null : head(s);
}

function registers_modified(s) {
     return is_symbol(s) ? null : head(tail(s));
}

function statements(s) {
     return is_symbol(s) ? list(s) : head(tail(tail(s));
}
```

and to determine whether a given sequence needs or modifies a given register we use the predicates

```js
function needs_register(seq, reg) {
    return member(reg(registers_needed(seq))); /// FIXME: see https://github.com/source-academy/sicp/issues/170
}

function modifies_register(seq, reg) {
    return member(reg(registers_modified(seq)));
}
```

根据这些谓词和选择器，我们可以实现整个编译器中使用的各种指令序列组合器。

基本的组合器是`append_instruction_sequences`。 这将任意数量的要顺序执行的指令序列作为参数，并返回一个指令序列，该指令序列的语句是附加在一起的所有序列的语句。 精妙之处在于确定所需的寄存器，并根据结果序列对其进行修改。 它修改那些被任何序列修改的寄存器； 它需要那些必须在运行第一个序列之前必须初始化的寄存器（第一个序列所需的寄存器），以及未由其前一个序列初始化（修改）的任何其他序列所需的那些寄存器。

这些序列一次由`append_2_sequences`附加两个。 这需要两个指令序列`seq1`和`seq2`，并返回其指令为`seq1`语句的指令序列，然后返回`seq2`语句的指令序列，其修改后的寄存器为被`seq1`或 `seq2`及其需要的寄存器是`seq1`所需的寄存器以及`seq2`所需的未由`seq1`修改的寄存器。 （在设置操作方面，新的所需寄存器集是`seq1`所需的寄存器集与`seq2`所需的寄存器集和`seq1`修改的寄存器的集差的并集。） `append_instruction_sequences`的实现如下：

```js
function append_instruction_sequences( . seqs) {
    function append_2_sequences(seq1, seq2) {
        return make_instruction_sequence(
            list_union(registers_needed(seq1),
                       list_difference(registers_needed(seq2)
                                       registers_modified(seq1))),
            list_union(registers_modified(seq1),
                       registers_modified(seq2)),
            append(statements(seq1), statements(seq2)));
    }

    function append_seq_list(seqs) {
        return is_null(seqs)
            ? empty_instruction_sequence
            : append_2_sequences(
                head(seqs),
                append_seq_list(tail(seqs)));
    }

    return append_seq_list(seqs);
}
```

此函数使用一些简单的操作来操作以列表表示的集合，类似于 [2.3.3](37) 部分中描述的（无序）集合表示：

```js
function list_union(s1, s2) {
    return is_null(s1)
        ? s2
        : member(head(s1), s2)
        ? list_union(tail(s1), s2)
        : pair(head(s1), list_union(tail(s1), s2));
}

function list_difference(s1, s2) {
    return is_null(s1)
        ? null
        : member(head(s1), s2)
        ? list_difference(tail(s1), s2)
        : pair(head(s1), list_difference(tail(s1), s2));
}
```

第二主要指令序列组合器`Preserving`获取寄存器`regs`和将依次执行的两个指令序列`seq1`和`seq2`的列表。 它返回一个指令序列，该指令序列的语句为`seq1`语句，然后是`seq2`语句，并在`seq1`周围加上适当的`save`和`restore`指令以保护`regs`中的寄存器，这些寄存器被`seq1`，但`seq2`需要。 为此，`preserving`首先创建一个序列，该序列具有所需的`save`，后跟`seq1`的语句，后跟所需的`restore`。 除了`seq1`所需的寄存器之外，此序列还需要保存和恢复的寄存器，并修改`seq1`修改过的寄存器（保存和恢复的寄存器除外）。 然后以通常的方式附加该扩增的序列和`seq2`。 以下函数递归地实现此策略，在要保留的寄存器列表中查找： [[1]](118#footnote-1)

```js
function preserving(regs, seq1, seq2) {
    if (is_null(regs)) {
        return append_instruction_sequences(seq1, seq2)
    } else {
        const first_reg = head(regs);

        if (need_register(seq2, first_reg)  modifies_register(seq1, first_reg)) {
            return preserving(
                tail(regs),
                make_instruction_sequence(
                    list_union(list(first_reg),
                               registers_needed(seq1)),
                    list_difference(registers_modified(seq1),
                                    list(first_reg)),
                    append(list(save(first_reg)),
                           append(statements(seq1),
                                  list(restore(first_reg)))))
                seq2);
        } else {
            return preserving(tail(regs), seq1, seq2);
        }
    }
}
```

`compile_lambda`使用另一个序列组合器`tack_on_instruction_sequence`将功能体附加到另一个序列。 由于该函数体不是要在组合序列的一部分中执行的行中的&lt;quote&gt;，因此其寄存器使用对嵌入其的序列的寄存器使用没有影响。 因此，当我们将函数体附加到其他序列上时，我们将忽略它的需要和修改的寄存器集。&lt;/quote&gt;

```js
function tack_on_instruction_sequence(seq, body_seq) {
    return make_instruction_sequence(
        registers_needed(seq),
        registers_modified(seq),
        append(statements(seq), statements(body_seq)));
}
```

`Compile_if`和`compile_procedure_call`使用称为`parallel-instruction-sequences`的特殊组合器来附加测试之后的两个替代分支。 这两个分支将永远不会顺序执行； 对于测试的任何特定评估，将进入一个分支或另一个分支。 因此，组合序列仍然需要第二个分支所需的寄存器，即使这些寄存器被第一个分支修改了也是如此。

```js
function parallel_instruction_sequences(seq1, seq2) {
    return make_instruction_sequence(
        list_union(
            registers_needed(seq1),
            registers_needed(seq2)),
        list_union(
            registers_modified(seq1),
            registers_modified(seq2)),
        append(
            statements(seq1),
            statements(seq2)));
}
```

* * *

[[1]](118#footnote-link-1) Note that `preserving` calls `append` with three arguments. Though the definition of `append` shown in this book accepts only two arguments, Scheme standardly provides an `append` function that takes an arbitrary number of arguments.

