理论教育 NeoPascal三地址代码的实现编译器设计之路

NeoPascal三地址代码的实现编译器设计之路

时间:2023-11-04 理论教育 版权反馈
【摘要】:前面读者应该已经了解了三地址代码的基本概念。下面,就来看看Neo Pascal三地址代码的实现,本书将其简称为“NPIR”。三地址代码设计主要包括两个方面:操作符与操作数。表5-2 NPIR的操作符(续)(续)仅就表5-2的规模而言,可能读者会觉得NPIR是一个非常庞大的体系。这种方案的目的在于得到的三地址代码可以同时作为MIR、LIR使用,并且不必考虑LIR到MIR的转换代价。NPIR操作符集合是一组完整的枚举值。

NeoPascal三地址代码的实现编译器设计之路

前面读者应该已经了解了三地址代码的基本概念。下面,就来看看Neo Pascal三地址代码的实现,本书将其简称为“NPIR”(Neo Pascal IR)。三地址代码设计主要包括两个方面:操作符与操作数。NPIR的操作符并不是完全由笔者凭空想象出来的,其设计灵感来源于lcc的IR。笔者觉得lcc IR的优势比较显著,尤其是其操作符的设计经验是值得与各位读者分享的。下面给出完整的NPIR的操作符列表,供读者参考,见表5-2。

表5-2 NPIR的操作符

978-7-111-32164-4-Chapter05-4.jpg

(续)

978-7-111-32164-4-Chapter05-5.jpg

(续)

978-7-111-32164-4-Chapter05-6.jpg

仅就表5-2的规模而言,可能读者会觉得NPIR是一个非常庞大的体系。从IR设计的角度而言,这种设计似乎并不是非常合理。不过,有理由相信这种来自lcc的设计思想是有优势的。那么,读者不妨思考一下其优势何在?这种方案的目的在于得到的三地址代码可以同时作为MIR、LIR使用,并且不必考虑LIR到MIR的转换代价。有些读者可能会对笔者的观点有所怀疑。下面解释一下其中的原因。

首先,按功能将这130余个操作符分为21大类,即“相等”、“不等”、“小于”等表头。如果将前20个类别(即除“其他”类之外)分别用1个操作符(即表头括号字符)表示,那么,这20个操作符和最后一类的15个操作符就形成了一套MIR。这个操作符集合与目标机几乎是无关的,因为它既没有涉及具体目标机的特性,也没有过多考虑物理存储结构。

然而,仅考虑大类下属的具体操作符,那么,它就是一套LIR。其中涉及一些目标机特性,例如,处理机字长、物理存储结构等。

那么,NPIR又是如何实现LIR与MIR之间无代价转换的呢?实际上,这是非常简单的。NPIR操作符集合是一组完整的枚举值。笔者通过一种编码规则对各枚举常量进行编号,根据枚举常量的编码就可以对类别及其所属操作符加以区分。例如,EQU的编码为1《4,而EQU_l、EQU_S的枚举值分别为(1<<4)上1和fl<<4)+2,依此类推,将EQU类别的所有所属操作符加以编号。也就是说,操作符编号二进制值的高5位(由于一共有21类,需要5位二进制值表示)表示其类别,低4位(由于同一类别中最多不超过16种操作符)表示操作符在该类别中的顺序编号。通过这样的编码方式,就可以方便地将LIR转换为MIR。注意,在Neo Pascal语义分析过程中,提交的三地址代码是MIR形式,而经过类型系统处理后的最终形式是LIR。这里,讨论LIR转换到MIR的目的就是考虑到很多中间代码优化算法是基于MIR实现的。通过简单地屏蔽操作符编码的低4位就可以获得MIR的结果是设计者乐意见到的。

讨论完NPIR的设计目的之后,笔者针对NPIR操作符的特点作两点说明:

(1)在带字节指示的操作符中,除了浮点数及明确说明无符号数的操作符之外,都默认为是有符号数的操作符,即三地址代码的操作数都是有符号类型的值或变量

(2)NPIR中设置了32B的操作符的目的是处理集合类型变量及数据的,因为Pascal集合类型数据所占的存储空间就是32B(256位)。

实际上,详细分析NPIR操作符的含义是没什么意义的,对汇编语言稍有了解的读者可以非常轻松地理解。(www.daowen.com)

至此,笔者已经详细地讲解了NPIR的操作符的相关内容。下面,再来简单看看操作数及其存储结构。NPIR的操作数是非常简单的,主要由以下三个字段描述:m_iType、m_iLink、m_bRefo结构声明如下所示:

【声明5-1】

978-7-111-32164-4-Chapter05-7.jpg

其中,m_iType描述该操作数的类型,即常量、变量、指针、标号、过程等。m_iLink是指向实际操作数的指针,例如,操作数类型为变量,则m_iLink的值指向该变量在变量信息表中的位序。m_bRef表示该操作数是否需要引用访问(或者称为间接访问)。对于NPIR而言,操作数的结构仅此而已。

当然,与声明5-1相比,Neo Pascal源代码中的操作数声明较复杂。这里,读者可以暂且略过。下面,再来看看NPIR的声明形式:

【声明5-2】

978-7-111-32164-4-Chapter05-8.jpg

实际上,m_Codes并不是一个全局变量,而是过程信息表项的一个属性,也就是说NPIR是以函数或过程为单位组织的。下面,笔者给出一个完整的实例分析,以便读者对NPIR有感性的认识。

例5-3 NPIR实例分析,见表5 3。

表5-3 NPIR实例分析

978-7-111-32164-4-Chapter05-9.jpg

这个实例比较简单,对于熟悉汇编语言的读者,相信并不需要太多的注释。不过,读者可能对其中的ByteToInt操作符有所疑惑。确实,在先前操作符列表中,笔者并没有提到ByteToInt。实际上,从字面上来看,也不难猜出其类型转换的功能。读者的关键问题可能是想知道它到底是不是操作符?答案是肯定的。实际上,表5-2所列出的操作符尚不完整,还有一部分类型转换的操作符并不在其中。关于这类操作符,将在第6章中详细阐述。

关于NPIR的优点,先前已经讲了不少了,选用这种形式还是具有一定积极意义的。下面,笔者想客观地谈谈NPIR设计的不足之处。由于NPIR主要定位是MIR、LIR,所以源程序中的某些非常明显的语言结构可能会被隐藏,例如,数组访问、记录访问等在NPIR中都是以指针引用形式出现。当然,从语义的角度而言,这是完全等价的,但是这种形式却并不利于某些别名分析算法的实现。实际上,这就是前面所说的级别问题,相对而言,抽象语法树等HIR在这方面的表现就比较有优势了。

由于NPIR是Neo Pascal的一个重要组成部分,因此,深入理解NPIR对于后续学习是有重要意义的。囿于篇幅,就不再举例分析了。对于某些尚不熟悉NPIR的读者,可以自己调试Neo Pascal源代码,将NPIR输出与源程序对照学习。

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

我要反馈