在前面的章节中,我们遗留了一个重要的内容,就是可变变量,我们用了
PHI
操作来完成了 For
If
等逻辑,但是实际上我们可以使用更符合直觉的多次赋值的方式。
当前的问题 #
以
int G, H;
int test(_Bool Condition) {
int X;
if (Condition)
X = G;
else
X = H;
return X;
}
为例子,我们现在生成的 LR
是这样的
@G = weak global i32 0 ; type of @G is i32*
@H = weak global i32 0 ; type of @H is i32*
define i32 @test(i1 %Condition) {
entry:
br i1 %Condition, label %cond_true, label %cond_false
cond_true:
%X.0 = load i32, i32* @G
br label %cond_next
cond_false:
%X.1 = load i32, i32* @H
br label %cond_next
cond_next:
%X.2 = phi i32 [ %X.1, %cond_false ], [ %X.0, %cond_true ]
ret i32 %X.2
}
解决之道 #
使用栈变量地址来完成工作
@G = weak global i32 0 ; type of @G is i32*
@H = weak global i32 0 ; type of @H is i32*
define i32 @test(i1 %Condition) {
entry:
%X = alloca i32 ; type of %X is i32*.
br i1 %Condition, label %cond_true, label %cond_false
cond_true:
%X.0 = load i32, i32* @G
store i32 %X.0, i32* %X ; Update X 使用变量栈地址更新 X
br label %cond_next
cond_false:
%X.1 = load i32, i32* @H
store i32 %X.1, i32* %X ; Update X 使用变量栈地址更新 X
br label %cond_next
cond_next:
%X.2 = load i32, i32* %X ; Read X 使用变量栈地址读取 X
ret i32 %X.2
}
有了这个,我们发现了一种处理任意可变变量的方法,而根本不需要创建 Phi 节点:
- 每个可变变量都成为堆栈分配。
- 变量的每次读取都成为堆栈中的负载。
- 变量的每次更新都将成为堆栈的存储。
- 获取变量的地址只是直接使用堆栈地址。
代码修改 #
为了做到上面的工作,主要修改就是 NamedValues,我们修改其定义
static std::map<std::string, AllocaInst*> NamedValues;
在函数的入口,我们定义变量
/// 在函数块的入口创建一个变量
static AllocaInst *CreateEntryBlockAlloca(Function *TheFunction,
const std::string &VarName) {
IRBuilder<> TmpB(&TheFunction->getEntryBlock(),
TheFunction->getEntryBlock().begin());
return TmpB.CreateAlloca(Type::getDoubleTy(TheContext), 0,
VarName.c_str());
}
修改 VariableExprAST::codegen
llvm::Value* VariableExprAST::codegen() {
// 修改为获取 AllocaInst
llvm::AllocaInst *A = NamedValues[Name];
if (!A)
LogErrorV("Unknown variable name");
// 读取这个值
return Builder->CreateLoad(A->getAllocatedType(), A, Name.c_str());
}
下面就要修改大部分使用到 PHI
和变量的地方。
For 修改 #
比如 For
循环中
llvm::Value* ForExprAST::codegen() {
llvm::Function* TheFunction = Builder->GetInsertBlock()->getParent();
// 创建循环变量
llvm::AllocaInst* Alloca = CreateEntryBlockAlloca(TheFunction, VarName);
// 计算初始化值
llvm::Value* StartVal = Start->codegen();
if (!StartVal)
return nullptr;
// 将初始化值赋值于变量
Builder->CreateStore(StartVal, Alloca);
llvm::BasicBlock* LoopBB = llvm::BasicBlock::Create(*TheContext, "loop", TheFunction);
Builder->CreateBr(LoopBB);
Builder->SetInsertPoint(LoopBB);
// 每次循环都从 NV 里面获取变量
llvm::AllocaInst* OldVal = NamedValues[VarName];
NamedValues[VarName] = Alloca;
// 中间略一些
// Step 和变量进行 + 操作
llvm::Value* CurVar = Builder->CreateLoad(Alloca->getAllocatedType(), Alloca, VarName.c_str());
llvm::Value* NextVar = Builder->CreateFAdd(CurVar, StepVal, "nextvar");
Builder->CreateStore(NextVar, Alloca);
// 中间略一些
// 如果外部有同名变量就是恢复一下
if (OldVal)
NamedValues[VarName] = OldVal;
else // 直接清除变量
NamedValues.erase(VarName);
return llvm::Constant::getNullValue(llvm::Type::getDoubleTy(*TheContext));
}
其他的就类似,就不再枚举