基于LLVM-for循环结构的支持

循环结构通常需要先初始化一个归纳变量,然后之后就要对这个变量做更新(增加或减少其数值),以及一个表示循环结束的终止条件,我们的for循环可以定义如下所示,其中i=1表示的是条件、i

def printstar(n x)
    for  i = 1, i < n, 1.0 in
    x + 1

还是一样的我们先去配置token

enum Token_Type {
    EOF_TOKEN = 0,
    NUMERIC_TOKEN,
    IDENTIFIER_TOKEN,
    LEFT_PARAN_TOKEN,
    RIGHT_PARAN_TOKEN,
    DEF_TOKEN,
    COMMA_TOKEN,
    IF_TOKEN,
    THEN_TOKEN,
    ELSE_TOKEN,
    FOR_TOKEN, //for循环的支持
    IN_TOKEN //in的支持
};

然后对于实现词法分析逻辑的函数get_token当中增加了两个判断

  if(Identifier_string=="for")
  {
      return FOR_TOKEN;
  }
  if(Identifier_string=="in")
  {
      return IN_TOKEN;
  }

然后我们再去定义其的AST的类结构

//为for循环去定义AST
class ExprForAST : public BaseAST {
    //定义变量标识
    std::string Var_Name;
    //这里是定义的Start、End、Step、Body的结构,可能是表达式也可能是数字也可能是字母等,所以用BaseAST去表示
    BaseAST *Start, *End, *Step, *Body;

public:
    ExprForAST(const std::string &varname, BaseAST *start, BaseAST *end,
               BaseAST *step, BaseAST *body)
    : Var_Name(varname), Start(start), End(end), Step(step), Body(body) {}
    Value *Codegen() override;
};

然后我们再去为循环结构去定义其解析的函数

Value *ExprForAST::Codegen() {

    //生成start条件的中间代码生成
    Value *StartVal = Start->Codegen();

    if (StartVal == 0)
        return 0;
    //获得函数实体
    Function *TheFunction = Builder.GetInsertBlock()->getParent();
    //获得插入的点
    BasicBlock *PreheaderBB = Builder.GetInsertBlock();
    //这里就是将loop:给插入到TheFunction当中
    BasicBlock *LoopBB =
    BasicBlock::Create(MyGlobalContext, "loop", TheFunction);

    //然后去创建br指令跳转也就是直接是在entry下面的br label %loop指令
    Builder.CreateBr(LoopBB);

    //设置插入点为loop块
    Builder.SetInsertPoint(LoopBB);

    //创建phi节点
    PHINode *Variable = Builder.CreatePHI(Type::getInt32Ty(MyGlobalContext),
                                          2, Var_Name.c_str());

    //生成phi节点可以从两个基本块当中得到变量i的两个值
    //其中一个是%entry块表示在循环初始化的时候对i的赋值是1
    //而%loop块对i的值进行更新,完成循环的一次迭代
    /*
     %i = phi i32 [ 1, %entry ], [ %nextvar, %loop ]
     */
    //preheaderBB块代表是entry块
    Variable->addIncoming(StartVal, PreheaderBB);

    //根据Var_Name也就是i获取旧的值
    Value *OldVal = Named_Values[Var_Name];
    //然后将新的值给穿进去也就是Variable其实就是PHInode节点
    Named_Values[Var_Name] = Variable;

    //然后判断函数体生成是否失败,失败就返回0
    if (Body->Codegen() == 0)
        return 0;

    //然后去判断步长
    Value *StepVal;
    if (Step) {

        //如果步长存在就直接去调用IR代码生成的函数
        StepVal = Step->Codegen();
        if (StepVal == 0)
            return 0;
    } else {
        //步长不存在就去使用默认的步长
        StepVal = ConstantInt::get(Type::getInt32Ty(MyGlobalContext), 1);
    }
    //创建一个加法指令
    Value *NextVar = Builder.CreateAdd(Variable, StepVal, "nextvar");

    //结束代码的生成
    Value *EndCond = End->Codegen();
    if (EndCond == 0)
        return EndCond;
    //创建一个比较指令
    EndCond = Builder.CreateICmpNE(
                                   EndCond, ConstantInt::get(Type::getInt32Ty(MyGlobalContext), 0), "loopcond");
    //获取插入点
    BasicBlock *LoopEndBB = Builder.GetInsertBlock();

    //插入到函数当中
    BasicBlock *AfterBB =
    BasicBlock::Create(MyGlobalContext, "afterloop", TheFunction);

    //创建跳转指令
    Builder.CreateCondBr(EndCond, LoopBB, AfterBB);

    //设置插入点,之后的插入就都在这个afterloop块里面的
    Builder.SetInsertPoint(AfterBB);

    //再去设置phi节点的另外一个label,其实就是loop块,LoopEndBB代表的就是loop块
    Variable->addIncoming(NextVar, LoopEndBB);

    //旧的值存在的就再去重新设置为1
    if (OldVal)
        Named_Values[Var_Name] = OldVal;

    else
        //删除这个节点像上面这样只是删除单个节点
        Named_Values.erase(Var_Name);

    return Constant::getNullValue(Type::getInt32Ty(MyGlobalContext));
}

然后我们还需要在Base_Parser当中去根据token类型去调用相应的解析函数

static BaseAST * Base_Parser(){

    switch (Current_token) {
        default:return 0;
        case IDENTIFIER_TOKEN: return identifier_parser();
        case NUMERIC_TOKEN: return numeric_parser();
        case '(':return paran_passer();
        //根据IF_TOKEN去返回,说明当前Current_token就是IF_TOKEN
        case IF_TOKEN:return If_parser();
        //根据for
        case FOR_TOKEN:return For_parser();
    }
}

这里的Base_Parser函数会在Driver函数中被间接调用我们可以看下Driver函数,这个函数可以理解为是驱动函数,就去去做对整个程序进行分析的

static void Driver(){
    while (1) {
        switch (Current_token) {
            case EOF_TOKEN:return;
            case ';':next_token();break;
            case DEF_TOKEN:HandleDefn();break;
            default:HandleTopExpression(); break;
        }
    }
}

对上面的代码段总的分析过程其实就是遇到def先去执行HandleDefn函数,然后这个函数会去执行func_defn_parser函数

static void HandleDefn(){

    if(FunctionDefnAST * F = func_defn_parser())
    {
        if(Function * LF = F->Codegen())
        {

        }
    }
    else
    {
        next_token();
    }
}

关于func_defn_parser函数其实就是分析函数实现的函数,然后就会去调用expression_parser函数

static FunctionDefnAST *func_defn_parser(){
    next_token();
    //创建函数声明的AST
    FunctionDeclAST * Decl = func_decl_parser();
    //判断Decl
    if(Decl==0) return 0;
    //如果函数实现存在的话
    if(BaseAST * Body = expression_parser())
    {   
        //分析结束返回这个,然后就返回
        return new FunctionDefnAST(Decl,Body);   
    }

    return 0;
}

关于表达式分析函数,先对左边进行分析,其实就是去调用Base_Parser方法进行分析,这个函数上面提到过,然后就可以去找到相应的token进行分析了

//解析表达式的函数
static BaseAST * expression_parser(){
    //进行左边部分的解析
    BaseAST * LHS = Base_Parser();
    if(!LHS) return 0;
    return binary_op_parser(0,LHS);
}

没有经过优化的分析结果如下所示
这里写图片描述

我们这里可以看到其生成了两条add指令,其实就是因为在使用BinaryAST::Codegen()函数的时候会再去生成

//二元表达式的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)是把字符串转换成整型数的一个函数
    //LLVM指令遵循严格的约束:例如,add指令的Left、Right操作数必须同属一个类型,结果的类型则必须与操作数的类型相容
    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");
        //表示运算符如果是小于的话
        case '<':
            //生成一个比较指令,icmp”指令根据其两个整数、整数向量、指针或指针向量操作数的比较返回布尔值或布尔值向量。
            //ult表示少于
            //相当于会生成这样的指令%cmptmp = icmp ult i32 %x, %addtmp
            L=Builder.CreateICmpULT(L,R,"cmptmp");
            //生成一个bool指令,“zext”指令接受要转换的值,以及要转换的类型。这两种类型都必须是整数类型,或者是相同数量的整数的向量。该值的位大小必须小于目标类型ty2的位大小。
            //  相当于会生成这样的指令 %booltmp = zext i1 %cmptmp to i32
            return Builder.CreateZExt(L,Type::getInt32Ty(MyGlobalContext),"booltmp");
        default:
            break;
    }

如果我们使用了pass进行优化得到的指令就可以了
这里写图片描述

猜你喜欢

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