代码生成概述
代码生成器以编译器前端生成的中间表示(IR)和相关符号表信息为输入,输出语义等价的目标程序。
编译器的代码优化和代码生成步骤常被称为编译器的后端(back end)。
代码生成器有三个主要任务:指令选择、寄存器分配和指派、指令排序。
代码生成器中的问题
代码生成器最重要的标准是生成正确的代码。优先考虑正确性的情况下,另一个重要的设计目标是把代码生成器设计得易于实现、测试和维护。
- 代码生成器的输入
代码生成器的输入是由前端生成的源程序的中间表示形式以及符号表中的信息组成的。这些信息用来确定IR中名字所指数据对象的运行时刻地址。
IR的中间表示形式的选择有多种:
- 三地址码。(四元式、三元式、简洁三元式)
- 字节码和堆栈机代码的虚拟机表示方式。
- 后缀表示的线性表示方式。
- 语法树和DAG的图形表示形式。
- 目标程序
构造一个能够产生高质量机器代码的代码生成器的难度会受到目标机器的指令集体系结构的极大影响。最常见的目标机体系结构是RISC(精简指令集计算机)、CISC(复杂指令集计算机)和基于堆栈的结构。
- 指令选择
如果IR是高层次的,代码生成器就要使用代码模板把每个IR语句翻译成为机器指令序列。但是这个逐语句生成的方式通常会产生质量不佳的代码。需要进一步优化。
如果IR中反映了相关计算机的一些低层次细节,那么代码生成器就可以使用这些信息来生成更加高效的代码序列。
CRE:寄存器的状态和当前指令的前后指令上下文相关。所以简单的逐语句翻译,生成的目标机器指令还有很大的优化空间。
比如一条加法运算指令用到了上一条加法运算指令的结果,就不同从内存再读取值到寄存器了。
在大多数机器上,一个给定的IR程序可以用很多种不同的代码序列来实现。这些不同实现之间在代价上有着显著区别。因此对中间代码的简单翻译虽然能产生正确的目标代码,但是这些代码却可能很低效。
比如一条加法指令,可以用一条“加一”指令来实现。
- 寄存器分配和指派
寄存器是目标机器上运行速度最快的计算单元,但是我们通常没有足够的寄存器来存放所有的值。没有存放在寄存器中的值必须存放在内存中。
使用寄存器运算分量的指令,总是要比那些运算分量在内存中的指令短并且快。因此有效利用寄存器非常重要。
寄存器使用的两个子问题:
- 寄存器分配:对于源程序中的每个点,我们选择一组将被存放在寄存器中的变量。
- 寄存器指派:我们指定一个变量被存放在哪个寄存器。
- 求值顺序
计算执行的顺序会影响目标代码执行效率。
(END)