Kaleidoscope 语言: Mutable Variables

在前面的章节中,我们遗留了一个重要的内容,就是可变变量,我们用了 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 节点:

  1. 每个可变变量都成为堆栈分配。
  2. 变量的每次读取都成为堆栈中的负载。
  3. 变量的每次更新都将成为堆栈的存储。
  4. 获取变量的地址只是直接使用堆栈地址。

代码修改 #

为了做到上面的工作,主要修改就是 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));
}

其他的就类似,就不再枚举

comments powered by Disqus