抽象语法树的观点认为任何复杂的语句嵌套情况都可以借助于树的形式加以描述。确实,不得不承认应用抽象语法树可以使语句翻译变得相对容易,它很好地描述了语句、表达式之间的联系。不过,由于Neo Pascal并不会显式构造抽象语法树,所以不得不借助于其他数据结构实现。根据先前的经验,栈结构就是不二之选。
DAG(有向无环图)
后缀表达式:也称为逆波兰表达式,这种形式简单明晰,便于存储。在处理表达式翻译时,后缀表达式有着其他形式无法比拟的优势。不过,由于后缀表达式的应用领域比较单一,所以很少独立作为一个实际编译器的IR存在。
举例:
(3 + 4) × 5 - 6 就是中缀表达式
当然,三地址代码也不是完美的,由于它是相对离散的,在分析源程序结构方面,它就不及语法树便捷
三地址代码的理由如下:三地址代码是一种线性IR。由于输入源程序及输出目标程序都是线性的,因此,线性IR有着其他形式无法比拟的优势。另外,相对于其他表示形式而言,程序员对于线性表示形式通常会有一种莫名的亲切感,编译器设计者当然也不例外。早期编译器设计者往往都是汇编语言程序设计的高手,可以非常自然、流畅地阅读线性的三地址代码形式。同时,线性表示形式也会降低输入输出的实现难度。随着编译器”端”、”遍”等概念的出现,IR已经不仅仅是一种存储在内存中的数据结构。有时它也需要以文件形式转存输出,作为接口供其他系统读取使用。
为什么将其设计为”三地址”的形式呢?实际上,这是计算机科学家经过多年实践探索后才得到共识的。三地址代码并不是唯一的线性IR,只能说是最为常见的而已。在编译技术领域,二地址代码、单地址代码(即栈式机代码)都曾出现过,也曾在某些应用领域盛行一时,尤其是单地址代码。
然而,单地址代码的情况则截然不同了,在现代编译器设计中,单地址代码也是应用比较广泛的一种IR。尤其是近年随着混合语言的日渐壮大,单地址代码也重新进入了人们的视野。由于执行单地址代码程序的栈式机架构相对比较简单,可以非常方便地构造相关的解释器或虚拟机,所以单地址代码深受混合语言设计者的欢迎。读者熟悉的Java字节码、.NET的IL都是单地址代码
三地址代码是在二地址代码的基础上发展而来的。二地址代码的不足之处在于它通常会给其中一个源操作分量带来一定副作用。当然,这种设计的灵感最初是来源于x86指令系统的,但是却忘了一个重要的区别:x86指令中往往都是以寄存器作为暂存空间的。而暂存空间对于二地址代码却是一个棘手的问题。为了解决二地址代码的不足,人们提出了一个对源操作分量不产生任何副作用的形式,那就是三地址代码。也就是说,在一行三地址代码中,任何运算都不会改变两个源操作分量。这是三地址代码与二地址代码的主要区别。这个特性是非常重要的,它将使得编译器更自由地复用名字与值,不必考虑代码带来的副作用。
最后,再来谈谈IR的级别,即IR依赖于目标机的程度。按级别分类,可将IR分成三类:高级形式(HIR)、中级形式(MIR)、低级形式(LIR),也可称为高级中间语言、中级中间语言、低级中间语言。
高级形式(HIR)是一种尽可能保持了源语言程序结构的IR,这种形式能较好地保留源程序的原始语义信息。由于高级形式太接近源语言程序结构,所以很少有编译器将其独立作为IR传递给后端。
中级形式(MIR)既要以一种与语言无关的方式在一定程度上反映源语言的特性,又要能够适应多种体系结构的IR。中级形式是一种比较常用的IR,它兼顾了源语言、目标机的特性,又能适用于大多数优化算法。当一个编译器仅设计一种IR时,中级形式是较理想的选择。
低级形式(LIR)就是在一定程度上包含某些目标机特性的IR,比目标语言稍高级,常作为一些机器相关的优化算法的输入。不过,实际上,除了一些较大型的编译器需要使用低级形式之外,低级形式并不是很常见。因为更多编译器设计者更愿意直接基于目标语言作优化。
表5-6 if语句的翻译方案
if语句
翻 译 方 案
if <表达式> then表达式>