【编译原理】中间代码(一)

在编译器的分析-综合模型中,前端对源程序进行分析并产生中间表示,后端在此基础上生成目标代码。理想情况下,和源语言相关的细节在前端分析中处理,而关于目标机器的细节则在后端处理。和中间代码相关的内容包括中间代码表示、静态类型检查和中间代码生成,本文将讨论关于中间代码表示的内容。

有向无环图

表达式的有向无环图(Directed Acyclic Graph,简称DAG)与语法分析树类似,一个DAG的叶子结点对应于原子运算分量,内部结点对应于运算符。与语法分析树不同的是,如果DAG中一个结点N表示一个公共子表达式,那么N可能有多个父结点。举个例子,表达式a+a*(b-c)+(b-c)*d的DAG如下:

《【编译原理】中间代码(一)》

DAG的每个结点通常作为一条记录被存放在数组中,一个记录包括的结点信息有:

  • 结点标号:如果记录表示叶子结点,那么结点标号是该结点的文法符号;如果记录表示内部结点,那么结点标号是运算符;
  • 词法值:如果记录表示叶子结点,那么记录还包括该结点的词法值,通常是一个指向符号表的指针或者一个常量;
  • 左右子结点:如果记录表示内部结点,那么记录还包括该结点的左右子结点。

由于一个DAG的结点都会保存在一个记录数组中,因此我们可以通过数组下标引用某个记录从而获取结点信息,这个数组下标称为相应结点的值编码

对图1的DAG,它的记录数组为:

《【编译原理】中间代码(一)》

其中,左边的下标是每个结点的值编码,并且在某些记录中可以包括值编码。例如:对于第5条记录,它表示内部结点”-“,该结点左边的子结点是叶子结点”b”,右边的子结点是叶子结点”c”,由于代表b和c的记录已经存在并且它们的值编码分别为2和3,因此内部结点”-“的记录为(-, 2, 3)。

三地址代码

三地址代码是形如x = y op z的指令集合,之所以名为“三地址代码”,是因为指令x = y op z具有三个地址:两个运算分量y和z,一个结果变量x。由于三地址代码会对多运算符算术表达式和控制流语句的嵌套结构进行拆分,因此适用于目标代码的生成和优化。

三地址代码基于两个基本的概念:地址和指令。简单地说,地址就是运算分量,指令就是运算符,一个地址的表现形式可以是变量名、常量或者编译器生成的临时变量。下面是几种常见的三地址指令形式:

《【编译原理】中间代码(一)》

除此之外,另一种方式是用记录表示三地址代码中的每条指令,四元式、三元式和间接三元式是三种用记录表示三地址代码的方式。

四元式

一个四元式是一条表示三地址指令的记录,它有4个字段:

  • op:表示一个运算符;
  • arg1:表示第一个运算分量;
  • arg2:表示第二个运算分量;
  • result:表示结果变量。

一个四元式中可能用到了所有4个字段,也可能只用到了其中几个字段,它的几个特例如下:

  • 形如x = minus y的单目运算符指令不使用arg2字段,”minus”表示单目减运算符;
  • 形如x = y的赋值指令不使用arg2字段,并且它的op字段是”=”;
  • 形如param x的参数传递指令不使用arg2和result字段;
  • 条件和非条件转移指令将目标标号放入result字段。

下图是一个例子,左边给出了三地址代码,右边是对应的四元式表示:

《【编译原理】中间代码(一)》

三元式

通过图2我们发现,一段三地址代码对应的四元式被存放在一个记录数组中,这一点和DAG结点的记录数组很像;另外,四元式中的result字段主要被用于保存临时变量名,这个临时变量是由编译器生成的。如果我们仿照DAG的记录数组用值编码表示临时变量的地址,那么就可以省略四元式中的result字段,三元式就是由此而来的。

一个三元式只有3个字段:op、arg1和arg2,它们的含义和在四元式中相同。为了取代四元式中的result字段,三元式用值编码表示结果变量的地址。图2中三地址代码的三元式表示如下:

《【编译原理】中间代码(一)》

在图3(b)中,表格左边的数字是值编码,它表示三元式的结果变量的地址,并且这些值编码可以在三元式中被使用(使用的时候用括号括起来)。另外,对形如x=y的赋值指令,和四元式不同的是,三元式的op字段是”=”,arg1字段是”x”,arg2字段是”y”。

间接三元式

三地址代码的三元式表示存在一个问题,如果记录数组中的某条记录R的位置发生改变,那么所有使用到记录R的值编码的记录都需要更新。举个例子,在图3(b)中,如果把第1和第2条记录的位置互换,那么第3和第4条记录的内容都会发生改变。为了解决这个问题,提出了用间接三元式来表示三地址代码。

一个间接三元式在三元式的基础上增加了一个列表,这个列表包含了指向三元式的指针。仍以例子说明,下图是图3中三元式的间接三元式:

《【编译原理】中间代码(一)》

图4(a)是图3(b)的间接三元式,它使用了一个instruction数组保存要执行的指令,每条指令是一个指向某个三元式的指针。这样的话,当我们改变指令顺序时,就不用再更新三元式了,如图4(b)所示。

静态单赋值形式

静态单赋值(Static Single Assignment,简称SSA)形式是另一种中间表示形式,它和三地址代码的主要区别在于:

第一,SSA中的所有赋值都是针对具有不同名字的变量的,这也是“静态单赋值”名字的由来。这一特性如下图中表示:

《【编译原理】中间代码(一)》

第二,由于同一个变量可能在两个不同的控制流路径中被定值,因此SSA使用一种被称为φ函数的表示规则将这个变量的两处定值合并起来。这一特性如下图所示:

《【编译原理】中间代码(一)》

《【编译原理】中间代码(一)》
欢迎关注微信公众号fightingZhヾ(◍°∇°◍)ノ゙

    原文作者:jzyhywxz
    原文地址: https://blog.csdn.net/jzyhywxz/article/details/78720620
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞