代码生成器将源代码的中间表示形式转换为易于由机器执行的形式。代码生成器应生成正确的代码。代码生成器的设计应以易于实现,测试和维护的方式进行。
在代码生成阶段会出现以下问题:
- 输入到代码生成器–
代码生成器的输入是前端生成的中间代码,以及符号表中的信息,该信息确定中间表中名称表示的数据对象的运行时地址。中间代码可能主要以四元组,三元组,间接三元组,后缀表示法,语法树,DAG等表示。代码生成阶段仅在假设输入没有所有语法和状态语义错误(必要的类型)的情况下进行检查已经进行,并且在必要时插入了类型转换运算符。 - 目标计划–
目标程序是代码生成器的输出。输出可以是绝对机器语言,可重定位机器语言,汇编语言。- 绝对机器语言作为输出的优点是可以将其放置在固定的内存位置并可以立即执行。
- 作为输出的可重定位机器语言允许子程序和子例程被分别编译。可重定位的对象模块可以链接在一起,并可以通过链接加载器加载。但是增加了链接和加载的费用。
- 汇编语言作为输出使代码生成更加容易。我们可以生成符号指令,并在生成代码时使用汇编程序的宏功能。在代码生成之后,我们需要一个附加的组装步骤。
- 内存管理 –
将源程序中的名称映射到数据对象的地址是由前端和代码生成器完成的。三个地址语句中的名称是指符号表条目中的名称。然后从符号表条目中,可以确定名称的相对地址。 - 指令选择–
选择最佳指令将提高程序的效率。它包括应该完整且统一的说明。考虑效率时,指令速度和机器习惯用法也起着重要作用。但是,如果我们不关心目标程序的效率,那么指令选择就很简单。例如,相应的三地址语句将被转换为后一个代码序列,如下所示:
P:=Q+R S:=P+T MOV Q, R0 ADD R, R0 MOV R0, P MOV P, R0 ADD T, R0 MOV R0, S
这里的第四条语句是多余的,因为P的值再次存储在刚刚存储在前一条语句中的那个语句中。这导致低效率的代码序列。给定的中间表示形式可以转换为许多代码序列,不同实现之间存在明显的成本差异。为了设计好的顺序,需要先了解教学成本,但是很难预测准确的成本信息。
- 注册分配问题–
与存储器相比,使用寄存器可使计算速度更快,因此有效利用寄存器非常重要。寄存器的使用分为两个子问题:- 在寄存器分配期间–我们仅选择程序每个点上将驻留在寄存器中的那些变量集。
- 在随后的寄存器分配阶段,将选择特定的寄存器来访问变量。
随着变量数量的增加,寄存器对变量的最佳分配变得困难。从数学上讲,这个问题成为NP完全的。某些机器要求寄存器对由偶数和下一个奇数寄存器组成。例如
M a, b
这些类型的乘法指令涉及寄存器对,其中被乘数是偶数寄存器,b是乘法器,是偶数/奇数寄存器对的奇数寄存器。
- 评估单–
代码生成器确定指令的执行顺序。计算顺序会影响目标代码的效率。在许多计算顺序中,有些仅需要较少的寄存器即可保存中间结果。但是,在一般情况下选择最佳顺序是一个困难的NP完全问题。 - 解决代码生成问题的方法:代码生成器必须始终生成正确的代码。这是必不可少的,因为代码生成器可能会遇到许多特殊情况。代码生成器的一些设计目标是:
- 正确的
- 易于维护
- 可测试的
- 高效的