编译原理实验四

news2024/11/24 3:29:37

编译原理实验四

实验要求

cminus-f的词法分析和语法分析部分已经完成,最终得到的是语法分析树。而为了产生目标代码,还需要将语法分析树转为抽象语法树,通过抽象语法分析树生成中间代码(即IR),最后使用中间代码来进行优化并生成目标代码。在本实验中,只要完成中间表示IR的生成,语法分析树到抽象语法树的转换已经在框架中提供了,而IR代码到目标代码的生成由clang来完成。

生成IR指令的过程为在访问者模式下访问抽象语法树。本次实验要完成的就是访问抽象语法树的相关visit函数,在这些visit函数中要完成IR指令的生成。

实验难点

1.使用AST和visit函数生成IR指令

由于cminus-f的语法还是比lab3中计算器要复杂的多,供参考的抽象语法树的打印代码也还是和生成IR指令由很大差别,在开始编写时存在困难。通过阅读AST的头文件和lab3的visitor.cpp,首先确定了visit函数生成IR指令的过程:

  • visit函数调用当前节点的子节点accept函数,即调用子节点的visit函数
  • 处理当前节点,生成指令

若子节点需要向父节点传值,则通过全局变量来实现。在visit节点时还要加入语义分析的部分,判断语义是否正确。

为了进一步弄清楚遍历AST生成IR的过程,在各个visit函数中加入logging信息,也为后续排查错误做好准备。然后首先完成较为简单的表达式处理等部分,通过这个过程逐渐熟悉IR转换和层次关系以及传值的过程,最后完成困难的visit函数。并在实现过程中逐步用简单的cminus程序测试,用logging找到出错的位置,完成逐个函数的设计。

以第一个产生式program->declaration-list为例,首先找到节点的定义,program节点包含一个保存了declaration的vector,因此在visit中要逐个调用这些declaration节点的accept函数。Program只需要产生声明,不需要产生IR,只需要进行语义分析,在语义说明中给出了以下规则:

  • 一个程序由一系列声明组成,声明包括了函数声明与变量声明,它们可以以任意顺序排列。
  • 一个程序中至少要有一个声明且最后一个声明必须是 void main(void)形式的函数声明。

只需要对第二条进行检查,判断至少有一个声明,且最后一个声明是否是void main(void)形式的函数声明。在Declaration类型的节点中有id和type,进行检查即可,其中type是CminusType类型,在hpp开头枚举。因此Program节点的visit函数如下:

void CminusfBuilder::visit(ASTProgram &node) { 
    LOG(DEBUG) << "Program\n";
    //语义检查
    if(node.declarations.size()==0){
        cout << "ERROR: 该程序中没有声明。\n";
        return;
    }
    if(!(node.declarations.back()->id=="main"&& node.declarations.back()->type==TYPE_VOID)){
        cout << "ERROR: 最后一个声明不是void main(void)";
        return;
    }
    //访问declarations子节点
    for(auto dec: node.declarations)
        dec->accept(*this);
    return;
}

2.函数参数的处理

在编写FunDeclaration和Param的visit函数时,不明确参数具体的处理方法。找到lab3中由clang生成的.ll,对于参数的处理都是先分配空间,然后使用store指令将参数值存入分配好的空间。例如以下程序:

define i32 @callee(i32 %0) #0 {
    %2 = alloca i32
    store i32 %0, i32* %2
	...
}

但是Param的参数只有id,表示的是源程序中的参数名,对于如何找到这个参数产生了疑惑。重新找到lab3中编写的cpp传参的部分,传参是在函数创建以后,通过Function中的iterator获取得到的。

    //插入callee的BasicBlock中的指令
    std::vector<Value *> args;                      //获取函数的形参,通过Function中的iterator
    for (auto arg = callee->arg_begin(); arg != callee->arg_end(); arg++) {
        args.push_back(*arg);                       //* 号运算符是从迭代器中取出迭代器当前指向的元素
    }
    auto mul = builder->create_imul(args[0], CONST_INT(110));             //mul = a*2

此处获取参数的部分,是直接按lab3提供的gcd_array.cpp仿照编写的,因为理解不够深入,所以在本实验处理参数时又产生了疑惑。回顾lab3此部分代码后,FunDeclaration中的参数存储的部分就没有问题了。与lab3不同的是,FunDeclaration是逐个调用Param的accept函数,完成存储空间分配的,所以需要一个全局变量在FunDeclaration和Param的visit函数之间传递arg。

3.ASTVar的处理

根据cminus的语义说明,Var可以是整型变量,浮点变量或数组变量。如果是数组变量,需要判断下标是否为负,如果为负则添加neg_idx_except指令退出程序,否则计算对应元素的地址(gep指令)。如果是数组,则下标可能是个表达式,需要确保表达式的返回结果为整型,然后才能进行取元素的操作。

从上面的语义说明可以理解Var是一种变量。但是下面的赋值语义说明:先找到var代表的变量地址(如果是数组,需要先对下标表达式求值),然后对右侧的表达式进行求值,求值结果将在转换成变量类型后存储在先前找到的地址中。同时,存储在var中的值将作为赋值表达式的求值结果。一开始结合Var节点中含有一个表达式,把这句话错误的理解为了在Var的visit函数内要完成找到地址,求表达式的值并赋值的指令。后来为了进一步理解Var变量的visit函数内要产生什么指令,继续阅读了剩下的产生式,最终找到Var出现的两种情况:

  • expression→var = expression | simple-expression
  • factor→(expression) | var | call | integer | float

这两种情况下对于var的处理是不同的。对于第一种情况,var是赋值表达式的一个组成部分,在访问赋值表达式时,应该访问Var,在全局变量ret中保存Var的地址,然后进行赋值,产生store指令;而第二种情况,应该将Var的值取出保存到ret中,作为因子参与计算表达式的计算。因此访问Var节点时,需要知道是从哪种情况访问的,为此添加一个全局变量ifAssign,如果访问赋值语句,就将该变量置为true,访问Var时返回地址。

解决了以上问题后,就可以根据是否有指向expression的指针判断是否为数组,然后取出相应的值或者地址了。如果是数组,还要对下标进行处理。如果下标为负则终止程序,这可以通过跳转到一个仅含终止指令的exitBasicBlock实现。

auto ifNegative = builder->create_icmp_lt(idx, CONST_INT(0));
//创建exitBasicBlock和正常继续的BasicBlock
auto exitBB = BasicBlock::create(module.get(), node.id + "exit", func);
auto nextBB = BasicBlock::create(module.get(), node.id + "next", func);
//条件跳转指令
builder->create_cond_br(ifNegative, exitBB, nextBB);

处理非数组变量比较简单,处理数组变量时又产生了问题,因为数组获取元素地址有两种方式,即lab3中的两种getelementptr,分别需要两个偏移和一个偏移。为了明确如何处理数组,得到数组元素的指针,重新比较两种数组的分配的IR形式:

  • %1 = alloca [10 x i32]
  • %1 = alloca i32*

可以看出,第一种分配的是数组,得到的是指向数组的指针。第二种分配的是指针,这个指针是数组的首地址,返回的值是一个指向指针的指针。因此取元素地址时,使用第一种分配方式,直接对%1使用两个偏移的getelementptr,就得到了元素的地址;使用第二种分配方式,需要先使用load取出指针(数组首地址),然后使用一个偏移获取元素地址。因此在Var的visit函数中,处理如下:

auto var = scope.find(node.id);
/*
	处理非数组,数组下标,检查下标不为负....
*/
//如果var的值是指向数组的指针,需要两个偏移取地址
if(var->get_type()->get_pointer_element_type()->is_array_type())
    builder->create_gep(var, { CONST_INT(0), idx });
else if(var->get_type()->get_pointer_element_type()->is_pointer_type()){
    //从var中取出数组的起始地址
    var = builder->create_load(var);          //a[]的地址
    var = builder->create_gep(var, { idx });  //a[idx]的地址
}
else
    std::cout << "ERROR: 变量" << node.id << "不是一个数组\n";

处理好以上两个问题后,剩下的部分就比较简单了。最终完整的Var的visit函数如下:

void CminusfBuilder::visit(ASTVar &node) { 
    LOG(DEBUG) << "Var\n";
    //先在作用域找到变量
    auto var = scope.find(node.id);
    if(var==nullptr)
        std::cout << "ERROR: 未声明的变量" << node.id << "\n";
    //处理不是数组的情况
    if(node.expression==nullptr){
        if(ifAssign){                             //直接返回地址
            ret = var;
            ifAssign = false;
        }
        else{
            //数组的指针即a[]类型就返回数组的起始地址,否则load取值
            if(var->get_type()->get_pointer_element_type()->is_array_type())
                ret = builder->create_gep(var, {CONST_INT(0), CONST_INT(0)});
            else
                ret = builder->create_load(var);
        }
        return;
    }
    //处理数组的情况
    node.expression->accept(*this);
    Value *idx = ret;                             //下标
    if(idx->get_type()->is_float_type())          //如果为浮点类型,转换为整型
        idx = builder->create_fptosi(idx, INT32_Type);
    auto func = builder->get_insert_block()->get_parent();
    auto ifNegative = builder->create_icmp_lt(idx, CONST_INT(0));
    //创建exitBasicBlock和正常继续的BasicBlock
    auto exitBB = BasicBlock::create(module.get(), node.id + " exit", func);
    auto nextBB = BasicBlock::create(module.get(), node.id + " next", func);
    //条件跳转指令
    builder->create_cond_br(ifNegative, exitBB, nextBB);
    //向exitBB插入退出程序的指令
    builder->set_insert_point(exitBB);
    auto fail = scope.find("neg_idx_except");               
    builder->create_call(static_cast<Function*>(fail), {}); 
    builder->create_br(nextBB);
    //向nextBB插入正常执行的指令
    builder->set_insert_point(nextBB);
    //如果var的值是指向数组的指针,需要两个偏移取地址
    if(var->get_type()->get_pointer_element_type()->is_array_type())
        builder->create_gep(var, { CONST_INT(0), idx });
    else if(var->get_type()->get_pointer_element_type()->is_pointer_type()){
        //从var中取出数组的起始地址
        var = builder->create_load(var);          //a[]的地址
        var = builder->create_gep(var, { idx });  //a[idx]的地址
    }
    else
        std::cout << "ERROR: 变量" << node.id << "不是一个数组\n";
    //判断是赋值还是变量引用
    if(ifAssign){                             //直接返回地址
        ret = var;
        ifAssign = false;
    }
    else
        ret = builder->create_load(var);
}

4.表达式顺序错误

在完成了整个实验后,进行测试,发现无法通过最后一个testcase。打开12.minus查看代码,其中有函数调用和一些基本运算,一开始不确定错误在什么位置,因为此时编译已经没有问题了,只可能是语义处理的问题。经过一些尝试后,改写了lab4_test.py,将运行的结果与正确的结果输出,分别为-39和39,通过这个结果将错误定位到函数调用,然后检查生成的.ll代码,发现函数gcd中有一个减法,操作数的顺序错误。因此找到了加法表达式中创建指令时,传值传反了的错误。additive-expression的产生式为:additive-expression -→ additive-expression addop term | term。生成运算指令时,加法表达式的结果应该在左边,编写时没有注意到这个问题,所以导致了结果的错误。修改后,testcases全部通过。

......
if(node.op==OP_PLUS){
        if(ifInt)
            ret = builder->create_iadd(addRes, mulRes);     //addRes在左!
        else
            ret = builder->create_fadd(addRes, mulRes);  
    }
......

实验设计

1.全局变量与宏定义

补充了一个CONST_INT()宏定义获取常量值。INT32_Type和FLOAT_TYPE获取int32和float类型。

#define CONST_INT(num) \
    ConstantInt::get((int)num, module.get())
#define INT32_Type \
 Type::get_int32_type(module.get())      
#define FLOAT_Type \
 Type::get_float_type(module.get())

全局变量ret用于节点返回值。arg用于传递参数。ifAssign表示访问Var节点时,应该返回值还是变量地址。

Value* ret;
Value* arg;
bool ifAssign = false;

2.visit函数

Program

检查语义后,逐个访问declarations。

void CminusfBuilder::visit(ASTProgram &node) { 
    LOG(DEBUG) << "Program\n";
    //语义检查
    if(node.declarations.size()==0){
        cout << "ERROR: 该程序中没有声明。\n";
        return;
    }
    if(!(node.declarations.back()->id=="main"&& node.declarations.back()->type==TYPE_VOID)){
        cout << "ERROR: 最后一个声明不是void main(void)";
        return;
    }
    //访问declarations子节点
    for(auto dec: node.declarations)
        dec->accept(*this);
    return;
}

Num

数值节点没有子节点,直接进行处理,根据type确认数值类型,然后将值保存到全局变量value中。根据语义规则,只能有整型和浮点数两个类型。但此处的检查是没有必要的,因为其他数据类型会在词法分析时被识别为标识符,在语法分析时就会产生错误。而VOID类型在变量声明时进行检查,保证变量声明只有整型和浮点型。

void CminusfBuilder::visit(ASTNum &node) { 
    LOG(DEBUG) << "Num\n";
    if(node.type == TYPE_INT)
        ret = CONST_INT(node.i_val);
    else if(node.type == TYPE_FLOAT)
        ret = CONST_FP(node.f_val);
    return;  //没有子节点,直接返回
}

VarDeclaration

根据节点的定义,节点中包含一个类型和一个指针,还有继承自ASTDeclaration的id。对于变量声明节点的处理,需要产生分配空间的IR,在处理时还要区分数组与一般变量(根据节点的指针是否为空区分),局部变量与全局变量。并且要把声明的变量放入当前作用域中,保证后续使用可以找到。根据语义规则,全局变量需要初始化为0,数组变量声明时,大小应该大于0。

void CminusfBuilder::visit(ASTVarDeclaration &node) { 
    LOG(DEBUG) << "VarDeclaration\n";
    Type *varType;
    if(node.type==TYPE_INT)
        varType = INT32_Type;
    else if(node.type==TYPE_FLOAT)
        varType = FLOAT_Type;
    else 
        std::cout <<"ERROR: 在变量声明中,只有整型和浮点型可以使用\n";
    //对变量进行空间分配
    if(scope.in_global()){           //全局变量
        if(node.num==nullptr){    //非数组
            auto initializer = CONST_ZERO(varType);
            auto globalVar = GlobalVariable::create(node.id, module.get(), varType, false, initializer);
            scope.push(node.id, globalVar);
        }
        else{
            if(node.num->i_val>0){
                auto *array = ArrayType::get(varType, node.num->i_val);
                auto initializer = CONST_ZERO(array);
                auto globalVar = GlobalVariable::create(node.id, module.get(), array, false, initializer);
                scope.push(node.id, globalVar);
            }
            else
                std::cout << "ERROR: 数组长度必须大于0\n";
        }
    }
    else{                          //局部变量
        if(node.num==nullptr){     //非数组
             auto localVar = builder->create_alloca(varType);
             scope.push(node.id, localVar);
        }
        else{
            if(node.num->i_val>0){
                auto *array = ArrayType::get(varType, node.num->i_val);
                auto localVar = builder->create_alloca(array);
                scope.push(node.id, localVar);
            }
            else
                std::cout << "ERROR: 数组长度必须大于0\n";
        }
    }
}

FunDeclaration

FunDeclaration节点包含一个形参列表param和复合语句compound-stmt。需要创建的IR是创建函数和创建函数的第一个BasicBlock的指令,然后处理复合语句。在进入函数时要进入函数作用域,创建函数时要处理参数与返回值。对于每个参数,用全局变量取出实参,调用accept函数进行处理,在Param的visit函数中完成存储空间的分配,并加入到函数作用域当中。

void CminusfBuilder::visit(ASTFunDeclaration &node) { 
    LOG(DEBUG) << "FunDeclaration\n";
    Type *retType;                 //函数返回类型
    std::vector<Type *> paramType; //参数类型
    FunctionType *funType;         //函数类型
    //语义检查
    if(node.id=="main" && node.params.size()>0)
      cout << "ERROR: main函数必须为void main(void)形式\n";
    //确定函数的返回类型
    if(node.type==TYPE_INT)
        retType = INT32_Type;
    else if(node.type==TYPE_FLOAT)
        retType = FLOAT_Type;
    else if(node.type==TYPE_VOID)
        retType = Type::get_void_type(module.get());
    //确定函数的参数类型
    for(auto param : node.params){
        if(param->isarray){       //参数为数组,则参数为数组的首地址指针
            if(param->type==TYPE_INT)
                paramType.push_back(Type::get_int32_ptr_type(module.get()));
            else if(param->type==TYPE_FLOAT)
                paramType.push_back(Type::get_int32_ptr_type(module.get()));
        }
        else{                    //参数为整型或浮点型
            if (param->type==TYPE_INT)       
                paramType.push_back(INT32_Type);
            else if(param->type==TYPE_FLOAT)
                paramType.push_back(FLOAT_Type);     
        }
    }
    //创建函数
    funType = FunctionType::get(retType, paramType);
    auto func = Function::create(funType, node.id, module.get());
    scope.push(node.id, func);   //将函数加入全局作用域
    scope.enter();               //进入函数作用域
    auto entryBB = BasicBlock::create(module.get(), node.id + "entry", func);
    builder->set_insert_point(entryBB);     //插入指令
    //visit参数节点
    std::vector<Value*> args;    //存储参数
    for (auto arg = func->arg_begin();arg != func->arg_end();arg++) {
        args.push_back(*arg);    
    }
    for (int i = 0;i < node.params.size();i++) {
        auto param = node.params[i];
        arg = args[i];           
        param->accept(*this);           
    }
    //处理函数体内的语句
    node.compound_stmt->accept(*this);
    //如果BasicBlock最后没有终止指令,创建返回指令
    if (builder->get_insert_block()->get_terminator() == nullptr) {
        if (func->get_return_type()->is_void_type())
            builder->create_void_ret();
        else if (func->get_return_type()->is_float_type())
            builder->create_ret(CONST_FP(0.0));
        else
            builder->create_ret(CONST_INT(0));
    }
    scope.exit();
}

Param

在处理参数时,要为参数分配空间,使参数能够保留在函数的作用域内。在lab3中自行编写.ll文件时直接使用参数,不进行存储,直接使用就可以实现相同的逻辑。但在将cminus转换为IR时,cminus的语义规定了每次函数调用都会产生一组独立内存的参数,因此为参数分配空间,并存入作用域。

void CminusfBuilder::visit(ASTParam &node) {     
    LOG(DEBUG) << "Param\n"; 
    Value* paramP; 
    if (node.isarray) {          //数组
        if (node.type==TYPE_INT)        
            paramP = builder->create_alloca(Type::get_int32_ptr_type(module.get()));
        else if (node.type==TYPE_FLOAT) 
            paramP = builder->create_alloca(Type::get_float_ptr_type(module.get()));
    }
    else {                       //非数组
        if (node.type==TYPE_INT)       
            paramP = builder->create_alloca(INT32_Type);
        else if (node.type==TYPE_FLOAT)
            paramP = builder->create_alloca(FLOAT_Type);
    }
    builder->create_store(arg, paramP);         //存储参数
    scope.push(node.id, paramP);                //加入作用域
}

CompoundStmt

每个函数内部都有一个复合语句,根据ASTCompoundStmt的定义,复合语句由局部声明和一系列语句构成。只需要逐个调用相应的accept函数,不需要产生IR。

void CminusfBuilder::visit(ASTCompoundStmt &node) { 
    LOG(DEBUG) << "CompoundStmt\n";
    //访问局部声明的节点和语句节点
    for(auto local_declaration: node.local_declarations)
        local_declaration->accept(*this);
    for(auto statement: node.statement_list)
        statement->accept(*this);
}

ExpressionStmt

ExpressionStmt对应一条表达式或空,只要表达式存在,就处理该表达式。

void CminusfBuilder::visit(ASTExpressionStmt &node) { 
    LOG(DEBUG) << "ExpressionStmt\n";
	if (node.expression != nullptr)
		node.expression->accept(*this);
}

SelectionStmt

SelectionStmt包含一个条件表达式,一个if语句块,还有可能存在的else语句块。先处理表达式,产生条件跳转语句。如果指向else语句块的指针为空,就说明只有if语句。考虑只有if的情况,在执行到if时,应该通过br指令条件跳转到if语句块或if后的部分。如果还有else语句,则通过br指令条件跳转到if语句块或else语句块,然后从这两个语句块的结尾返回或者跳转到ifelse语句之后的部分。因此在SelectionStmt的visit函数中应该至少生成三个BasicBlock,并生成br指令。根据else指针是否为空判断是否需要生成条件判断为false的BasicBlock。

void CminusfBuilder::visit(ASTSelectionStmt &node) { 
    LOG(DEBUG) << "SelectionStmt\n";
    //处理条件表达式
    auto func = builder->get_insert_block()->get_parent();
    node.expression->accept(*this);
    auto retType = ret->get_type();
    Value* ifExpression;                         //条件表达式的结果             
    //根据返回类型处理, 生成比较语句
    if(retType->is_integer_type())
        ifExpression = builder->create_icmp_gt(ret, CONST_ZERO(INT32_Type));
    else
        ifExpression = builder->create_fcmp_gt(ret, CONST_ZERO(FLOAT_Type));
    //生成BasicBlock
    auto ifBB = BasicBlock::create(module.get(), "ifBasicBlock", func);
    auto elseBB = BasicBlock::create(module.get(), "elseBasicBlock", func);
    auto afterIfBB = BasicBlock::create(module.get(), "afterIfBasicBlock", func);
    //创建br指令
    if(node.else_statement==nullptr)             //没有else
        builder->create_cond_br(ifExpression, ifBB, afterIfBB);
    else
        builder->create_cond_br(ifExpression, ifBB, elseBB);
    //为ifBB添加IR
    builder->set_insert_point(ifBB);
    node.if_statement->accept(*this);
	if (builder->get_insert_block()->get_terminator() == nullptr)
		builder->create_br(afterIfBB);
    //为elseBB添加IR
    if(node.else_statement!=nullptr){
        builder->set_insert_point(elseBB);
        node.else_statement->accept(*this);
	    if (builder->get_insert_block()->get_terminator() == nullptr)
		builder->create_br(afterIfBB);        
    }
    else
        elseBB->erase_from_parent();
    //后面的语句块的IR在新的BasicBlock中
    builder->set_insert_point(afterIfBB);
}

IterationStmt

与if语句类似,while迭代语句也有一个条件表达式,进行条件跳转。可以创建一个用于判断的ifBasicBlock,一个循环的loopBasicBlock,一个while语句后的afterWhileBasicBlock,添加相应的指令。当条件表达式为True时,进行ifBB->loopBB->ifBB的循环跳转。

void CminusfBuilder::visit(ASTIterationStmt &node) {
    LOG(DEBUG) << "IterationStmt\n";
    //创建BasicBlock
    auto func = builder->get_insert_block()->get_parent();
    auto ifBB = BasicBlock::create(module.get(), "ifBasicBlock", func);
    auto loopBB = BasicBlock::create(module.get(), "loopBasicBlock", func);
    auto afterWhileBB = BasicBlock::create(module.get(), "afterWhileBasicBlock", func);
    //跳转到条件表达式判断的BasicBlock
    builder->create_br(ifBB);
    //为ifBB添加IR, 判断条件表达式的结果
    builder->set_insert_point(ifBB);
    node.expression->accept(*this);
    auto retType = ret->get_type();
    Value* ifExpression;                         //条件表达式的结果             
    //根据返回类型处理, 生成比较语句
    if(retType->is_integer_type())
        ifExpression = builder->create_icmp_gt(ret, CONST_ZERO(INT32_Type));
    else
        ifExpression = builder->create_fcmp_gt(ret, CONST_ZERO(FLOAT_Type));
    //条件跳转
    builder->create_cond_br(ifExpression, loopBB, afterWhileBB);
    //为loopBB添加IR
    builder->set_insert_point(loopBB);
    node.statement->accept(*this);
    if (builder->get_insert_block()->get_terminator() == nullptr)
        builder->create_br(ifBB);
    //后面的语句块的IR在新的BasicBlock中
    builder->set_insert_point(afterWhileBB);
}

ReturnStmt

返回语句中有一个表达式计算返回值,如果指向该返回语句的指针为空,说明没有返回值,创建一个void返回IR,否则需要调用该表达式的accept函数,并检查返回类型是否和函数的返回类型相同。

void CminusfBuilder::visit(ASTReturnStmt &node) { 
	LOG(DEBUG) << "ReturnStmt\n";
    if(node.expression==nullptr){
        builder->create_void_ret();
        return;
    }
    node.expression->accept(*this);
    //处理返回值,产生返回的IR
    auto func = builder->get_insert_block()->get_parent();
    auto retType = func->get_return_type();
    auto resType = ret->get_type();
    if (retType->is_integer_type() && resType->is_float_type())
        ret = builder->create_fptosi(ret, INT32_Type);
    else if(retType->is_float_type() && resType->is_integer_type())
        ret = builder->create_sitofp(ret, FLOAT_Type);
    builder->create_ret(ret);
}

Var

在实验难点中已说明。

AssignExpression

对于Assign语句,将全局变量ifAssign置为true,调用子节点var的accept函数得到变量的地址,然后计算表达式的值,创建store指令将值存入地址。需要确认表达式结果是否与变量类型相同,如果不同需要将表达式结果转换为和变量相同的类型。

void CminusfBuilder::visit(ASTAssignExpression &node) { 
    LOG(DEBUG) << "AssignExpression\n";
    ifAssign = true;
    node.var->accept(*this);
    auto varAdd = ret;
    node.expression->accept(*this);
    //确认表达式结果类型与变量类型相同,不同则进行转换
    auto varType = varAdd->get_type()->get_pointer_element_type();
    auto valueType = ret->get_type();
    Value* value = ret;
    if(varType!=valueType){
        if(varType==INT32_Type)
            value = builder->create_fptosi(ret, INT32_Type);
        else
            value = builder->create_sitofp(ret, FLOAT_Type);
    }
    builder->create_store(value, varAdd);
}

SimpleExpression

简单表达式SimpleExpression是一个加法表达式或两个加法表达式的关系运算。在节点中有两个加法表达式的指针和一个运算符类型为RelOp的运算符op,RelOp是一个枚举类型,包含了所有比较运算符。根据语义,对于该节点的处理,应该先处理加法表达式,将表达式的值保存下来,如果两个表达式指针都不为空,说明为关系运算,再比较两个运算结果,根据结果将表达式的值赋为0或1。进行比较时需要注意两个值的类型,整型和浮点型比较时要将整型转换为浮点型。

具体实现中,应该调用加法表达式的accept函数(如果指针不为空),暂存结果,对于比较运算,根据op生成icmp或fcmp的指令,最后返回的值就是指令结果。

void CminusfBuilder::visit(ASTSimpleExpression &node) { 
    LOG(DEBUG) << "SimpleExpression\n";
    bool ifInt = true;                      //表达式结果类型
    Value *resL, *resR;
    Type *resLType, *resRType;
    //计算两个加法表达式
    if(node.additive_expression_l!=nullptr){
        node.additive_expression_l->accept(*this);
        resL = ret;
        resLType = ret->get_type();
    }
    if(node.additive_expression_r!=nullptr){
        node.additive_expression_r->accept(*this);
        resR = ret;
        resRType = ret->get_type();
    }
    //如果只有一个加法表达式, 直接返回表达式的值
    if(!(node.additive_expression_l!=nullptr && node.additive_expression_r!=nullptr)){
        ret = (node.additive_expression_l!=nullptr) ? resL : resR;
        return;
    }
    //如果有两个加法表达式, 为关系运算
    //先统一类型
    if(resLType!=resRType){
        ifInt = false;
        if(resLType->is_float_type())        //左表达式为浮点型,则右表达式从整型转换为浮点型
            resL = builder->create_sitofp(resL, FLOAT_Type);
        else								 //左表达式为整型,转换为浮点型
            resR = builder->create_sitofp(resR, FLOAT_Type);
    }
    //根据关系运算类型,创建指令
    switch (node.op)
    {
    case OP_LE:
        if(ifInt)
            ret = builder->create_icmp_le(resL, resR);
        else
            ret = builder->create_fcmp_le(resL, resR);
        break;
	/*其他运算符......*/
    default:
        break;
    }
    //结果为整型
    ret = builder->create_zext(ret, INT32_Type);
}

AdditiveExpression

加法表达式中包含了一个乘法表达式,一个加法表达式和一个运算符。如果加法表达式指针为空,则表达式的值就是乘法表达式的值,否则分别计算两个表达式,调用相应的accept函数,然后进行根据运算符生成加或减指令。

void CminusfBuilder::visit(ASTAdditiveExpression &node) { 
    LOG(DEBUG) << "AdditiveExpression\n";
    bool ifInt = true;                        //两个表达式类型
    //如果只有乘法表达式
    if(node.additive_expression==nullptr){
        node.term->accept(*this);
        return;
    }
    //有乘法表达式和加法表达式,创建加法或减法指令
    node.additive_expression->accept(*this);  //处理加法表达式
    auto addRes = ret;
    auto addType = addRes->get_type();
    node.term->accept(*this);                 //处理乘法表达式
    auto mulRes = ret;
    auto mulType = mulRes->get_type();
    //确认类型相同
    if(addType!=mulType){
        ifInt = false;
        if(addType->is_float_type())
            mulRes = builder->create_sitofp(mulRes, FLOAT_Type);
        else
            addRes = builder->create_sitofp(addRes, INT32_Type);
    }
    //指令
    if(node.op==OP_PLUS){
        if(ifInt)
            ret = builder->create_iadd(addRes, mulRes);     //addRes在左!
        else
            ret = builder->create_fadd(addRes, mulRes);  
    }
    else{
        if(ifInt)
            ret = builder->create_isub(addRes, mulRes);
        else
            ret = builder->create_fsub(addRes, mulRes);        
    }
}

Term

乘法表达式由乘法表达式和因子或单独一个因子构成。与加法表达式的处理相同。

void CminusfBuilder::visit(ASTTerm &node) { 
    LOG(DEBUG) << "Term\n";
    bool ifInt = true;                        //因子和乘法表达式结果类型
    //如果只有因子
    if(node.term==nullptr){
        node.factor->accept(*this);
        return;
    }
    //有乘法表达式和因子,创建乘法或除法指令
    node.term->accept(*this);                 //处理乘法表达式
    auto mulRes = ret;
    auto mulType = mulRes->get_type();
    node.factor->accept(*this);               //处理因子
    auto factorRes = ret;
    auto factorType = factorRes->get_type();
    //确认类型相同
    if(factorType!=mulType){
        ifInt = false;
        if(factorType->is_float_type())
            mulRes = builder->create_sitofp(mulRes, FLOAT_Type);
        else
            factorRes = builder->create_sitofp(factorRes, INT32_Type);
    }
    //指令
    if(node.op==OP_MUL){
        if(ifInt)
            ret = builder->create_imul(mulRes, factorRes);
        else
            ret = builder->create_fmul(mulRes, factorRes);
    }
    else{
        if(ifInt)
            ret = builder->create_isdiv(mulRes, factorRes);
        else
            ret = builder->create_fdiv(mulRes, factorRes);        
    }
}

Call

call节点需要创建一条函数调用call指令,从作用域中取出函数,然后根据函数的参数将节点的实参传入,并检查类型是否与函数参数的类型一致,不一致则需要转换为函数的形参类型。创建一个参数列表,将转换好的参数存入列表来调用函数。

void CminusfBuilder::visit(ASTCall &node) { 
    LOG(DEBUG) << "Call\n";
    auto func = static_cast<Function*>(scope.find(node.id));
    auto paramType = func->get_function_type()->param_begin();
    if(func==nullptr)
        std::cout << "ERROR: 函数" << node.id << "未定义\n";
    //处理参数
    Value *temp;
    std::vector<Value*> args;
    for(auto arg: node.args){
        arg->accept(*this);                   //获取参数值
        temp = ret;
        auto argType = ret->get_type();       //处理类型
        if(argType!=*paramType)
            if((*paramType)->is_integer_type())
                temp = builder->create_fptosi(ret, INT32_Type);
            else
                temp = builder->create_sitofp(ret, FLOAT_Type);
        args.push_back(temp);
        paramType++;
    }
    builder->create_call(func, args);
}

实验总结

1.结果验证

在进行测试,改正编写过程中的一些错误后,编译后运行测试程序,所有测试样例都通过。

在这里插入图片描述

2.总结

通过本次实验,理解了从抽象语法树产生中间IR的方法,并进行了实现。在实现过程中,对于IR的指令有了进一步的熟悉与理解,掌握了使用C++接口创建不同IR指令的方法,以及在访问者模式下遍历抽象语法树,完成IR生成的过程。在完成实现时阅读了相关的头文件,语义规则,通过整个实验的框架复习了C++中一些概念和方法。经过四次实验,结合课程所学的原理,理解了编译器的词法分析,语法分析,中间代码生成的过程,也学习了相关工具的使用并进行了实践,清楚了编译器工作的每一个部分的原理和相互之间的配合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/91411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

easyExcel导出表头合并 不得不说真牛

有个导出单元格合并的任务&#xff0c;表头不规则合并格式&#xff0c;看得就烦&#xff0c;尤其是对于没玩儿过合并的我来说&#xff0c;任务放在哪里不知咋做&#xff0c;网上也看了一堆合并的方法&#xff0c;自己写注解来写的那些&#xff0c;麻烦得要命&#xff0c;我写一…

48.python break语句-终止循环

48.break语句-终止循环 文章目录48.break语句-终止循环1.循环控制2.break的作用3. 语法4. 实操练习5. 知识扩展&#xff1a;print的位置6. break语句循环图1.循环控制 在循环的过程中如果要退出循环&#xff0c;我们可以用break语句和continue语句。 2.break的作用 break [b…

Android入门第49天-使用RadioGroup+Fragment来重构类首页底部4个按钮的界面

简介 我们在&#xff1a;Android入门第47天-Fragment的基本使用 中使用Fragment制作了一个类首页底部含4个按钮的界面。今天的课程我们要做的是把第47天里的代码中一部分共用的东西抽象到res/values/themes.xml文件中。另外我们使用RadioGroup天然的只有一个可以被选中来代替…

Python学习笔记-文件及文件夹操作

记述python中关于文件和目录的基本操作。 一、基本文件操作 1.文件的创建和打开 file open(filename[,mode[,buffering]]) file&#xff1a;文件对象filename&#xff1a;文件路径&#xff0c;字符串类型&#xff0c;若要打开的文件与程序文件在同一文件夹&#xff0c;可直…

OPengl学习(三)——绘制几何物体和状态管理

文章目录0、 写在前面1、绘图工具1.1、清除窗口1.2、指定颜色1.3、强制完成绘图1.4、坐标系统工具2、点&#xff0c;直线&#xff0c;多边形2.1、填充多边形3、点&#xff0c;直线&#xff0c;多边形细节刻画4、基本状态管理0、 写在前面 1、OpenGL自身是一个巨大的状态机(Sta…

【CELL】compass利用单细胞转录组+流平衡分析预测细胞代谢状态,促进免疫细胞代谢研究

细胞代谢调控正常细胞功能以及多种疾病状态的病理生理。最近&#xff0c;免疫细胞代谢研究&#xff08;immunometabolism&#xff09;成为一个研究热点&#xff0c;揭示了包括抗病毒免疫、自身免疫和抗肿瘤反应在内的炎症代谢调节。然而&#xff0c;由于代谢网络的规模和复杂性…

BIO、NIO、AIO理解(I/O模型)

IO模型(unix网络编程第一卷) unix有五种I/O模型&#xff0c;好像其他系统也差不多吧。 I/O模型主要是两个阶段&#xff1a;等待数据与把数据从内核空间复制到用户空间&#xff0c;然后根据这两个阶段的不同&#xff0c;分类出来下面几类I/O模型。 前四个是同步IO,最后一个是异…

高并发下你还敢用ArrayList?过来看看CopyOnWriteArrayList吧!

一、ArrayList线程不安全 在Java的集合框架中&#xff0c;想必大家对ArrayList肯定不陌生&#xff0c;单线程的情况下使用它去做一些CRUD的操作是非常方便的&#xff0c;先来看看这个例子&#xff1a; public class ListTest {public static void main(String[] args) {List&…

GIS基于智慧城市建设的作用

​​​​​智慧城市的建设对于改善居民的生活质量和提高城市的管理水平&#xff0c;有着公认的推动作用。其中&#xff0c;地理信息技术特别是GIS平台&#xff0c;在智慧城市的建设过程中扮演着关键角色。 在现实情况中&#xff0c;除了政策本身的一些因素&#xff0c;受限于一…

Web前端105天-day44-JSCORE

JSCORE04 目录 前言 一、复习 二、forEach 三、reduce 四、展开语法 五、解构语法 六、形参默认值 七、剩余参数 总结 前言 JSCORE04学习开始 一、复习 JS的第6个版本, 带来了大量的新特性, 新语法let/const : 两个新的声明变量的方式 新的作用域 脚本: 对应全局, 用…

RocketMQ中的线程池是如何创建的?

前言 大家好&#xff0c;今天主要来和大家聊一聊RocketMQ中的线程池是如何创建的&#xff0c;如何设置线程池数量&#xff0c;同时也可以从中去学习到一些线程池的实践和需要注意的一些细节。 RocketMQ在哪些地方使用到了线程池&#xff1f; 在RocketMQ中存在了大量的对线程…

学籍信息网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 学籍信息管理&#xff1a;添加信息、修改信息、删除信息、查询信息 添加信息&#xff0c;管理员根据学生的将信息导入系…

[附源码]Python计算机毕业设计高校师资管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

行业分析| 智慧头盔在快对讲上的应用与实践

快对讲综合调度系统是基于移动通信网络&#xff0c; 整合集群对讲、视频监控、实时音视频技术、PSTN、GIS定位、IM和调度业务的产品&#xff0c;为客户提供专业对讲、视频会议、可视化融合指挥调度等功能为一体的音视频实时交互平台。 快对讲和智慧头盔 智慧头盔&#xff0c;…

PHP实验室管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP实验室管理系统 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为PHP APACHE&#xff0c;数据库为 mysql5.0&#xff0c;使用php语言开发。 PHP…

第10讲:vue脚手架集成axios

一、创建项目并添加axios支持 创建项目请参考&#xff1a;使用脚手架创建vue项目 创建路由项目请参考&#xff1a;路由开发 1.1、添加axios支持 使用如下命令添加axios支持 npm install axios //vue-cli2.0安装方式1.2、在main.js中引用并使用axios 使用如下命令 impor…

git初识(三)

分支 顾名思义&#xff0c;分支就是从主线上分离出来进行另外的操作&#xff0c;而又不影响主线&#xff0c;主线又可以继续干它的事&#xff0c;&#xff0c;最后分支做完事后合并到主线上而分支的任务完成可以删掉了。为了不受其他开发人员的影响&#xff0c;你可以在主分支…

数据看板可视化

前言 这段时间一直在做可视化&#xff0c;在我的项目中有一部分是电力巡检的数据可视化。其中的数据看板比较简单&#xff0c;我将其单独抽离出来形成一个demo&#xff0c;为保密demo中数据非真实数据。先看效果。 具体效果 链接相关 浏览链接&#xff1a;http://xisite.top…

【人工智能与机器学习】——聚类(学习笔记)

&#x1f4d6; 前言&#xff1a;我们之前学习的算法均是有监督学习&#xff08;supervised learning&#xff09;&#xff0c;它的一个鲜明特征是通过给定的标签来学习从数据特征&#xff08;如图像&#xff09;到语义标签的映射关系。但在很多实际问题中&#xff0c;数据并没有…

vuex笔记

Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 调试工具&#xff1a;vue devtools Vuex就像眼镜&#xff1a;您自会知道什么时候需要它。 1、state 在store中定义数据&#xff0c;在组件中直接使用&#xff1a; 目录&#xff1a;store/index.js export defau…