理论教育 类型系统设计方案:《编译器设计之路》小说中详细揭示

类型系统设计方案:《编译器设计之路》小说中详细揭示

时间:2023-11-04 理论教育 版权反馈
【摘要】:类型系统表的优势就在于编译器设计者只需将类型规则加入表中,并编码实现简单的表检索及处理动作,即可完成繁复的类型系统。最后,再来看看Neo Pascal类型系统表的实现。

类型系统设计方案:《编译器设计之路》小说中详细揭示

前面详细讨论了类型系统的一些理论基础,包括类型等价、类型相容、类型推断以及隐式转换等话题,不少读者可能已经看得一头雾水。类型系统理论确实有些抽象,不太容易理解。不过,幸运的是编译器设计所需要的类型理论相对有限。同时,借助于Neo Pascal的源代码及相关设计文档,相信并不算太抽象。下面,就从一个实际编译器的类型系统实例入手,分析类型系统设计与实现的某些细节。

从编译器设计的角度而言,类型系统又是什么呢?实际上,简言之,主要就是实现类型检查和类型转换的机制。那么,如何才能真正实现这两个类型机制呢?先来看一段程序伪代码,如图6-4所示。

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

图6-4 类型系统的伪代码实现

这是一段非常简单的类型检查程序,其功能是检查取模运算的操作数类型。有些读者认为,可以依样画葫芦实现所有的类型检查,而且,一些早期经典编译器的成功也足以证明这种方案是完全可行的,但是,是否考虑过这项工程的复杂性?以Neo Pascal为例,可以估算一下手工实现类型检查机制需要的if判断数量。Neo Pascal有18个双目运算符、4个单目运算符以及16种类型,在不考虑单目运算符的情况下,至少存在16×16×18=4608种组合。当然,在实现过程中,并不需要考虑这么多种组合情况。因为,设计者更多关心的是合法情况时所需执行的语义动作,比如,类型转换、类型推断等。对于非法的组合,只需报错即可。在这种情况下,只需建立一个集合,该集合描述的是各种运算符与类型的正确组合。在类型检查时,编译器按输入表达式的实际运算符及类型与集合中的元素匹配,如果存在则表示类型检查合法,直接执行相应的语义动作。如果不存在于集合中,则表示类型检查是非法的,由编译器统一报告出错信息。即使如此,根据Neo Pascal的实际情况,依然不得不面临1300余种组合需要编译器处理。读者能否想象在程序中出现1300多个if是什么样的情况?随着源语言基本类型的扩展,程序的规模还会呈非线性增长,这是比较可怕的。同时,这种程序的可读性、可维护性必定大大降低。

不过,笔者需要指出一点,类型系统是编译器前端的一个核心元素,这1300余个类型判断是必不可少的,关键在于如何有效地实现。如果试图以任何理由回避类型系统的实现可能是徒劳的。

下面,介绍一种应用表格实现类型系统的方法。这种方法可统筹地考虑类型检查、类型转换以及类型推断等,甚至类型优化也得到了兼顾。实际上,编译器类型系统的工作流程是机械的、模式化的。无论是类型检查、类型转换还是类型推断都是由相应规则指导的,并不需要编译器作太多的决策或加工。以取模运算为例,可以将其总结为表6-2所示。

表6-2 取模运算类型系统实例

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

借助表6-2(表中并没有完整地列出取模运算的所有类型规则),编译器可以非常方便地实现类型检查、类型转换以及类型推断。首先,编译器根据取模表达式的左、右操作数类型检索表6-2,检索成功,则表示输入表达式的操作数类型合法,即实现了类型检查机制。然后,编译器根据当前表项的信息,判断左、右操作数是否需要类型转换,需要则生成相应的IR。最后,编译器根据结果操作数类型,生成相应的运算IR。而结果操作数的类型就是根据左、右操作数类型的不同组合,加以推断得到的。至此,类型检查、类型转换、类型推断都已经非常直观地显示在表6-2中。下面,笔者就类型相容的实现作简单讨论。实际上,类型相容是非常简单的。读者不妨仔细考虑一下,如何应用类型系统描述类型相容呢?一般而言,A类型与B类型相容就是指A类型可以通过类型转换后得到B类型。这种情况恰好与赋值运算类型检查类似,赋值运算的类型检查主要是核查源操作数类型是否与结果操作数类型相容。例如,在Pascal语言中,不允许将REAL类型的变量直接赋给INTEGER类型的变量,原因就是REAL与INTEGER是不相容的。因此,完全可以将类型相容问题转化为赋值运算类型检查问题来处理。至此,表6-2已经足以实现类型检查、类型转换、类型推断及类型相容等机制了。这里,笔者暂时将类似于表6-2的表格形式称为“类型系统表”。类型系统表的优势就在于编译器设计者只需将类型规则加入表中,并编码实现简单的表检索及处理动作,即可完成繁复的类型系统。

当然,也不可否认一个事实,那就是类型系统表的规模可能比较庞大,尤其是实际编译器项目中,视类型系统复杂程度不同,类型系统表的记录量可能达到数千甚至上万。

最后,再来看看Neo Pascal类型系统表的实现。实际上,先前讨论的表6-2只是一个类型系统表的简单模型,它与真正实现还是存在一定差异的。下面,笔者给出Neo Pascal类型系统表结构,如图6-5所示。(www.daowen.com)

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

图6-5 Neo Pascal类型系统表结构示例

图6-5是Neo Pascal类型系统表的部分截图,先来看看其中的某些字段属性的含义。

变量操作码、常量操作码字段表示的是语义处理的动作,即语义处理程序根据操作码值的不同,完成相应的语义动作。这里,读者必须注意一点,为了提高目标程序的效率,当表达式的操作数都是常量时,编译器会直接计算结果并将其记录,而不会按照常规方式生成IR。那么,就必须分两种情况考虑,即不存在变量操作数(都是常量操作数)、存在变量操作数。这里,常量操作码的意义就是标识前者的语义动作,而变量操作码就是后者的语义动作标识。

中间码字段表示的是该运算的三地址代码的操作符,编译器根据该字段生成IR。

类型转换1、类型转换2表示的是操作数1、操作数2的类型转换信息,编译器根据这两个字段生成类型转换的IR。

Neo Pascal类型系统表的结构声明如下:

【声明6-1】

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

最后,笔者还需要指出一点,在Neo Pascal中,由于类型系统将以文件形式存储在外存中,所以实际使用的类型系统表是图6-5所示的代码化形式,这是由源码附带的工具自动完成的,对于编译器设计是透明的。

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

我要反馈