8.2 目标语言 - 代码生成的核心
理解目标机器
设计高效的代码生成器首要任务是深入了解目标机器及其指令集。由于目标机器的复杂性,一般性讨论往往难以覆盖机器语言的所有细节,这也是为何精确的代码生成对于特定机器而言极其重要。
8.2.1 目标机器的指令集
考虑一种简化的多寄存器机器模型,该模型采用字节寻址,每4个字节构成一个字,拥有n个通用寄存器(RO, R1, …, Rn-1)。它支持基本的二地址指令(op源,目的),其中op代表操作码,源和目的代表操作数。
核心操作码
- MOV: 数据从源传至目的
- ADD: 将源加至目的
- SUB: 从目的减去源
地址模式
由于地址空间的限制,指令需要通过特定的地址模式来指定操作数,这包括绝对地址、寄存器直接寻址、变址寻址和间接寻址等,每种模式都有其汇编语言形式和可能的附加代价。
汇编语言形式及附加代价
- 绝对地址(M): 直接指明内存地址,附加代价为1
- 寄存器(R): 直接使用寄存器,无附加代价
- 变址(e(R)): 基于寄存器和偏移量的寻址,附加代价为1
- 间接寄存器(*R): 通过寄存器间接访问内存,无附加代价
- 间接变址(*c(R)): 组合间接寻址和变址寻址,附加代价取决于具体实现
通过详细了解这些基础元素和指令集,可以为特定目标机器生成更高效的代码。虽然挑战重重,但通过精心设计的代码生成策略,可以显著提高程序执行的效率和性能。
8.2.2 指令代价
在现代计算机体系结构的设计中,指令的代价及其优化是编译器设计者和系统架构师需要深入考虑的重要方面。本节将探讨指令代价的概念,分析影响指令长度和执行效率的因素,并通过具体例子说明如何在代码生成阶段优化这些代价。
指令代价的基本概念
指令代价是衡量一条指令在执行过程中消耗资源的一个指标,通常表现为指令占用的内存空间(以字为单位)。在简化的机器模型中,这个代价可以通过以下公式进行计算:
指令代价=1+源地址模式的附加代价+目的地址模式的附加代价指令代价=1+源地址模式的附加代价+目的地址模式的附加代价
这个公式揭示了指令长度与指令中操作数地址模式选择直接的关系。寄存器地址模式的代价为0,而涉及内存单元或常数的地址模式代价为1,因为后者需要额外的内存空间来存储。
指令长度与性能
优化指令长度的重要性
指令长度的优化对于提高程序的运行效率至关重要。在大多数计算机体系结构中,读取指令所需的时间常常超过执行指令本身所需的时间。因此,减少指令长度不仅可以节约宝贵的内存空间,还能加快程序的执行速度。
指令代价的实例分析
我们通过以下例子进一步理解指令代价的计算:
- MOV RO,R1:代价为1,因为只涉及寄存器操作,占用一个字的内存。
- MOV R5,M:代价为2,需要一个额外的字来存储内存地址M。
- ADD#1,R3:同样的代价为2,常数1占用一个额外的字。
- SUB4(RO),*12(R1):代价为3,因为涉及到两个常量的存储。
代码生成中的指令优化
挑战与策略
在代码生成过程中,选择最有效的指令序列是一项挑战,尤其是在资源受限的情况下。有效地利用机器的寻址模式,以及在可能的情况下将数据保留在寄存器中,都是优化指令代价的关键策略。
实例应用
以C语言中的表达式 a[i] = b + 1
为例,我们分析了如何根据中间代码树来生成高效的代码。这个过程涉及到对变量地址的动态分配、左值与右值的区分,以及如何利用这些信息来选择指令序列,从而最小化总体指令代价。
结论
在本节中,我们探讨了指令代价及其在代码生成中的影响。通过优化指令长度和选择合适的地址模式,可以显著提高程序的运行效率和性能。这不仅是编译器设计的一个重要方面,也是系统级优化中不可忽视的一环。