为AST类定义IR代码的生成

上次说到的是现在所有的必要的信息都是存储在AST这一个数据结构当中,下一个阶段就是从AST中生成的LLVM IR在代码的生成的过程当中,我们可以去使用LLVM的API,通过内建的API可以生成预定义格式的LLVM IR

我们需要在BaseAST类和其的子类当中去添加Codegen函数,因为我们需要去生成其的中间表示形式的代码,其中在有些子类当中的Codegen函数的返回值是LLVM Value对象,它表示了静态单赋值(SSA)对象,SSA其实就是静态单赋值形式,这种形式是一种特殊形式的中间码,每个变量仅仅被赋值一次,在SSA中间表示中,可以保证每个被使用的变量都有唯一的定义,即SSA能带来精确的使用–定义关系

我们需要在全局作用域当中去声明如下的静态变量

//Module_Ob模块包含了代码中的所有函数和变量
static Module *Module_Ob;
//Builder对象帮助生成LLVM IR 并且记录程序的当前点,以插入LLVM指令,以及Builder对象有创建新指令的函数
static IRBuilder<> Builder(MyGlobalContext);
//Named_Values map对象是记录当前作用域中的所有已定义的值,充当符号表的功能,对于TOY语言来说这个map会包含函数参数信息
static std::map<std::string,Value *>Named_Values;

下面我们来看下数值变量的Codegen函数的定义

Value *NumericAST::Codegen()
{
    //在LLVM IR当中,整数常量由ConstantInt类表示,它的值由APInt类持有
    return ConstantInt::get(Type::getInt32Ty(MyGlobalContext), numeric_val);
}

为变量表达式生成代码的函数定义如下所示

Value * VariableAST::Codegen()
{
    Value * V = Named_Values[Var_Name];
    return V ? V:0;
}

二元表达式的Codegen()函数,如果这里生成了多个addtmp变量,LLVM会自动的为每一个addtmp添加递增的、唯一的数值后缀加以区分

Value *BinaryAST::Codegen(){

    Value * L = LHS->Codegen();
    Value * R = RHS->Codegen();

    if(L==0||R==0) return 0;
    //atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
    switch (atoi(Bin_Operator.c_str())) {
        case '+':
            return Builder.CreateAdd(L, R,"addtmp");
        case '-':
            return Builder.CreateSub(L,R, "subtmp");
        case '*':
            return Builder.CreateMul(L,R, "multmp");
        case '/':
            return Builder.CreateUDiv(L,R, "divtmp");
        default:
            return 0;
    }
}

解析函数调用的时候,会递归的对其传递的参数调用Codegen()函数,最后再去创建LLVM调用指令

Value *FunctionCallAST::Codegen()
{
    Function * CalleeF = Module_Ob->getFunction(Function_Callee);
    //函数参数
    std::vector<Value *>ArgsV;
    //遍历函数参数

    for (unsigned i = 0, e = Function_Arguments.size(); i != e; ++i)  {
        ArgsV.push_back(Function_Arguments[i]->Codegen());
    }
    return Builder.CreateCall(CalleeF,ArgsV,"calltmp");
}

函数声明的Codegen(),需要注意的是该函数的返回值类型是“Function*”而不是“Value*”。“函数原型”描述的是函数的对外接口(而不是某表达式计算出的值),返回代码生成过程中与之相对应的LLVM Function自然也合情合理

Function *FunctionDeclAST::Codegen()
{
std::vector<Type*>Integers(Arguments.size(),Type::getInt32Ty(MyGlobalContext));
//我们定义函数的参数为int类型,和常数一样,LLVM中的类型对象也是单例,应该用“get”而不是“new”来获取
    //因此第一行创建了一个包含“N”个LLVM int的vector。随后,FunctionType::get方法以这“N”个int为参数类型、以单个int为返回值类型,创建出一个参数个数不可变
    FunctionType * FT = FunctionType::get(Type::getInt32Ty(MyGlobalContext), Integers, false);

    /*
     下面实际上创建的是与该函数原型相对应的函数。其中包含了类型、链接方式和函数名等信息,还指定了该函数待插入的模块。“ExternalLinkage”表示该函数可能定义于当前模块之外,且/或可以被当前模块之外的函数调用。Name是用户指定的函数名:如上述代码中的调用所示,既然将函数定义在“TheModule”内,函数名自然也注册在“TheModule”的符号表内。
     */
    Function *F = Function::Create(FT, Function::ExternalLinkage, Func_Name, Module_Ob);

    /**
     在这里主要是在处理名称冲突时,Module的符号表与Function的符号表类似:在模块中添加新函数时,如果发现函数名与符号表中现有的名称重复,新函数会被默默地重命名。被重命名过就代表我的名字不一致,那么就要删除相应的函数
     */
    /**我们重定义函数可以允许对同一个函数进行多次extern声明,前提是所有声明中的函数原型保持一致(由于只有一种参数类型,我们只需要检查参数的个数是否匹配即可)。所以我们下面判断名字的时候下面还要去判断参数
     */
    if(F->getName() != Func_Name) {

        //调用eraseFunctionParent)将刚刚创建的函数对象删除
        F->eraseFromParent();

        //然后调用getFunction获取与函数名相对应的函数对象
        F = Module_Ob->getFunction(Func_Name);

        //如果函数体就说明定义过,就返回0,直接拒绝
        if(!F->empty()) return 0;
        //判断还是你的参数如果不等于我们之前构造的数量,那么也直接返回
        if(F->arg_size() != Arguments.size()) return 0;

    }
    unsigned Idx = 0;

    //遍历函数原型的所有参数,为这些LLVM Argument对象逐一设置参数名,并将这些参数注册倒NamedValues映射表内
    for(Function::arg_iterator Arg_It = F->arg_begin(); Idx != Arguments.size(); ++Arg_It, ++Idx) {
        Arg_It->setName(Arguments[Idx]);
       //将函数参数放入到Named_Values
        Named_Values[Arguments[Idx]] = Arg_It;
    }
    return F;
}

函数定义的Codegen()

Function *FunctionDefnAST::Codegen() {

      /*生成函数原型(Proto)的代码并进行校验。与此同时,需要清空NamedValues映射表,确保其中不会残留之前代码生成过程中的产生的内容。函数原型的代码生成完毕后,一个现成的LLVM Function对象就到手了。*/

    Named_Values.clear();

    //生成函数定义的IR代码,
    Function *TheFunction = Func_Decl->Codegen();

    if(TheFunction == 0) return 0;
    /*
     模块(Module),函数(Function),代码块(BasicBlock),指令(Instruction)
     模块包含了函数,函数又包含了代码块,后者又是由指令组成。除了模块以外,所有结构都是从值产生而来的。
     */
    //新建了一个名为“entry”的基本块对象,稍后该对象将被插入TheFunction
    BasicBlock *BB = BasicBlock::Create(MyGlobalContext,"entry", TheFunction);

    //插入BasicBlock 代码块,告诉Builder,后续的新指令应该插至刚刚新建的基本块的末尾处。
    //builder.setInsertPoint 会告知 LLVM 引擎接下来将指令插入何处,简而言之,它指定创建的指令应该附加到指定块的末尾
    Builder.SetInsertPoint(BB);

    //现在该开始设置Builder对象了。LLVM基本块是用于定义控制流图(Control Flow Graph)的重要部件。当前我们还不涉及到控制流,所以所有的函数都只有一个基本块
    //函数体产生
    if(Value *RetVal = Body->Codegen()) {

        Builder.CreateRet(RetVal);
        //验证生成的代码,检查一致性。
        //简单检查一个函数的错误,在调试一个pass时有用。如果没有错误,函数返回false。如果发现错误,则向OS(如果非空)写入描述错误的消息,并返回true。
        verifyFunction(*TheFunction);
        return TheFunction;
    }
    //Error reading body, remove function.
    //错误读取函数体,所以删除函数
    TheFunction->eraseFromParent();
    return 0;
}

然后之后我们就可以去生成可执行文件,去分析我们自己写的语言,分析的语句如下所示

def foo(x,y)
x + y * 16

结果如下所示

; ModuleID = 'my compiler'
source_filename = "my compiler"

define i32 @foo(i32 %x, i32 %y) {
entry:
  %multmp = mul i32 %y, 16
  %addtmp = add i32 %x, %multmp
  ret i32 %addtmp
}

猜你喜欢

转载自blog.csdn.net/zcmuczx/article/details/80740376