阶段任务

在上一阶段中,我们已经完成了对 Decaf 程序的语义分析工作。 针对语义检查通过的程序,我们将在这一阶段把带有类型标注的 AST 进行翻译成适合后端处理的一种中间表示 (Intermediated Representation, IR)。 事实上,框架中已经实现了解释执行该 IR 的模拟器程序,因此你即使不进行后端的工作,在这一阶段结束时也可以在该模拟器上完整地运行 Decaf 程序。 这是一个很大的里程碑,它标志着我们已经完整实现了 Decaf 编译器的前端。

一般来说,把程序翻译为 IR 是各种编译器前端的最后一步工作,这步工作以后编译过程便进入了中端处理或者直接进入后端处理部分。 为了便于翻译工作的进行,在实际的编译过程中往往需要反复地从一种 IR 变为另一种 IR,直到变换成最终的汇编代码或者二进制目标代码为止,其中后面的 IR 总是更加适合“更后端”部分所需的分析和处理。 例如 gcc 的编译过程除 AST 以外还有 GENERIC, GIMPLE, RTL 几种 IR。

在 Decaf 编译器中,为简单起见,我们只涉及一种 IR——三地址码 (Three Address Code, TAC)。 它看起来很像汇编,但是和汇编最大的差异在于——汇编里面使用的是目标平台(如 x86, mips)规定的物理寄存器,其数目有限; 而 TAC 使用的是“伪寄存器”,我们称临时变量,理论上其数目可以无限,实际中我们想要多少就有多少(只要模拟器在运行时操作系统允许占用那么大的内存)。

这一阶段的核心任务是:把带有类型标注的 AST 翻译成语义上等价的 TAC,并在合适的地方加入检查如数组访问越界、数组大小非法等运行时错误的 TAC 指令。 输出的 TAC 通过模拟器运行,得到预期的运行结果(控制台输出)。 在本阶段,你将会体验到“元编程”——编写用来生成代码(TAC)的代码。 通过这个阶段,希望大家能掌握一般的 IR 翻译方法,并且对于过程调用约定、面向对象机制的实现方法、存储布局等内容有所了解。

相关文件

与本阶段相关的文件有:

src
└── main
    └── java
        └── decaf
            └── frontend
            │   └── tacgen
            │       ├── TacGen.java       TAC 生成入口
            │       └── TacEmitter.java   语句和表达式的 TAC 生成、辅助函数
            └── lowlevel
                ├── instr   指令、临时变量
                ├── label   标签
                ├── tac     TAC 程序的生成与模拟运行
                └── log     调试日志相关

其中 decaf.lowlevel 包作为一个库使用,在进行实验时无需阅读其实现代码,只需要了解所提供函数的使用方法。

Last updated