理论教育 操作数地址与形态-《编译器设计之路》成果

操作数地址与形态-《编译器设计之路》成果

时间:2023-11-04 理论教育 版权反馈
【摘要】:然而,对于操作数翻译而言,这些逻辑空间块的首地址显然是非常重要的,它是在运行时获取变量值的唯一途径。了解了操作数地址的概念后,就来看看其形态设计。实际上,操作数的形态就是指IR中关于操作数的描述形式。前面提到了操作数偏移量的问题,无论是常量偏移,还是变量偏移,它们都是基于首地址而言的。较低级的操作数地址的形态是非常有利于优化及代码生成的。

操作数地址与形态-《编译器设计之路》成果

本节将介绍语义处理的最后一个话题——操作数翻译。前面花费了较大的精力讲述表达式的翻译,以便读者了解如何将一个表达式翻译成相应的IR序列。不过,在此期间却略过了一个问题,就是文法中非终结符“变量”的相关语义子程序。换句话说,讲述表达式翻译时,讨论的前提就是表达式的操作数都是以OpInfo对象的形式存在于Operand栈内的。其中,笔者只分析了semantic049是如何生成常量操作数的OpInfo对象的,但并没有解释编译器是如何为其他操作数构造OpInfo对象的。然而,本节的重点就是研究编译器如何从输入源程序中捕捉相关信息,并为各种可能的情况构造OpInfo对象。这是一个非常复杂的过程,也是表达式翻译的精髓所在。

与常量操作数不同,当变量参与表达式运算时,由于操作数的值是在程序运行过程中确定的,因此,在绝大多数情况下,编译阶段是无法得到的。不过,这并不意味着编译器完全无能为力了。虽然变量的值不能在编译阶段确定,但是变量的地址在某种意义上却是可以确定的。注意,笔者只是将其界定为“在某种意义上”。在很多情况下,变量地址的管理和组织是由编译器与操作系统及目标机共同完成的。因此,编译阶段所能确定的地址只是变量的逻辑地址,并不是实际的物理地址。

在目标程序运行过程中,任何一个变量都需要占用一定的存储空间,即使是编译器自动分配的临时变量也不例外。由此,编译器必定会为每个变量分配一片逻辑地址空间,这片空间的容量是根据变量的实际类型而定的。这里,读者并不需要关注这些逻辑空间块的组织形式,可以将它们理解为是完全离散的,彼此并不存在任何联系。然而,对于操作数翻译而言,这些逻辑空间块的首地址显然是非常重要的,它是在运行时获取变量值的唯一途径。不过,在语义处理阶段,遗憾的是编译器可能无法得到这些逻辑空间块的首地址。那么,编译器又如何生成相关的寻址IR呢?最有效的解决方法就是为每个逻辑地址空间定义一个名字,将其与相应逻辑空间块的首地址关联。在语义处理阶段,一切对变量逻辑空间块首地址的访问都转换成对其名字的寻址即可。例如,(ADD,a,1,b1的含义是将a逻辑空间块中存储的值加1,并保存到b逻辑空间块中。当然,在后续阶段,编译器最终会将a、b替换为相应逻辑空间块的首地址。

不过,仅有逻辑空间块的首地址还不足以应对任意形式的操作数。编译器还必须计算所需访问的空间在逻辑空间块内的偏移量。对于简单变量操作数来说,该偏移量当然就是0。然而,当操作数是数组元素、结构字段等形式时,该偏移量就不一定是0,例如,a.b、a[10]、a[i]等。编译器通常将整个数组或结构作为一个逻辑空间块,而其中的元素只能表示为相对于逻辑空间块首地址的偏移量的形式。同样,块内偏移量也可以分为两种情况:常量偏移、变量偏移。常量偏移就是指该偏移量可以在编译阶段计算得到的,例如,a.b、a[10]等。而变量偏移就是指该偏移量只能在运行阶段确定的,例如,a[i]、a[a啪]等。

了解了操作数地址的概念后,就来看看其形态设计。实际上,操作数的形态就是指IR中关于操作数的描述形式。换句话说,就是讨论如何运用IR序列表示操作数。在编译技术中,关于操作数的形态并没有统一的观点,与IR设计类似,设计操作数的形态也是一项基于经验的工程。当然,其形态的抽象层次对于IR的级别也有较大影响。

鉴于Neo Pascal采用了中、低级别的IR,在设计其操作数形态时,笔者主要考虑如下两个方面:

(1)简单地址形式。前面提到了操作数偏移量的问题,无论是常量偏移,还是变量偏移,它们都是基于首地址而言的。然而,在通用机编译器中,逻辑空间块的首地址是不可能在编译阶段确定的。在设计IR时,为了便于后续处理,笔者尽可能细化寻址过程的每个步骤,将其转换为相应的IR输出,见表6-6。(www.daowen.com)

表6-6 简单地址形式

978-7-111-32164-4-Chapter06-48.jpg

不难发现,表6-6中操作数的形式都比较简单,关于操作数寻址的描述是以IR指令形式给出,而没有将操作数的抽象滞留在较高的层次上。这种方案的主要特点就是IR中的每个OpInfo对象的形态都是简单地址形式,不存在任何偏移信息。较低级的操作数地址的形态是非常有利于优化及代码生成的。虽然这个方案可能会产生较多的临时变量,但是这个结果却是可以接受的。因为编译器可以通过存储分配及代码优化算法来减少临时变量的使用。例如,可以通过数据流的分析获得各个临时变量的使用周期(即定值点到引用点的范围),将使用周期互不重叠的临时变量分配在一个逻辑地址空间中,以节省临时变量的空间开销。在后续章节中将详细介绍相关设计思想及算法实现。

(2)无类型支持。在讨论IR的抽象级别时,曾经谈过这个话题。实际上,关于IR是否提供类型信息,完全是由IR的抽象级别决定的,并不能一概而论。针对不同的代码优化与生成算法,编译器设计者可能需要设计几种形式、级别不同的IR。实践证明,完备的类型信息反而会加大某些类型无关的优化算法的复杂程度。因此,在通常情况下,编译器设计者都会构造一种无类型的IR形式供后端使用。不过,这并不意味着无类型的IR必须在语义处理阶段生成。如果有特殊需要,在语义处理阶段,编译器完全可以生成有类型的较高级的IR。随后,在特定的时刻,将其转换为其他形式的IR与无类型的IR,这项工程应该并不复杂。

必须指出,任何IR形式的评价都是基于具体应用环境(如代码优化与生成等)的,脱离了应用环境讨论IR的优劣是没有意义的。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈