



在select_instructions编译遍中,我们开始将 C Var 语言转换为x86 Var 语言。此遍的目标语言是仍然使用变量的x86汇编的一种变体,因此我们要将形如(Var var )的AST节点添加到x86 Int 抽象语法的 arg 非终结符中(图2.10)。我们建议使用三个辅助函数来实现select_instructions遍,每个辅助函数分别用于 C Var 语言的非终结符: atm 、 stmt 和 tail 。
非终结符 atm 的情况处理很简单;变量保持不变,整型常量变为立即数;也就是(Int n)变成立即数(Imm n )。
接下来考虑 stmt 非终结符的情况,先从算术运算开始。例如,考虑下面左边的加法操作。在x86中有一个addq指令,但是它的执行方式是就地更新。所以,我们可以把 arg 1 移到左边的变量中然后把 arg 2 加到 var 中, arg 1 和 arg 2 分别是 atm 1 和 atm 2 对应的转换结果:
另外还有一些情况需要特别注意,以避免生成不必要的复杂代码。比如,如果加法的其中一个参数与赋值的左侧相同,则不需要额外的传送指令,如下面例子,其中赋值语句可以转换成一条addq指令,如下所示。
在x86汇编中没有直接对应的read操作,因此我们在用C编写的文件runtime. c(Kernighan and Ritchie 1988)中通过read_int函数来提供该功能。通常,我们将此文件中的所有功能称为 运行时系统 ,或者简称为 运行时 。在编译生成的x86汇编代码时,需要将runtime.c编译为runtime.o(这是一个 目标文件 ,使用gcc编译时带有选项-c),并将其链接到可执行文件中。为了完成代码生成,所需要做的就是将read赋值转换为对read_int函数的调用,然后将值从rax传送到左侧变量。(回想一下,函数的返回值放在rax中)。
非终结符 tail 有两种情况:Return和Seq。关于Return,我们建议将其视为对rax寄存器的赋值,然后跳转到程序的收尾部分(因此需要标记收尾)。对于(Seq s t),可以递归地转换语句 s 和尾部 t ,然后再联结得到的结果指令。
习题2.5 实现在compiler.rkt中的select_instructions编译遍。创建三个新的示例程序,用于练习本编译遍中所有有趣的案例。在run-tests.rkt脚本中,将以下条目添加到编译遍的列表中,然后运行该脚本以测试编译器。