编译原理实验四

编译原理实验四

实验要求

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++中一些概念和方法。经过四次实验,结合课程所学的原理,理解了编译器的词法分析,语法分析,中间代码生成的过程,也学习了相关工具的使用并进行了实践,清楚了编译器工作的每一个部分的原理和相互之间的配合。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理中,C语言转元组是指将C语言代码转换为一种简单的中间表示形式,以便于后续的代码优化和生成。编译原理实验中,正规式到正规文法的转换是将一个正规式转换为一种等价的正规文法,以便于后续的语法分析。 下面分别介绍这两个过程的具体实现。 1. C语言转元组 C语言转元组通常分为以下几个步骤: 1)词法分析:将源代码分解成一系列的词法单元,如关键字、标识符、常量等。 2)语法分析:将词法单元按照一定的语法规则组合成为一颗语法树,同时进行语义分析,如类型检查等。 3)中间代码生成:将语法树转换为一种中间表示形式,如三地址码、元组等。 4)代码优化:对中间代码进行一系列的优化,如常量折叠、公共子表达式消除等。 5)目标代码生成:将优化后的中间代码转换为目标机器代码。 在具体实现时,可以采用词法分析器和语法分析器生成器来自动生成词法分析和语法分析部分的代码,从而提高编译器的开发效率。而中间代码生成和代码优化部分通常需要手动实现。 2. 正规式到正规文法的转换 正规式到正规文法的转换通常分为以下几个步骤: 1)将正规式转换为NFA(非确定性有限状态自动机)。 2)将NFA转换为DFA(确定性有限状态自动机)。 3)将DFA转换为正规文法。 在具体实现时,可以采用现成的正规式转换工具,如Lex和Yacc,来生成NFA和DFA的转换代码。而将DFA转换为正规文法则需要手动实现。一般而言,可以将DFA的状态作为非终结符号,将DFA中的边转换为产生式,从而得到等价的正规文法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值