[置顶] 长期出售:Godaddy老域名,Godaddy历史BA老域名!

[置顶] 长期出售:搜狗pr域名,搜狗收录域名,搜狗权重域名!

[置顶] 长期出售:高外链域名,高反链域名,权重域名,有收录的域名!

代码生成之指令选择

目标机指令集本身的特性对指令选择的难度有很大的影响。比如,指令集的统一性和完整性是两个很重要的因素。如果目标机没有以统一的方式支持每种数据类型,那么总体规则的每个例外都需要进行特别处理。比如,在某些机器上,浮点数运算使用单独的寄存器完成。
代码生成器必须把IR程序映射成为可以在目标机上运行的代码序列。完成这个映射的复杂性由如下的因素决定:

*IR的层次。

*指令集体系结构本身的特性。

*想要达到的生成代码的质量。

如果IR是高层次的,代码生成器就要使用代码模板把每个IR语句翻译成为机器指令序列。但是,这种逐个语句生成代码的方式通常会产生质量不佳的代码。这些代码需要进一步优化。如果IR中反映了相关计算机的某些低层次细节,那么代码生成器就可以使用这些信息来生成更 加高效的代码序列。

目标代码中的地址

我们将说明如何使用静态和栈式内存分配为简单的过程调用和返回生成代码,以此将IR中的名字转换成为目标代码中的地址。我们描述了每个正在执行的程序是如何在它的逻辑地址空间上运行的。这个空间被划分成为四个代码及数据区域:

1)一个静态确定的代码区Code。这个区存放可执行的目标代码。目标代码的大小可以在编译时刻确定。

2)一个静态确定的静态数据区Static。这个区存放全局常量和编译器生成的其他数据。全局常量和编译器数据的大小也可以在编译时刻确定。

3)一个动态管理的堆区heap。这个区存放程序运行时刻分配和释放的数据对象。Heap的大小不能在编译时刻静态确定。

流图的表示方式和循环

首先,在流图里面把到达指令的序号或标号的跳转指令替换为到达基本块的跳转,这么做是很正常的。回忆一下,所有条2件或无条件跳转指令总是跳转到某些基本块的首指令,而现在这些跳转指令指向了相应的基本块。

这么做的原因是,在流图构造完成之后经常会对多个基本块中的指令做出实质性的改变。如果跳转的目标是指令,我们将不得不在每次改变了某个目标指令之后修正跳转指令的目标。

流图就是通常的图,它可以用任何适合表示图的数据结构来表示。结点(即基本块)的内容需要有它们自己的表示方式。我们可以用一个指向该基本块在三地址指令数组中的首指令的指针,再加上基本块的指令数量或一个指向结尾指令的指针来表示结点的内容。但是,因为我们可能会频繁改变一个基本块中的指令数量,所以为每个基本块创建一个指令链表是一种高效的表示方法。

代数恒等式的使用

在DAG上消除死代码的操作可以按照如下方式实现。我们从一个DAG上删除所有没有附加活跃变量的根结点(即没有父结点的结点)。重复应用这样的处理过程就可以从DAG中消除所有对应于死代码的结点。

代数恒等式表示基本块的另一类重要的优化方法。比如,我们可以使用诸如:

x+0=0+x=x  x-0 =x

x*1=1*x=x  x/1 =x

这样的恒等式来从一个基本块中消除计算步骤。

另一类代数优化是局部强度消减(reduction in strength),就是把一个代价较高的运算替换为 一个代价较低的运算。比如:

从DAG到基本块的重组

对DAG的各种优化处理可以在生成DAG图时进行,也可以在DAG构造完成后通过对DAG的运算完成。在完成这些优化处理之后,我们就可以根据优化得到的DAG重组生成相应基本块的三地址代码。对每个具有一个或多个附加变量的结点,我们构造一个三地址语句来计算其中某个变量的值。我们倾向于把计算得到的结果赋给一个在基本块出口处活跃的变量。但是,如果我们没有全局活跃变量的信息作为依据,就要假设程序的所有变量都在基本块出口处活跃(但是不包含编译器为了处理表达式而生成的临时变量)。

如果结点有多个附加的活跃变量,我们就必须引入复制语句,以便给每一个变量都赋予正确的值。有时我们可以通过全局优化技术,设法用其中的一两个变量来替代其他变量,从而消除这些复制语句。

寄存器和地址描述符

我们的代码生成算法依次考虑了各个三地址指令,并决定需要哪些加载指令来把必需的运算分量加载进寄存器。在生成加载指令之后,它开始生成运算代码。然后,如果有必要把结果存放人一个内存位置,它还会生成相应的保存指令。

为了做出这些必要的决定,我们需要一个数据结构来说明哪些程序变量的值当前被存放在哪个或哪些寄存器里面。我们还需要知道当前存放在一个给定变量的内存位置上的值是否就是这个变量的正确值。因为变量的新值可能已经在寄存器中计算出来但还没有存放到内存中。这个数据结构具有下列描述符:

函数getReg的设计

让我们考虑如何针对一个三地址指令I实现函数getReg(I)。实现这个函数可以选择很多种方法,当然也存在一些绝对不可以选择的方法。这些错误方法会因丢失一个或多个活跃变量的值而导致生成错误代码。我们用处理一个运算指令的步骤来开始我们的讨论,还是用x=y+z作为一般性的例子。首先,我们必须为y和z分别选择一个寄存器。这两次选择所面临的问题是相同的,因此我们将集中考虑为y选择寄存器Ry的方法。选择规则如下:

1)如果y当前就在一个寄存器中,则选择一个已经包含了y的寄存器作为Ry。不需要生成一个机器指令来把y加载到这个寄存器。

消除不可达代码

另一个窥孔优化的机会是消除不可达的指令。一个紧跟在无条件跳转之后的不带标号的指令可以被删除。通过重复这个运算,就可以删除一个指令序列。比如,为了调试的目的,一个大型程序中可能含有一些只有当变量debug等于1时才运行的代码片断。在中间表示形式中,这个代码看起来可能就像

if debug == 1 goto L1
goto L2

L1: print debugging information
L2:

一个显而易见的窥孔优化方法是消除级联跳转指令。因此,不管debug的值是什么,上面的代码序列可以被替换为:

控制流优化

简单的中间代码生成算法经常生成目标为无条件跳转指令的无条件跳转指令,到达条件跳转指令的无条件跳转指令,或者到达无条件跳转指令的条件跳转指令。这些不必要的跳转指令可以通过下面几种窥孔优化技术从中间代码或者目标代码中消除。我们可以把序列

goto L1
...
L1: goto L2

替换为

goto L2
...
LI: goto L2

如果没有跳转到L1的指令,并且语句L1:goto L2之前是一个无条件跳转指令,所以可以消除这个语句。

全局寄存器分配

代码生成算法在单个基本块的运行期间使用寄存器来存放值。但是,在每个基本块的结尾处,所有活跃变量的值都被保存到内存中。为了省略一部分这样的保存及相应的加载指令,我们可以把一些寄存器指派给频繁使用的变量,并且使得这些寄存器在不同基本块中的(即全局的)指派保持一致。因为程序的大部分时间花在它的内部循环上,所以一个自然的全局寄存器指派方法是试图在整个循环中把频繁使用的值存放在固定的寄存器中。从现在开始,假设我们知道一个流图的循环结构,并且我们知道在一个基本块中计算的哪些值会在该基本块外使用。

«697071727374757677787980818283»

热门搜索: 外链域名 高外链域名 高收录域名

Copyright www.thyst.cn. Some Rights Reserved.