改进干涉图节点

首先需要说明,目前的框架中尚未做这个改进,不过做起来也不太难,也许未来框架中会加上。

课堂上,以及上一章中,我们都将干涉图节点定义为虚拟寄存器。这其实并不是最优的,考虑下面这个程序:

void foo(int n) {
  int i;
  for (i = 0; i < n; i = i + 1) {
    // ...
  }
  for (i = 0; i < n; i = i + 1) {
    // ...
  }
}

虽然在两个循环中都使用i作为循环变量,但是这两个i其实是没有任何关系的。按照我们的翻译策略,自始至终都会以同一个虚拟寄存器来表示i,这样与i冲突的节点将包括两个循环中的虚拟寄存器。然而,如果这样改写程序:

void foo(int n) {
  int i1, i2;
  for (i1 = 0; i1 < n; i1 = i1 + 1) {
    // ...
  }
  for (i2 = 0; i2 < n; i2 = i2 + 1) {
    // ...
  }
}

这样与分别i1i2冲突的节点都只包括一个循环中的虚拟寄存器。不难想象,修改后的程序构造出来的干涉图将更加容易着色。如果我们要在不修改程序的情况下,让第一个程序达到和第二个程序一样的效果,就必须允许在不同的程序点上,同一个虚拟寄存器使用不同的物理寄存器,也就是说干涉图节点不应该定义为虚拟寄存器。

我们引入生命周期的概念,一个生命周期由一个变量的定值点,以及所有这个定值活跃的程序点的集合组成。一个定值在一点活跃定义为:这个定值可以到达这里,并且这一点处定值的变量活跃。根据这个定义,不难设想生命周期的计算方式:先进行到达-定值分析和活跃变量分析,然后对于每个定值点,寻找所有这个定值可以到达并且定值的变量活跃的程序点。

我们希望能够对于每个生命周期分配一个物理寄存器,而不是为每个虚拟寄存器分配一个物理寄存器。为了达成这一点还需要做一点修正,考虑这个程序片段:

int a;
if (b) {
  a = ...;
} else {
  a = ...;
}
// use a

按照上面的定义,a的两次定值会形成两个生命周期,它们可以使用不同的物理寄存器。但是这是不对的,因为下面的a的使用点处只能使用一个确定的物理寄存器。为了避免这样的情况发生,需要进行生命周期的合并:如果同一个虚拟寄存器的两个生命周期发生了重叠,那么这两个生命周期需要被合并为一个,让它们只能使用同一个物理寄存器。

至此,干涉图的定义可以修改为:干涉图的节点是(合并后的)所有生命周期,如果两个生命周期有重叠,就在两个节点间连一条边。除了干涉图的定义之外,算法的其它部分不需要有任何变化。

对于修改前的例子,i总共有4个定值点(假设循环体内只使用i,没有给i定值),两个for中的i = 0i = i + 1对应的生命周期会被合并成一个(因为它们在循环体内有重叠),从而总共有两个生命周期,它们在寄存器分配时作为两个独立的节点,这样就和修改后的例子效果完全一样了。

Last updated