gcc编译器----中间表示

中间表示( intermediate representation, IR) 指编译器对于源程序进行扫描后生成的内部表示,代表源程序的语义和语法结构,编译器的各个阶段都在IR上进行分析或优化变换,因而它对编译器的整体结构、效率和健壮性都有着极大的影响[38]。中间表示对提高编译器的可移植性以及代码生成起到关键作用,在编译器的研究中,应该设计一种结构良好的中间表示,这种中间表示应在适当的抽象层次上,向上能支持多语言的映射,向下能适应多平台转换且易于进行各种优化[39]。

对于现代编译器来说,编译器前端和后端分别指分析输入源语言和生成目标平台汇编代码的两编译阶段,大部分现代编译器在前端和后端之间会有个中间表示层。有了一个设计良好的中间表示,有m种前端编程语言(C\C++FortranAdaJava等)和n种后端平台(x86MIPSSparcARM等)的编译器设计,这样就减少了为了设计m中语言和n个平台从而设计m*n个编译器设计

     中间语言的使用可以大大减低开发各个语言和各个平台所需编译器的工作量。支持多种编程语言和多种后端平台的编译器可以通过使用一种中间表示来实现。总的来说,中间表示层在减低编译器开发方面的开销和复杂度上功不可没。有些中间表示语言的设计是专门为了支持一种特定的语言,例如Jvm的设计仅仅是为了支持java语言,而大多数中间表示语言的设计是用来将不同的前端语言和后端平台连接起来。

       GCC编译器有三种中间表示语言,一种AST/GENERIC比较完善的表示了前端语言的信息,一种GIMPLE用来在相对比较高的层次来表示源语言程序。而另外一种rtl用来高度抽象的表示从特定平台抽象出来的机器指令。

        一、GENERIC形式与前端的编程语言是相关的,每种前端语言词法语法分析后形成的AST/GENERIC是异构的,GENERIC包含了前端语言所有的信息。

GCC编译器的前端将高级语言源码经过词法分析、语法分析生成GENERICGENERIC是一棵抽象语法树(Abstract Syntax TreeAST),用数据结构中的树(tree)结构表示。GCC编译器的抽象语法树是源程序的一种中间表示形式,比较直观的表示出源程序的语法结构,并含有源程序结构显示所需要的全部静态信息GCC 格式的 AST 文件是 GCC 编译源程序时产生的,以文本方式记录源程序抽象语法树的文件

C语言源码和对应的GENERIC表示如下所示:

C语言源码:

int sum(int a,int b)
{
  int c;
c=a+b;
return c;
}

GENERIC表示(部分):

GCC的前端将源语言解析生成GENERIC。不同的前端高级编程语言,GCC前端生成的GENERIC形式不尽相同。每种前端语言词法语法分析后形成的AST/GENERIC是异构的,需要转换成一种统一的中间形式进行后续的处理,这种统一的中间表示形式就是GIMPLE形式。

二、中间表示GIMPLE:GIMPLE是一种三地址码的中间表示形式

GIMPLE中间形式由GENERIC表达式变换而来,与GENERIC相比,主要有如下的转换:

1)、通过引入临时变量保存中间结果,并将GENERIC表达式拆分成不超过三个操作数的元组(tuples)。

2)、GENERIC中的控制结构,例如if-elseforwhile等也被转换成条件转移。

3)、词法作用域(lexical scopes)被取消了。

4)、异常区域(lexical scopes)被转换成一个单独的异常区域树(exception region tree)。

GIMPLE是通过简化GENERIC得来的,这样做的好处通过下面的例子比较来说明。

xxx@localhost$ cat compare_generic_to_gimple.c

int func(int j, int k){
int i;
for(i=0;i>=10;i++)
{
j = i + 1; 
k = j + i + 5;
}
return k;
}
int func(int j, int k){
int i;
for(i=0;i>=10;i++)
{
j = i + 1; 
k = j + i + 5;
}
return k;
}

使用如下命令进行编译,其中-fdump-tree-gimple选项打印出GCC处理的中间过程,包括列出GENERIC的中间形式和GIMPLE中间形式。

 xxx@localhost$ paag-linux-gcc -fdump-tree-gimple -S -o compare_generic_to_gimple.s compare_generic_to_gimple.c
 xxx@localhost$ paag-linux-gcc -fdump-tree-gimple -S -o compare_generic_to_gimple.s compare_generic_to_gimple.c

下表分别给出其GENERIC形式和GIMPLE表示,并进行对比分析。

从上图可以看出,GIMPLE相比较GENERIC而言的优势,也就是为什么要进行GENERIC形式到GIMPLE形式的转化,主要有以下几个方面的原因:

(1) GENERIC是树结构表示和存储的,对于分析和处理非常不便。

(2) GENERIC形式与前端的编程语言是相关的,而GIMPLE是与前端编程语言无关的,所有的语言并不能够表示成统一的GNERIC,这就使得GENERIC并不适合做优化

(3) GIMPLE形式从本质上讲就是线性的代码序列,所有的计算表达式都表示成一系列的基本操作。这样可以更方便有效地进行后续的编译优化,优化算法很容易在GIMPLE形式上实现。在GIMPLE的表示中,控制结构都分解为直接或间接跳转语句。

(4) GIMPLE表达式的表现形式更加严谨,除了函数表达式以外,每个表达式的操作数不超过3个。

(5) GIMPLE表达式将GENERIC分解为3地址格式的表达式,用临时变量来存储计算中的中间值。同样在GIMPLE的表示中,GIMPLE节点并不表示其节点的值,类似CON_EXPR或是BIND_EXPR这样的节点如果有值,那么GIMPLE格式的表示会将其值存储在临时变量中。

   GIMPLE形式的中间表示由于其与前端无关的特性,将多种前端高级编程语言统一到一种中间表示上来,并且在GIMPLE层上做了部分优化。随后GCCGIMPLE转换成RTL形式的中间表示。

三、中间表示RTL:RTL叫做“寄存器转移语言”(Register Transfer Language),它是以一种虚拟寄存器(pseudo register)的方式来叙述计算机行为的语言。RTL 是一种接近机器指令的语言,既有指令序列组成的内部形式,又有机器描述和调试信息组成的文本形式

RTL的表示形式:RTL的产生受到了LISP表的启发,要描述的输出指令基本上都是以字母表顺序一个接一个地描述指令完成的工作。它既有内部(内存)形式,是由指向其他结构的指针组成;也有文本形式,用于机器描述和调试输出打印。

RTL的对象类型:表达式,整数,宽整数,字符串和向量。其中表达式是最重要的一类对象。一个RTL表达式类似于一个c的结构体,通常用指针来引用它,指针类型定义为RTX,每个RTX都具有自己的内部数据结构与外部语法。

RTL在一个指令序列(INSN)中如下图 所示。


GCC中,函数代码的RTL表示被称为INSNs,所有的INSNs被一个双向链表所连接(注意,不是循环双向链表)。有些INSN表示实际的指令,有些代表switch语句的跳转表,有些表示程序跳转所对应的标号,还有一些可以表示各种不同的声明信息。INSN的格式为:

(insn 第0个操作数   第1个操作数  第2个操作数  第3个操作数  第4个操作数 第5个操作数 第6个操作数 第7操作数)

在一个函数中,每个INSN都具有一个唯一的标识(ID,用整数表示),用来将其与其它的INSN区分开来,上图中“insn 7 6 8 3 expr_plus.c:6”insn后的第一个操作数“7”表示该指令的ID,其访问该ID的宏为“INSN_UID(INSN)”。同时,每个INSN都包含了指向其前驱,前驱(PREV_INSN(INSN))在insn中是用第二个操作数“6”表示的。INSN的第三个操作数“8”表示其后继(NEXT_INSN(INSN))节点。INSN的第四个操作数是“3”表示该条INSN指令序列所在的基本块(Basic Block,BB)位置,其宏定义为“BLOCK_FOR_INSN(INSN)”。INSN的第五个操作数为该INSN的主体,描述了该INSN的RTL指令模板,使用PATTERN(INSN)宏进行访问。第6操作数为INSN的代码(INSN CODE),即该INSN描述动作所对应的指令的索引值,使用INSN_CODE(INSN)进行访问,其输出格式为“i”。最后一个操作数,即第7操作数表示空(nil),意义为未使用。

      所有的INSN中都包含了INSN的标识、该INSN的前驱节点指针以及该INSN的后继节点指针等3个字段。INSN的标识字段的值可以通过XINT宏定义获取,其前驱INSN节点指针及后继INSN节点指针均可以通过XEXP宏定义获取。

1.另外GCC中也定义了专门的宏定义用来访问这几个字段。

2.INSN_UID(i):访问insn i的唯一的ID值。

3.PREV_INSN(i):访问insn i的前驱节点指针,如果i是第一个insn,则返回空指针。

4.NEXT_INSN(i):访问insn i的后继节点指针,如果i是最后一个insn,则返回空指针。

在一个insn链表中,如果insn i不是第一个节点,则:NEXT_INSN (PREV_INSN (i)) == i返回true;如果insn i不是最后一个节点,则:PREV_INSN (NEXT_INSN (i)) == i 返回true

INSN中,其主体为PARTTERN,也就是该条INSN序列的RTL。在RTL中,RTXRTL的基本元素,表达式的语法一般形式为(OPERAND_CODE:Mode OPN1 OPN2…)。

OPERAND_CODE为操作码。该操作码指明RTX表示的操作类型,如表示一条RTX指令,除此之外,OPERAND_CODE还能确定RTX的操作个数和这些操作数的种类。Mode表示机器模式。机器模式表示数据和运行结果的类型,常用的类型有QImodeSImodeSFmodeDFmode,它反映了数据类型与字长2部分信息modeSI是一个简写,其全称是SImode表示一个32为的整型数。在RTL表示中,数据类型分为整型、浮点类型和复型3种,机器字长分为8位、16位、32位、双字64位等。这2部分信息有条件组合,所构成的机器模式反映了机器能表示的各种数据类型RTL可以用直观的图示表示出来,如下图所示。



上图是一个rtx表达式例子表示意义展开的树状结构,其中,操作码‘insn’指向这是一条表示指令的rtx012操作数均为整数,3操作数为一个rtx表达式,表示该指令的动作,省略了4以后的操作数。第3操作数中的‘set’、‘mem’、‘plus’分别表示赋值,存储器引用和加法运算,它们的操作数均为rtx表达式;regconst_int分别表示寄存器引用和整数引用,他们的操作数分别为一般整型和宽整型。

RTX使用结构体rtx_def进行存储,定义在gcc/rtl.h中。该结构体分为两大部分:

1) rtx首部(rtx Header),其中描述了该rtxCODErtx的机器模式,以及一些rtx标志;所有rtx首部的长度都是相同的,可以使用RTX_HDR_SIZE宏定义来获得,该定义形如:#define RTX_HDR_SIZE offsetof (struct rtx_def, u) 即首部的长度即是字段u再结构体rtx_def内的偏移量(以字节计算)。

2) rtx的第1操作数描述。该操作数使用union u进行描述,可以表示一个rtunion联合体表示的某一种操作数、或者一个HOST_WIDE_INT宽度的整数、或者一个block_symbol结构体、或者一个实数或者定点数。如下图所示:

所有的rtx都使用rtx_def结构体进行描述。前面也介绍了rtx包含了各种RTX CODERTL表达式,每种表达式所包含的操作数数目和类型也不尽相同,所以每种rtx的实际存储大小也不尽相同,可以使用RTX_CODE_SIZE(CODE)来获得代码为CODErtx所占用的实际存储大小,其定义如下:#define RTX_CODE_SIZE(CODE) rtx_code_size[CODE]

rtx_code_size[]数组的初始化在gcc/rtl.c中完成,如下所示:

DEF_RTL_EXPR(INSN, "insn", "iuuBieie", RTX_INSN)为例,其RTX CODEINSNFORMAT"iuuBieie",因此rtx_code_size[INSN]的大小为:

RTX_HDR_SIZE + (sizeof "iuuBieie" - 1) * sizeof (rtunion))

其中sizeof "iuuBieie"的值为9,所以(sizeof "iuuBieie" - 1)8,即操作数的个数,每个操作数的大小取sizeof (rtunion),因此,存储RTX代码为INSN的结构体的实际大小为rtx的首部大小RTX_HDR_SIZE,再加上存储所有操作数的存储空间(sizeof "iuuBieie" - 1) * sizeof (rtunion))。如下图所示给出了RTXCODE=INSN)的存储结构:


经过上面对 GCC 编译器的整体结构分析可以总结得出 GCC 编译器中主要包含三种中间语言表示形式,分别为 GENERIC层,GIMPLE层(包含高级 GIMPLE 和低级 GIMPLE 以及 SSA)和 RTL表示。这三种中间语言都是与前端语言无关的,其中 GENERIC 树是将前端语言直接翻译过来后形成的中间树,而GIMPLE 则是简化了的 GENERIC 树的集合,在将 GENERIC 转化为 GIMPLE 的过程中将 GENERIC 树中比较复杂的语句都转化为了多个比较简单的语句,其中的计算结果用临时变量来保存RTL是通过低级GIMPLE转化而来的,将低级GIMPLE 树转化为 RTL 树的主要目的是进行优化

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值