基于支配树的alloca2reg
一些基本概念
CFG控制流图
函数是由一系列基本块以及基本块的跳转关系组成的,基本块与基本块的跳转关系,组成了CFG
控制流图。
int f() {
int i = 0;
int count = 0;
for(; i < 10 ; i++) {
count++;
}
return 0;
}
其生成的中间代码大致为
def i32 @f() {
Entry:
%i = alloca i32, align 8
%count = alloca i32, align 8
store i32 0 ptr %i, align 8
store i32 0 ptr %count align 8
br Cond
Cond:
%1 = load i32 ptr %i
%2 = %1 < 10
br i1 %2 Body, After
Body:
%3 = load i32 ptr %count
%4 = add nsw i32 %3 i32 1
store i32 %4 ptr %count
%5 = load i32 ptr %i
%6 = add nsw i32 %5, i32 1
store i32 %6 ptr %i
br Cond:
After:
ret i32 0;
}
这个控制流图为
Entry
Cond
Body
After
在上面的控制流图中,Entry
是入口节点, Cond
的前驱节点为Entry
, Cond
的后继节点为Body
和After
, 而且可以看到Body
的前驱节点为Cond
,后继节点也是Cond
, 这个是允许存在的。为了简化后面的表示,用prev(X)
来代表X的前驱节点,用succ(X)
来代表X的后继节点。
支配树相关的概念
- 基本块A**支配(dominate)**基本块B, 表示从控制流图顶层节点出发到基本块B的路径中,必定经过基本块A, 那么就称基本块A支配基本块B。数学表示为 A 支配 B A支配B A支配B或者 A d o m B A\quad dom \quad B AdomB
- 基本块A**直接支配(immediate dominate)**基本块B,表示基本块A支配基本块B,并且基本块A不等于B,那么称基本块A直接支配基本块B。数学表示为 A 直接支配 B A直接支配B A直接支配B或者 A i d o m B A \quad idom \quad B AidomB
- 支配边界(dominant boundary),基本块A的支配边界,称为DF(A), 它是一个集合,对于**DF(A)**中的任意元素X,满足A支配X的前驱节点,但是A不支配X。数学表示为 D F ( A ) = { X ∣ A 支配 X 的前驱节点,且 A 不支配 X } DF(A) = \{X|A支配X的前驱节点,且 A不支配X\} DF(A)={X∣A支配X的前驱节点,且A不支配X}
- 支配树由一个根节点(CFG入口节点)和一系列的子节点组成的一个由支配关系构建的树,每一个节点的子节点都是被父节点直接支配,比如上图,其支配树为
Entry
Cond
Body
After
alloca2reg算法
像alloca
, load
以及store
这类的指令,都是要访存的,在现代处理器中,CPU访存的速度比读取寄存器的速度要慢很多,所以alloca2reg
的目的就是尽量将在内存上的操作变成在寄存器上的操作,以此来提升程序运行速度。
实现alloca2reg, 需要有几个步骤。
- 首先我们需要知道
CFG
的支配关系,关于支配树的构建算法,有现成的论文可以参考,比如说节点删除法, Lengauer-Tarjan算法这里不赘述,我们使用现成的求解算法,llvm
中DominatorTree在llvm/IR/Dominators.h中
提供了计算支配树的算法,其提供的API
如下。
函数 |
描述 |
recalculate(ParentTypen& F) |
支配树, 这里参数可以是Function |
operator[] |
获取某个基本块的支配信息 |
getIDom() |
获取某个基本块的直接支配者 |
getBlock() |
或缺这个支配节点的基本块 |
使用方法
#include "llvm/IR/Dominatore.h"
DominatorTree DT;
/// 计算支配树
DT.recalculate(F);
/// 获取某个基本块的直接支配者
BasicBlock* idomBB = DT[BB]->getIDom()->getBlock();
- 在获取基本块的支配关系之后(即拿到支配树之后)然后就是需要知道每一个基本块的支配边界,因为PHI节点的插入需要有支配边界的信息。支配边界的计算算法如下。
/// 算法伪代码
/// DF(X)代表基本快X的支配边界
/// for B in F
/// DF(B) = {}
/// for B in F {
/// if B 有多个前驱节点 {
/// for(runner in prev(B)) {
/// runner = p
/// while(runner != idom(B)) {
/// DF(runner) = DF(runner) U {B}
/// runner = idom(runner)
/// }
/// }
/// }
/// }
void calcuDf(Function& F) {
for(BasicBlock& BB : F) {
DomFsBlock[&BB] = {};
}
BasicBlock *prevBB, *idomBB;
for(BasicBlock& BB : F) {
if(pred_size(&BB) > 1) {
idomBB = DT[&BB]->getIDom()->getBlock();
for(auto prev : predecessors(&BB)) {
prevBB = dyn_cast(prev);
while(prevBB != idomBB) {
DomFsBlock[prevBB].push_back(&BB);
prevBB = DT[prevBB]->getIDom()->getBlock();
}
}
}
}
}
-
在得到支配边界之后,还需要一些其他的工作,比如收集promoted
的alloca
指令,以及他们定义和使用的地方
-
在有了这些信息之后,下面就是插入PHI节点了。
/// 算法伪代码
/// for a in allocas
/// W = {}
/// F = {}
/// for bb in Def[a]
/// W = W U {bb}
/// while W not empty
/// X = W.back()
/// W.removeback()
/// for Y in DF(X)
/// if(Y not in F)
/// add phi(a) to Y
/// F = F u {Y}
/// 具体C++代码实现
for(AllocaInst* alloca : Allocas) {
PhiSet.clear();
W.clear();
phi = nullptr;
for (BasicBlock* BB : DefsBlock[alloca]) {
W.push_back(BB);
}
while(!W.empty()) {
X = W.back();
W.pop_back();
for(BasicBlock* Y : DomFsBlock[X]) {
if(PhiSet.find(Y) == PhiSet.end()) {
phi = PHINode::Create(alloca->getAllocatedType(), 0);
phi->insertBefore(dyn_cast(Y->begin()));
PhiSet.insert(Y);
PhiMap[Y].insert({phi, alloca});
if(std::find(W.begin(), W.end(), Y) == W.end())
W.push_back(Y);
}
}
}
}
- 在插入
phi
节点之后,下面就是填充phi
节点了, 即rename
, 其伪代码为
WorkList = {}
IncommingValue = Null;
for(alloca in Allocas)
IncommingValue[alloca] = Undef
WorkList.add({EntryBB, IncommingValue})
while WorkList not empty
BB = WorkList.back()[0]
IncommingValue = WorkList.back()[1]
WorkList remove back
for inst in BB
if inst is load
if load's var in Allocas
remove load
replace load's user with IncommingValue[load's var]
else if inst is store
if store's var in Allocas
remove store
IncommingValue[store's var] = store's value
else if inst is phi
IncommingValue[phi's var] = phi
for succBB in succ(BB)
WorkList.add({succBB, IncommingValue})
for phi in succBB
phi.addIncommingValue(IncommingValue[phi's var], BB)
项目源代码
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Dominators.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/IR/CFG.h"
#include
#include
#include