1 背景介绍
本系列的前两篇博文分别介绍了 LLVM 在做寄存器分配前的准备工作和线性扫描算法的理论基础,有了这些背景知识后,就可以开始进入 LLVM 中真实的寄存器分配器了。在 LLVM3.0 中,新提出了两个寄存器分配器:basic/greedy 分配器,它们的核心算法都是线性扫描算法的变种。其中,greedy 分配器是新版本 LLVM 默认的寄存器分配器,而 basic 分配器可以理解为一个简化版的 greedy 分配器。因此,这两个分配器的中心思想相差不大,所使用的关键数据结构也没什么区别。所以,本篇博文先介绍 basic 分配器,为后续学习 greedy 分配器打下基础。
2 basic 分配算法与线性扫描算法
事实上,basic 分配器生成的代码质量与线性扫描算法相差无几。同时,为了提高代码质量,basic 分配器也依赖虚拟寄存器重写器(rewriter)来清除冗余代码。可以说,basic 分配算法与线性扫描算法相比并没有特别大的提高。那么,LLVM 为何要提供此分配器呢?其实,我们可以把 basic 分配器理解为一个实验性质的分配器,LLVM 提出 basic 分配器主要为了用它来测试新式分配框架的性能。因此,basic 分配器实现得十分简单,只是把大体的分配框架实现出来,并没有充分压榨分配算法。相对而言,默认的 greedy 分配器才是成熟的寄存器分配器,充分压榨了分配算法的每一处潜能。两种分配器的基本框架如下图所示(其中左图为 basic 分配器,右图为 greedy 分配器):
3 basic 分配器详解
LLVM 给各个寄存器分配器提供了一套公用的接口,即 RegAllocBase 类。在这个类里,寄存器分配器的基本框架已经构造完成了,我们只需要重载部分函数即可以实现想要的寄存器分配器。一个寄存器分配器必须做以下两个工作:第一,该分配器必须覆盖 selectOrSplit 方法,我们可以在这里给分配器加各种有趣的启发式;第二,该分配器必须覆盖 enqueue/dequeue 方法,用于提供自己的寄存器分配顺序。下面,开始介绍 basic 寄存器中所做的这两样工作。
3.1 priority queues 与分配顺序
在线性扫描算法中,active list 决定了扫描顺序必须是线性的,即按 interval 起始位置的升序扫描。这种分配顺序显然是有问题的,它没有考虑到溢出的代价。考虑这样一种情况,假设我们现在有一个 active list,所有寄存器都分配给里面的 interval 了,如若下一次扫描到的 interval 溢出代价很大,且与 active list 中所有的 interval 相交。那么,