您为特定指令集构建编译器,该指令集是所选“指令集架构 (ISA)”的子集。(许多指令集都有 I/O 指令,但编译器几乎从不生成这些指令)。可能有几种不同的处理器设计来执行这种“指令集架构”,它们将与您选择的特定指令子集一起工作。
在实践中会发生三种进化事件。
您确定如果使用更多来自 ISA 的指令,您的编译器会更好。例如,您可能决定 MULTIPLY 指令将允许您的编译器生成比您过去用于乘法的子例程调用更快的代码。在这种情况下,您稍微扩展编译器。
ISA 的所有者(英特尔、AMD、IBM ......)将全新的指令集添加到 ISA。例如,对数据高速缓存行的数据并行操作(“SIMD 指令”)。您可以决定将其中一些添加到您的编译器中。这个事件可能很困难,因为新的指令系列通常会对数据的布局和处理方式做出不同的假设。
您会找到一些完全不同的 ISA 来处理。在这种情况下,您将重建编译器的后端,因为指令集在存在哪些寄存器、如何使用它们等方面完全不同。
编译器构建器通常构建编译器以分阶段运行。在生成实际机器代码之前的最后一个阶段通常将程序表示为对相当低级数据的一组抽象操作(例如,对固定字长值的操作),具有相当标准的抽象操作(ADD、MULTIPLY、COMPARE、JUMP、 CALL, STORE, LOAD, ...) 对实际 ISA 没有任何承诺(尤其是没有关于寄存器或特定机器指令的承诺)。这样做可以独立于 ISA 进行更高级别的优化;只需将其视为良好的模块化。最后几个阶段专门针对 ISA;通常在阶段分配寄存器,然后是模式匹配实际指令与抽象指令的阶段。
有整本书是关于更高层次的优化的,还有其他关于最终代码生成状态的书(而且通常是在单独的章节中讨论这两个问题的书)。[Aho&Ullman Dragon 的书和 Torczon 的 Engineering a Compiler 在这两个主题上都是相当不错的书)。有很多技术可以让人们写下最终的指令集和寄存器布局,并将生成大部分最后阶段;海湾合作委员会有这样的。那项技术很复杂,不适合这句话;最好去看看书。
一旦您以这种方式获得了适用于第一个 ISA 的编译器,您就可以使用相同的技术构建一个变体。您最终会得到两个物理编译器,每个 ISA 一个。他们共享所有前端逻辑和抽象代码生成和优化。它们在最后阶段完全不同。
您应该了解的是,构建编译器以利用指令集是一个复杂的过程。