diff --git a/Reports/lab4/report.md b/Reports/lab4/report.md index 5557c6cff2ce440cb3d976ae46698f558d0e9001..58e913d97266183c8af7575de8a403626c429612 100644 --- a/Reports/lab4/report.md +++ b/Reports/lab4/report.md @@ -2,33 +2,1259 @@ 姓名 学号 +# Lab4 实验报告 + +**王辉 202208010126** + ## 实验要求 -请按照自己的理解,写明本次实验需要干什么 +在本实验中,只要完成抽象语法树到中间表示IR的生成这一步。 + +具体来讲,在理解cminus-f 语法与语义的基础上,参考`cminusf_builder.hpp`文件以及include中的文件,补充完成 `cminusf_builder.cpp` 中的16个visit函数,来实现自动 IR 产生的算法,使得它能正确编译任何合法的 cminus-f 程序。 + +在自动产生 IR 的过程中,利用访问者模式自顶向下遍历抽象语法树的每一个结点,调用我们补充完成的函数对每一个抽象语法树的结点进行分析,如果程序是合法的则编译应该显示Success,否则编译不通过显示Fail。 + +1. 阅读cminus-f 的语义规则成为语言律师,我们将按照语义实现程度进行评分 +2. 阅读LightIR 核心类介绍 +3. 阅读实验框架,理解如何使用框架以及注意事项 +4. 修改 `src/cminusfc/cminusf_builder.cpp` 来实现自动 IR 产生的算法,使得它能正确编译任何合法的 cminus-f 程序 +5. 在` report.md `中解释你们的设计,遇到的困难和解决方案 + +待完成的函数: +``` +void CminusfBuilder::visit(ASTProgram &node) { } +void CminusfBuilder::visit(ASTNum &node) { } +void CminusfBuilder::visit(ASTVarDeclaration &node) { } +void CminusfBuilder::visit(ASTFunDeclaration &node) { } +void CminusfBuilder::visit(ASTParam &node) { } +void CminusfBuilder::visit(ASTCompoundStmt &node) { } +void CminusfBuilder::visit(ASTExpressionStmt &node) { } +void CminusfBuilder::visit(ASTSelectionStmt &node) { } +void CminusfBuilder::visit(ASTIterationStmt &node) { } +void CminusfBuilder::visit(ASTReturnStmt &node) { } +void CminusfBuilder::visit(ASTVar &node) { } +void CminusfBuilder::visit(ASTAssignExpression &node) { } +void CminusfBuilder::visit(ASTSimpleExpression &node) { } +void CminusfBuilder::visit(ASTAdditiveExpression &node) { } +void CminusfBuilder::visit(ASTTerm &node) { } +void CminusfBuilder::visit(ASTCall &node) { } +``` ## 实验难点 -实验中遇到哪些挑战 +### 1. 全局变量的设置 +调用的函数在`cminusf_builder.hpp`中被定义为了void类型的函数,故返回的结果不能通过函数本身实现,只能通过全局变量来进行传递。 + +此外用于Param的处理的参数指针也需要全局进行传递。 + +设置有效的全局变量也是实验的要点。 + +### 2. 数组类型的判断与特殊处理 +在`void CminusfBuilder::visit(ASTVar &node)`,`void CminusfBuilder::visit(ASTParam &node)`等函数中,对于数组类型都做了特殊的判断和处理,而且判断的方式有所不同。 + +如在前者中,使用`node.num != nullptr`进行判断,在后者中使用`node.expression != nullptr`进行判断。而相应的处理也是不同的。 + +### 3. 作用域 +一个变量,在放入表的时候,要考虑它的作用域,这显然是符合我们常识的。 + +如在实现`void CminusfBuilder::visit(ASTFunDeclaration &node)`函数中创建函数之后,要进入函数的作用域再创建基本块,并在处理完函数体内的语句之后退出基本块。 + +同样的还有在实现`void CminusfBuilder::visit(ASTCompoundStmt &node)`函数中处理复杂语句时,要先进入本基本块的作用域,并在结束之后退出这个作用域。这也是符合我们常识的,考虑下面这个场景,处理一个复杂(比如像if语句或者while语句后面的{}内部的语句块)语句块时,有可能我们会定义一些临时变量,那么显然这个临时变量的生命周期只在这个语句块内,如果不引入作用域,可能会导致溢出的后果。 + +对于作用域的使用也是实验的一个难点。 + +### 4. 阅读资料,理解含义 +完成本实验需要阅读的资料实在是太多了。 + +需要吃透的文件至少有 + ++ cminusf_builder.cpp ++ cminusf_builder.hpp ++ ast.cpp ++ ast.hpp + +对于一个变量,它的原型或者说定义,往往要跳转好几次才能找到,这个理解代价还是很大的。 + ## 实验设计 -请写明为了顺利完成本次实验,加入了哪些亮点设计,并对这些设计进行解释。 -可能的阐述方向有: +### 1. 作用域类:Scope + +在实验文档中提到: + +在`include/cminusf_builder.hpp`中,助教还定义了一个用于存储作用域的类`Scope`。它的作用是辅助我们在遍历语法树时,管理不同作用域中的变量。它提供了以下接口: +``` +// 进入一个新的作用域 +void enter(); +// 退出一个作用域 +void exit(); +// 往当前作用域插入新的名字->值映射 +bool push(std::string name, Value *val); +// 根据名字,寻找到值 +Value* find(std::string name); +// 判断当前是否在全局作用域内 +bool in_global(); +``` +在文件`include/cminusf_builder.hpp`中查看: +``` +class Scope { +public: + // enter a new scope + void enter() { + inner.push_back({}); + } + + // exit a scope + void exit() { + inner.pop_back(); + } + + bool in_global() { + return inner.size() == 1; + } + + // push a name to scope + // return true if successful + // return false if this name already exits + bool push(std::string name, Value *val) { + auto result = inner[inner.size() - 1].insert({name, val}); + return result.second; + } + + Value* find(std::string name) { + for (auto s = inner.rbegin(); s!= inner.rend();s++) { + auto iter = s->find(name); + if (iter != s->end()) { + return iter->second; + } + } + + return nullptr; + } + +private: + std::vector> inner; +}; +``` + +解释上面的代码: + +#### 成员变量 + +- `std::vector> inner;` + - 这是一个向量(动态数组),其中每个元素都是一个从字符串到 `Value*`(指向 `Value` 类型的指针)的映射(`map`)。这个结构用于表示作用域层次。每个 `map` 代表一个作用域,其中键是变量的名称,值是变量的值(通过指针指向)。 + - `inner` 的第一个元素(索引为 0)通常代表全局作用域,因为根据代码逻辑,进入新作用域时会向 `inner` 添加新元素,而退出作用域时会移除最后一个元素。 + +#### 成员函数 + +- `void enter()` + - 进入一个新作用域。通过向 `inner` 向量末尾添加一个空的 `map` 来实现。 + +- `void exit()` + - 退出当前作用域。通过移除 `inner` 向量末尾的 `map` 来实现。 + +- `bool in_global()` + - 检查当前是否处于全局作用域。如果 `inner` 的大小为 1(即只有一个作用域,通常是全局作用域),则返回 `true`;否则返回 `false`。 + +- `bool push(std::string name, Value *val)` + - 在当前作用域中添加一个变量。尝试将给定的变量名(`name`)和变量值(`val`)插入到 `inner` 最后一个元素(当前作用域)的 `map` 中。 + - 如果插入成功(即变量名在当前作用域中尚不存在),则返回 `true`;如果变量名已存在,则插入失败并返回 `false`。 + +- `Value* find(std::string name)` + - 查找给定名称的变量。从 `inner` 的最后一个元素(当前作用域)开始,向前遍历每个作用域(即使用反向迭代器),查找具有给定名称的变量。 + - 如果找到该变量,则返回指向其值的指针;如果在所有作用域中都未找到,则返回 `nullptr`。 + +实验需要根据语义合理调用`enter`与`exit`,并且在变量声明和使用时正确调用`push`与`find`。在类`CminusfBuilder`中,有一个`Scope`类型的成员变量`scope`,它在初始化时已经将`input`、`output`等函数加入了作用域中。因此,你们在进行名字查找时不需要顾虑是否需要对特殊函数进行特殊操作。 + +### 2.全局变量与宏定义 +调用的函数在`cminusf_builder.hpp`中被定义为了`void`类型的函数,故返回的结果不能通过函数本身实现,只能通过全局变量来进行传递。 + +1. 增加一个有关整型的宏 +2. 补充了获取类型的宏,分别获取`int32`型的类型和`float`的类型。 +3. 补充了整型,浮点型,指针类型判断的宏,能够判断该指针的类型是否是指定的 +``` +#define CONST_INT(num) \ + ConstantInt::get(num, module.get()) /* 增加一个有关整型的宏 */ + +#define Int32Type \ + Type::get_int32_type(module.get()) /* 获取int32类型 */ +#define FloatType \ + Type::get_float_type(module.get()) /* 获取float类型 */ + +#define checkInt(num) \ + num->get_type()->is_integer_type() /* 整型判断 */ +#define checkFloat(num) \ + num->get_type()->is_float_type() /* 浮点型判断 */ +#define checkPointer(num) \ + num->get_type()->is_pointer_type() /* 指针类型判断 */ +``` + +全局变量ret用于节点返回值。arg用于传递参数。need_as_address表示访问Var节点时,应该返回值还是变量地址。 +``` +Value *Res; /* 存储返回的结果 */ +Value *arg; /* 存储参数指针,用于Param的处理 */ +bool need_as_address = false; /* 标志是返回值还是返回地址 */ +``` + +### 3.具体实现Visit函数 +#### (1) void CminusfBuilder::visit(ASTProgram &node) +对应的语法为: + +==$program {\longrightarrow} declaration-list$== + + +在ast.hpp中查看ASTProgram的结构定义: +``` +struct ASTProgram : ASTNode { + virtual void accept(ASTVisitor &) override final; + std::vector> + declarations; +}; +``` + + +`ASTProgram`是一个结构体,它继承自`ASTNode`。它包含: + +- 一个重写的`accept`方法,它接受一个`ASTVisitor`类型的引用作为参数,并调用访问者的`visit`方法来处理`ASTProgram`节点。 +- 一个`declarations`成员,它是一个指针向量(`std::vector>`),用于存储程序中所有声明的列表。 + +其主要功能是遍历program下层的declaration-list中的声明。故可以用一个for循环遍历declaration-list,然后处理每一个declaration。具体代码如下: +``` +/* Program, 程序, program->declaration-list */ +void CminusfBuilder::visit(ASTProgram &node) +{ + // ASTProgram有一个std::vector>类型的向量 + // 我们要对这个向量进行遍历 + + if (node.declarations.size() == 0) + { + std::cout << "ERROR: 该程序中没有声明。\n"; + return; + } + if (!(node.declarations.back()->id == "main" && node.declarations.back()->type == TYPE_VOID)) + { + std::cout << "ERROR: 最后一个声明不是void main(void)\n"; + return; + } + for (auto decl : node.declarations) /* 遍历declaration-list子结点 */ + decl->accept(*this); /* 处理每一个declaration */ + return; +} +``` +#### (2) void CminusfBuilder::visit(ASTNum &node) +在ast.hpp中查看ASTNum的结构定义: +``` +struct ASTNum: ASTFactor { + virtual void accept(ASTVisitor &) override final; + CminusType type; + union { + int i_val; + float f_val; + }; +}; +``` + +`union`是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型(但只能同时存储其中一个)。这对于节省内存空间非常有用,特别是当你知道同一时间只会有一种数据类型被使用时。 + +数值节点没有子节点,直接进行处理,根据type确认数值类型,然后将值保存到全局变量中。根据语义规则,只能有整型和浮点数两个类型。分别赋值即可。 + +代码如下: +``` +/* Num,对数值进行处理 */ +void CminusfBuilder::visit(ASTNum &node) +{ + // ASTNum中有两个成员变量 + // CminusType type; 类型决定这片空间的读取方式 + // 联合体union{ int i_val; float f_val;}是这片空间保存的值 + + if (node.type == TYPE_INT) /* 若为整型 */ + // 调用ConstantInt中的API + Res = ConstantInt::get(node.i_val, module.get()); /* 获取结点中存储的整型数值 */ + else if (node.type == TYPE_FLOAT) /* 若为浮点型 */ + // 调用ConstantFP中的API + Res = ConstantFP::get(node.f_val, module.get()); /* 获取结点中存储的浮点型数值 */ + return; +} +``` + +#### (3) void CminusfBuilder::visit(ASTVarDeclaration &node) + +**变量声明** + +对应的语法为: + +==$var-declaration {\longrightarrow} type-specifier \quad ID ; | type-specifier \quad ID \quad [ INTEGER ] ;$== + +==$type-specifier {\longrightarrow} int | float | void$== + +此语法规则有如下说明: +``` +全局变量需要初始化为全 0 +cminus-f 的基础类型只有整型(int)、浮点型(float)和 void。而在变量声明中,只有整型和浮点型可以使用,void 仅用于函数声明。 +一个变量声明定义一个整型或者浮点型的变量,或者一个整型或浮点型的数组变量(这里整型指的是32位有符号整型,浮点数是指32位浮点数)。 +数组变量在声明时,INTEGER应当大于0。 +一次只能声明一个变量。 +``` + +在ast.hpp中查看ASTVarDeclaration的结构定义: +``` +struct ASTVarDeclaration: ASTDeclaration { + virtual void accept(ASTVisitor &) override final; + CminusType type; + std::shared_ptr num; +}; +``` + +根据节点的定义,节点中包含一个类型和一个指针,还有继承自ASTDeclaration的id。对于变量声明节点的处理,需要产生分配空间的IR,在处理需考虑两个维度:是否全局变量,是否数组定义(根据节点的指针是否为空区分),分4类讨论。并且要把声明的变量放入当前作用域中,保证后续使用可以找到。 + +根据语义规则,全局变量需要初始化为0,数组变量声明时,大小应该大于0。 + +根据要求,实现如下: +``` +/* Var-Declaration, 变量声明, var-declaration -> type-specifier ID | type-specifier ID [INTEGER] */ +void CminusfBuilder::visit(ASTVarDeclaration &node) +{ + // ASTVarDeclaration有两个成员变量: + // CminusType type; + // std::shared_ptr num; + + Type *tmpType; /* 类型暂存变量,用于存储变量的类型,用于后续申请空间 */ + if (node.type == TYPE_INT) /* 若为整型 */ + tmpType = Int32Type; /* 则type为整数类型 */ + else if (node.type == TYPE_FLOAT) /* 则为浮点型 */ + tmpType = FloatType; /* 则type为浮点类型 */ + else + std::cout << "ERROR: 在变量声明中,只有整型和浮点型可以使用\n"; + + // 需考虑两个维度:是否全局变量,是否数组定义,分4类讨论 + if (node.num != nullptr) + { /* 若为数组类型 */ + if (node.num->i_val <= 0) + std::cout << "ERROR: 数组长度必须大于c0\n"; + /* 获取需开辟的对应大小的空间的类型指针 */ + auto *arrayType = ArrayType::get(tmpType, node.num->i_val); /* 获取对应的数组Type */ + auto initializer = CONST_ZERO(tmpType); /* 全局变量初始化为0 */ + Value *arrayAlloca; /* 存储申请到的数组空间的地址 */ + if (scope.in_global()) /* 若为全局数组,则开辟全局数组 */ + arrayAlloca = GlobalVariable::create(node.id, module.get(), arrayType, false, initializer); + else /* 若不是全局数组,则开辟局部数组 */ + arrayAlloca = builder->create_alloca(arrayType); + scope.push(node.id, arrayAlloca); /* 将获得的数组变量加入域 */ + } + else + { /* 若不是数组类型 */ + auto initializer = CONST_ZERO(tmpType); /* 全局变量初始化为0 */ + Value *varAlloca; /* 存储申请到的变量空间的地址 */ + if (scope.in_global()) /* 若为全局变量,则申请全局空间 */ + varAlloca = GlobalVariable::create(node.id, module.get(), tmpType, false, initializer); + else /* 若不是全局变量,则申请局部空间 */ + varAlloca = builder->create_alloca(tmpType); + scope.push(node.id, varAlloca); /* 将获得变量加入域 */ + } +} +``` +#### (4) void CminusfBuilder::visit(ASTFunDeclaration &node) +**函数声明** + +对应的语法为: + +==$fun-declaration {\longrightarrow} type-specifier \quad ID \quad ( params ) \quad compound-stmt$== + +==$params {\longrightarrow} param-list | void$== + +==$param-list {\longrightarrow} param-list , param | param$== + +==$param {\longrightarrow} type-specifier \quad ID \quad | \quad type-specifier \quad ID \quad []$== + +``` +函数声明包含了返回类型,标识符,由逗号分隔的形参列表,还有一个复合语句。 +当函数的返回类型是 void 时,函数不返回任何值。 +函数的参数可以是 void ,也可以是一个列表。当函数的形参是void时,调用该函数时不用传入任何参数。 +形参中跟着中括号代表数组参数,它们可以有不同长度。 +整型参数通过值来传入函数(pass by value),而数组参数通过引用来传入函数(pass by reference,即指针)。 +函数的形参拥有和函数声明的复合语句相同的作用域,并且每次函数调用都会产生一组独立内存的参数。(和C语言一致) +函数可以递归调用 +``` +在ast.hpp中查看ASTFunDeclaration的结构定义: +``` +struct ASTFunDeclaration: ASTDeclaration { + virtual void accept(ASTVisitor &) override final; + std::vector> params; + std::shared_ptr compound_stmt; +}; +``` +FunDeclaration节点包含一个形参列表param和复合语句compound-stmt。需要创建的IR是创建函数和创建函数的第一个BasicBlock的指令,然后处理复合语句。在进入函数时要进入函数作用域,创建函数时要处理参数与返回值。对于每个参数,用全局变量取出实参,调用accept函数进行处理,在Param的visit函数中完成存储空间的分配,并加入到函数作用域当中。 + +根据要求,实现如下: +``` +void CminusfBuilder::visit(ASTFunDeclaration &node) +{ + // ASTFunDeclaration有两个成员变量: + // std::vector> params; + // std::shared_ptr compound_stmt; + + // 考虑函数返回类型+函数名+参数列表+复合语句(局部声明与语句列表) + + Type *retType; /* 函数返回类型 */ + /* 根据不同的返回类型,设置retType */ + if (node.type == TYPE_INT) + { + retType = Int32Type; + } + else if (node.type == TYPE_FLOAT) + { + retType = FloatType; + } + else if (node.type == TYPE_VOID) + { + retType = Type::get_void_type(module.get()); + } + + /* 根据函数声明,构造形参列表(此处的形参即参数的类型) */ + std::vector paramsType; /* 参数类型列表 */ + for (auto param : node.params) + { + if (param->isarray) + { /* 若参数为数组形式,则存入首地址指针 */ + if (param->type == TYPE_INT) /* 若为整型 */ + paramsType.push_back(Type::get_int32_ptr_type(module.get())); + else if (param->type == TYPE_FLOAT) /* 若为浮点型 */ + paramsType.push_back(Type::get_float_ptr_type(module.get())); + } + else + { /* 若为单个变量形式,则存入对应类型 */ + if (param->type == TYPE_INT) /* 若为整型 */ + paramsType.push_back(Int32Type); + else if (param->type == TYPE_FLOAT) /* 若为浮点型 */ + paramsType.push_back(FloatType); + } + } + auto funType = FunctionType::get(retType, paramsType); /* retType返回结构,paramsType函数形参结构 */ + auto function = Function::create(funType, node.id, module.get()); /* 创建函数 */ + scope.push(node.id, function); /* 将函数加入到全局作用域 */ + scope.enter(); /* 进入此函数作用域 */ + auto bb = BasicBlock::create(module.get(), node.id + "_entry", function); /* 创建基本块 */ + builder->set_insert_point(bb); /* 将基本块加入到builder中 */ + /* 函数传参,要将实参和形参进行匹配 */ + std::vector args; /* 创建vector存储实参 */ + for (auto arg = function->arg_begin(); arg != function->arg_end(); arg++) + { /* 遍历实参列表 */ + args.push_back(*arg); /* 将实参加入vector */ + } + for (int i = 0; i < node.params.size(); i++) + { /* 遍历形参列表(=遍历实参列表) */ + auto param = node.params[i]; /* 取出对应形参 */ + arg = args[i]; /* 取出对应实参 */ + param->accept(*this); /* 调用param的accept进行处理 */ + } + node.compound_stmt->accept(*this); /* 处理函数体内语句compound-stmt */ + // 判断返回值的类型,根据对应的返回值类型,执行ret操作 + // 这里应该是实现若没有显式返回,默认返回对应类型的0或void + if (builder->get_insert_block()->get_terminator() == nullptr) + { + if (function->get_return_type()->is_void_type()) + builder->create_void_ret(); + else if (function->get_return_type()->is_float_type()) + builder->create_ret(CONST_FP(0.0)); + else + builder->create_ret(CONST_INT(0)); + } + scope.exit(); /* 退出此函数作用域 */ +} + +``` + +#### (5) void CminusfBuilder::visit(ASTParam &node) + +对应的语法为: + +==$param {\longrightarrow} type-specifier \quad ID \quad | \quad type-specifier \quad ID \quad []$== + +在ast.hpp中查看ASTParam的结构定义: +``` +struct ASTParam: ASTNode { + virtual void accept(ASTVisitor &) override final; + CminusType type; + std::string id; + // true if it is array param + bool isarray; +}; +``` + +在处理参数时,要为参数分配空间,使参数能够保留在函数的作用域内。 +在将cminus转换为IR时,cminus的语义规定了每次函数调用都会产生一组独立内存的参数,因此为参数分配空间,并存入作用域。 + +根据要求,实现如下: + +``` +/* Param, 参数 */ +void CminusfBuilder::visit(ASTParam &node) +{ + // ASTParam有3个成员变量 + // CminusType type; 参数类型 + // std::string id; 参数名 + // bool isarray; 是否数组标记 + + Value *paramAlloca; // 分配该参数的存储空间 + // 区分是否为数组,为整型还是浮点型,共分为4类讨论 + if (node.isarray) + { /* 若为数组 */ + if (node.type == TYPE_INT) /* 若为整型数组,则开辟整型数组存储空间 */ + paramAlloca = builder->create_alloca(Type::get_int32_ptr_type(module.get())); + else if (node.type == TYPE_FLOAT) /* 若为浮点数数组,则开辟浮点数数组存储空间 */ + paramAlloca = builder->create_alloca(Type::get_float_ptr_type(module.get())); + } + else + { /* 若不是数组 */ + if (node.type == TYPE_INT) /* 若为整型,则开辟整型存储空间 */ + paramAlloca = builder->create_alloca(Int32Type); + else if (node.type == TYPE_FLOAT) /* 若为浮点数,则开辟浮点数存储空间 */ + paramAlloca = builder->create_alloca(FloatType); + } + builder->create_store(arg, paramAlloca); /* 将实参load到开辟的存储空间中 */ + // 此处arg通过全局变量传递、 + // 函数原型:StoreInst *create_store(Value *val, Value *ptr) + scope.push(node.id, paramAlloca); /* 将参数push到域中 */ +} +``` +#### (6) void CminusfBuilder::visit(ASTCompoundStmt &node) + +对应的语法为: + +==$compound-stmt {\longrightarrow} \{ local-declarations \quad statement-list \}$== + +在ast.hpp中查看ASTCompoundStmt的结构定义: +``` +struct ASTCompoundStmt: ASTStatement { + virtual void accept(ASTVisitor&) override final; + std::vector> local_declarations; + std::vector> statement_list; +}; +``` + +每个函数内部都有一个复合语句,根据ASTCompoundStmt的定义,复合语句由局部声明和一系列语句构成。只需要逐个调用相应的accept函数,不需要产生IR。 + +根据要求,实现如下: + +``` +void CminusfBuilder::visit(ASTCompoundStmt &node) +{ + // ASTCompoundStmt有两个成员变量 + // std::vector> local_declarations; + // std::vector> statement_list; + + scope.enter(); /* 进入函数体内的作用域 */ + for (auto local_declaration : node.local_declarations) /* 遍历 */ + local_declaration->accept(*this); /* 处理每一个局部声明 */ + for (auto statement : node.statement_list) /* 遍历 */ + statement->accept(*this); /* 处理每一个语句 */ + scope.exit(); /* 退出作用域 */ +} +``` + +#### (7) void CminusfBuilder::visit(ASTExpressionStmt &node) + +对应的语法为: + +==$expression-stmt {\longrightarrow} expression ; | ;$== + +在ast.hpp中查看ASTExpressionStmt的结构定义: +``` +struct ASTExpressionStmt: ASTStatement { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr expression; +}; +``` +ExpressionStmt对应一条表达式或空,只要表达式存在,就处理该表达式,否则不需要做处理。 + +根据要求,实现如下: + +``` +/* ExpressionStmt, 表达式语句, expression-stmt -> expression; | ; */ +void CminusfBuilder::visit(ASTExpressionStmt &node) +{ + // ASTExpressionStmt只有一个成员变量: + // std::shared_ptr expression; + + if (node.expression != nullptr) /* 若对应表达式存在 */ + node.expression->accept(*this); /* 则处理该表达式 */ +} +``` +#### (8) void CminusfBuilder::visit(ASTSelectionStmt &node) +对应的语法为: + +==$selection-stmt {\longrightarrow} if \quad ( expression ) \quad statement \quad | \quad if \quad ( expression ) \quad statement \quad else \quad statement$== + +if语句中的表达式将被求值,若结果的值等于0,则第二个语句执行(如果存在的话),否则第一个语句会执行。 + +为了避免歧义,else 将会匹配最近的将会匹配最近的 if + +在ast.hpp中查看ASTSelectionStmt的结构定义: +``` +struct ASTSelectionStmt: ASTStatement { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr expression; + std::shared_ptr if_statement; + // should be nullptr if no else structure exists + std::shared_ptr else_statement; +}; +``` +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) +{ + // ASTSelectionStmt有3个成员变量: + // std::shared_ptr expression; 判断条件 + // std::shared_ptr if_statement; if语句执行体 + // std::shared_ptr else_statement; else语句执行体 + // 若无else语句,else_statement == nullptr + + + // 构建判断条件代码 + node.expression->accept(*this); /* 处理条件判断对应的表达式,得到返回值存到expression中 */ + auto resType = Res->get_type(); /* 获取表达式得到的结果类型 */ + if (resType->is_pointer_type()) + { /* 若结果为指针型,则针对指针型进行处理 */ + Res = builder->create_load(Res); /* 大于0视为true */ + } + else if (resType->is_integer_type()) + { /* 若结果为整型,则针对整型进行处理(bool类型视为整型) */ + Res = builder->create_icmp_gt(Res, CONST_ZERO(Int32Type)); /* 大于0视为true */ + } + else if (resType->is_float_type()) + { /* 若结果为浮点型,则针对浮点数进行处理 */ + Res = builder->create_fcmp_gt(Res, CONST_ZERO(FloatType)); /* 大于0视为true */ + } + auto function = builder->get_insert_block()->get_parent(); /* 获得当前所对应的函数 */ + + auto trueBB = BasicBlock::create(module.get(), "true", function); /* 创建符合条件块 */ + + // 构建语句执行体代码 + // 根据是否存在else语句分2类讨论 + if (node.else_statement != nullptr) + { /* 若存在else语句 */ + auto falseBB = BasicBlock::create(module.get(), "false", function); /* 创建else块 */ + builder->create_cond_br(Res, trueBB, falseBB); /* 设置跳转语句 */ + // 处理trueBB + builder->set_insert_point(trueBB); /* 符合if条件的块 */ + node.if_statement->accept(*this); /* 处理if语句执行体 */ + auto curTrueBB = builder->get_insert_block(); /* 将块加入 */ + // 处理falseBB + builder->set_insert_point(falseBB); /* else的块 */ + node.else_statement->accept(*this); /* 处理else语句执行体 */ + auto curFalseBB = builder->get_insert_block(); /* 将块加入 */ + + /* 处理返回,避免跳转到对应块后无return */ + // 判断true语句中是否存在ret语句 + auto trueTerm = builder->get_insert_block()->get_terminator(); + // 判断else语句中是否存在ret语句 + auto falseTerm = builder->get_insert_block()->get_terminator(); + + if (trueTerm == nullptr || falseTerm == nullptr) + { /* 若有一方不存在return语句,则需要创建返回块 */ + BasicBlock *retBB; + retBB = BasicBlock::create(module.get(), "ret", function); /* 创建return块 */ + builder->set_insert_point(retBB); /* return块(即后续语句) */ + if (trueTerm == nullptr) + { /* 若if语句执行体不存在return */ + builder->set_insert_point(curTrueBB); /* 则设置跳转 */ + } + else if (falseTerm == nullptr) + { /* 若else语句执行体中不存在return */ + builder->set_insert_point(curFalseBB); /* 则设置跳转 */ + } + builder->create_br(retBB); /* 跳转到刚刚设置的return块 */ + } + } + else + { /* 若不存在else语句,则只需要设置true语句块和后续语句块即可 */ + auto retBB = BasicBlock::create(module.get(), "ret", function); /* 后续语句块 */ + builder->create_cond_br(Res, trueBB, retBB); /* 根据条件设置跳转指令 */ + + builder->set_insert_point(trueBB); /* true语句块 */ + node.if_statement->accept(*this); /* 执行条件符合后要执行的语句 */ + if (builder->get_insert_block()->get_terminator() == nullptr) /* 补充return(同上) */ + builder->create_br(retBB); /* 跳转到刚刚设置的return块 */ + builder->set_insert_point(retBB); /* return块(即后续语句) */ + } +} +``` +#### (9) void CminusfBuilder::visit(ASTIterationStmt &node) +对应的语法为: + +==$iteration-stmt {\longrightarrow} while ( expression ) statement$== + +在ast.hpp中查看ASTIterationStmt的结构定义: +``` +struct ASTIterationStmt: ASTStatement { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr expression; + std::shared_ptr statement; +}; +``` +while迭代语句有一个条件表达式,进行条件跳转(与if类似)。可以创建一个用于判断的ifBasicBlock,一个循环的loopBasicBlock,一个while语句后的afterWhileBasicBlock,添加相应的指令。当条件表达式为True时,进行ifBB->loopBB->ifBB的循环跳转。 + +根据要求,实现如下: +``` +/* IterationStmt, while语句, iteration-stmt -> while (expression) statement */ +void CminusfBuilder::visit(ASTIterationStmt &node) +{ + // ASTIterationStmt有2个成员变量: + // std::shared_ptr expression; 循环判断条件 + // std::shared_ptr statement; while循环执行体 + + auto function = builder->get_insert_block()->get_parent(); /* 获得当前所对应的函数 */ + auto conditionBB = BasicBlock::create(module.get(), "condition", function); /* 创建条件判断块 */ + auto loopBB = BasicBlock::create(module.get(), "loop", function); /* 创建循环语句块 */ + auto retBB = BasicBlock::create(module.get(), "ret", function); /* 创建后续语句块 */ + if (builder->get_insert_block()->get_terminator() == nullptr) + builder->create_br(conditionBB); // 跳转到条件判断块 + + // 构建条件判断代码 + builder->set_insert_point(conditionBB); /* 条件判断块 */ + node.expression->accept(*this); /* 处理条件判断对应的表达式,得到返回值存到expression中 */ + auto resType = Res->get_type(); /* 获取表达式得到的结果类型 */ + if (resType->is_pointer_type()) + { /* 若结果为指针型,则针对指针型进行处理 */ + Res = builder->create_load(Res); /* 大于0视为true */ + } + else if (resType->is_integer_type()) + { /* 若结果为整型,则针对整型进行处理(bool类型视为整型) */ + Res = builder->create_icmp_gt(Res, CONST_ZERO(Int32Type)); /* 大于0视为true */ + } + else if (resType->is_float_type()) + { /* 若结果为浮点型,则针对浮点数进行处理 */ + Res = builder->create_fcmp_gt(Res, CONST_ZERO(FloatType)); /* 大于0视为true */ + } + builder->create_cond_br(Res, loopBB, retBB); /* 设置条件跳转语句 */ + + // 构建循环语句代码 + builder->set_insert_point(loopBB); /* 循环语句执行块 */ + node.statement->accept(*this); /* 执行对应的语句 */ + if (builder->get_insert_block()->get_terminator() == nullptr) /* 若无返回,则补充跳转 */ + builder->create_br(conditionBB); /* 跳转到条件判断语句 */ + + builder->set_insert_point(retBB); /* return块(即后续语句) */ +} +``` +#### (10) void CminusfBuilder::visit(ASTReturnStmt &node) +对应的语法为: + +==$return-stmt {\longrightarrow} return ; | return \quad expression ;$== + +在ast.hpp中查看ASTReturnStmt的结构定义: +``` +struct ASTReturnStmt: ASTStatement { + virtual void accept(ASTVisitor &) override final; + // should be nullptr if return void + std::shared_ptr expression; +}; +``` +返回语句中有一个表达式计算返回值,如果指向该返回语句的指针为空,说明没有返回值,创建一个void返回IR,否则需要调用该表达式的accept函数,并检查返回类型是否和函数的返回类型相同。 + +根据要求,实现如下: +``` +/* ReturnStmt, 返回语句, return-stmt -> return; + * | return expression; */ +void CminusfBuilder::visit(ASTReturnStmt &node) +{ + // ASTReturnStmt只有1个成员变量 + // std::shared_ptr expression; 返回语句表达式 + + auto function = builder->get_insert_block()->get_parent(); /* 获得当前所对应的函数 */ + auto retType = function->get_return_type(); /* 获取返回类型 */ + + // 处理返回语句表达式 + if (retType->is_void_type()) + { + // 如果本来就是void类型的函数,需要返回void + builder->create_void_ret(); /* 则创建void返回,随后return,无需后续操作 */ + return; + } + // 如果需要返回非void + node.expression->accept(*this); /* 处理条件判断对应的表达式,得到返回值存到expression中 */ + auto resType = Res->get_type(); /* 获取表达式得到的结果类型 */ + /* 处理expression返回的结果和需要return的结果类型不匹配的问题 */ + // 使用强制类型转换 + if (retType->is_integer_type() && resType->is_float_type()) + Res = builder->create_fptosi(Res, Int32Type); + if (retType->is_float_type() && resType->is_integer_type()) + Res = builder->create_sitofp(Res, Int32Type); + builder->create_ret(Res); /* 创建return,将expression的结果进行返回 */ +} +``` +#### (11) void CminusfBuilder::visit(ASTVar &node) +对应的语法为: + +==$var {\longrightarrow} ID | ID [ expression]$== + +在ast.hpp中查看ASTVar的结构定义: +``` +struct ASTVar: ASTFactor { + virtual void accept(ASTVisitor &) override final; + std::string id; + // nullptr if var is of int type + std::shared_ptr expression; +}; +``` +根据cminus的语义说明,Var可以是整型变量,浮点变量或数组变量。如果是数组变量,需要判断下标是否为负,如果为负则添加neg_idx_except指令退出程序,否则计算对应元素的地址(gep指令)。如果是数组,则下标可能是个表达式,需要确保表达式的返回结果为整型,然后才能进行取元素的操作。 + +根据要求,实现如下: +``` +void CminusfBuilder::visit(ASTVar &node) +{ + // ASTVar有2个成员变量: + // std::string id; 变量名 + // std::shared_ptr expression; 变量类型 + // 如果变量var是int型的,这个取nullptr + + auto var = scope.find(node.id); /* 从域中取出对应变量 */ + bool if_return_lvalue = need_as_address; /* 判断是否需要返回地址(即是否由赋值语句调用) */ + need_as_address = false; /* 重置全局变量need_as_address */ + Value *index = CONST_INT(0); /* 初始化index */ + if (node.expression != nullptr) + { /* 若有expression,代表不是int类型的引用*/ + node.expression->accept(*this); /* 处理expression,得到结果Res */ + auto res = Res; /* 存储结果 */ + if (checkFloat(res)) /* 判断结果是否为浮点数 */ + res = builder->create_fptosi(res, Int32Type); /* 若是,则矫正为整数 */ + index = res; /* 赋值给index,表示数组下标 */ + /* 判断下标是否为负数。若是,则调用neg_idx_except函数进行处理 */ + auto function = builder->get_insert_block()->get_parent(); /* 获取当前函数 */ + auto indexTest = builder->create_icmp_lt(index, CONST_ZERO(Int32Type)); /* test是否为负数 */ + auto failBB = BasicBlock::create(module.get(), node.id + "_failTest", function); /* fail块 */ + auto passBB = BasicBlock::create(module.get(), node.id + "_passTest", function); /* pass块 */ + builder->create_cond_br(indexTest, failBB, passBB); /* 设置跳转语句 */ + + // 下标为负数,调用neg_idx_except函数进行处理 + builder->set_insert_point(failBB); /* fail块,即下标为负数 */ + auto fail = scope.find("neg_idx_except"); /* 取出neg_idx_except函数 */ + builder->create_call(static_cast(fail), {}); /* 调用neg_idx_except函数进行处理 */ + builder->create_br(passBB); /* 跳转到pass块 */ + + // 下标合法, + builder->set_insert_point(passBB); /* pass块 */ + if (var->get_type()->get_pointer_element_type()->is_array_type()) /* 若为指向数组的指针 */ + var = builder->create_gep(var, {CONST_INT(0), index}); /* 则进行两层寻址 */ + else + { + if (var->get_type()->get_pointer_element_type()->is_pointer_type()) /* 若为指针 */ + var = builder->create_load(var); /* 则取出指针指向的元素 */ + var = builder->create_gep(var, {index}); /* 进行一层寻址(因为此时并非指向数组) */ + } + if (if_return_lvalue) + { /* 若要返回值 */ + Res = var; /* 则返回var对应的地址 */ + need_as_address = false; /* 并重置全局变量need_as_address */ + } + else + Res = builder->create_load(var); /* 否则则进行load */ + return; + } + else + { // 处理不是数组的情况 + if (if_return_lvalue) + { /* 若要返回值 */ + Res = var; /* 则返回var对应的地址 */ + need_as_address = false; /* 并重置全局变量need_as_address */ + } + else + { /* 否则 */ + // 数组的指针即a[]类型就返回数组的起始地址,否则load取值 + if (var->get_type()->get_pointer_element_type()->is_array_type()) /* 若指向数组,需要两个偏移取地址 */ + Res = builder->create_gep(var, {CONST_INT(0), CONST_INT(0)}); /* 则寻址 */ + else + Res = builder->create_load(var); /* 否则则进行load */ + } + } +} +``` +#### (12) void CminusfBuilder::visit(ASTAssignExpression &node) +对应的语法为: + +==$expression {\longrightarrow} var = expression | simple-expression$== + +在ast.hpp中查看ASTAssignExpression的结构定义: +``` +struct ASTAssignExpression: ASTExpression { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr var; + std::shared_ptr expression; +}; +``` +对于Assign语句,将全局变量need_as_address置为true,调用子节点var的accept函数得到变量的地址,然后计算表达式的值,创建store指令将值存入地址。需要确认表达式结果是否与变量类型相同,如果不同需要将表达式结果转换为和变量相同的类型。 + +根据要求,实现如下: +``` +/* AssignExpression, 赋值语句, var = expression */ +void CminusfBuilder::visit(ASTAssignExpression &node) +{ + // ASTAssignExpression有2个成员变量 + // std::shared_ptr var; 左值(被赋值的对象) + // std::shared_ptr expression; 右值(赋的值) + + need_as_address = true; /* 设置need_as_address,表示需要返回值 */ + + // 获取左值,右值 + node.var->accept(*this); /* 处理左var */ + auto left = Res; /* 获取地址 */ + node.expression->accept(*this); /* 处理右expression */ + auto right = Res; /* 获得结果 */ + + // 处理左值,右值类型冲突问题 + auto leftType = left->get_type()->get_pointer_element_type(); /* 获取var的类型 */ + /* 若赋值语句左右类型不匹配,则进行匹配 */ + if (leftType == FloatType && checkInt(right)) + right = builder->create_sitofp(right, FloatType); + if (leftType == Int32Type && checkFloat(right)) + right = builder->create_fptosi(right, Int32Type); + + // 赋值 + builder->create_store(right, left); +} +``` +#### (13) void CminusfBuilder::visit(ASTSimpleExpression &node) +对应的语法为: + +==$simple-expression {\longrightarrow} additive-expression \quad relop \quad additive-expression | additive-expression$== + +在ast.hpp中查看ASTSimpleExpression的结构定义: +``` +struct ASTSimpleExpression: ASTExpression { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr additive_expression_l; + std::shared_ptr additive_expression_r; + RelOp op; +}; +``` +简单表达式SimpleExpression是一个加法表达式或两个加法表达式的关系运算。在节点中有两个加法表达式的指针和一个运算符类型为RelOp的运算符op,RelOp是一个枚举类型,包含了所有比较运算符。根据语义,对于该节点的处理,应该先处理加法表达式,将表达式的值保存下来,如果两个表达式指针都不为空,说明为关系运算,再比较两个运算结果,根据结果将表达式的值赋为0或1。进行比较时需要注意两个值的类型,整型和浮点型比较时要将整型转换为浮点型。 + +具体实现中,应该调用加法表达式的accept函数(如果指针不为空),暂存结果,对于比较运算,根据op生成icmp或fcmp的指令,最后返回的值就是指令结果。 + +根据要求,实现如下: +``` +void CminusfBuilder::visit(ASTSimpleExpression &node) +{ + // ASTSimpleExpression有3个成员变量 + // std::shared_ptr additive_expression_l; + // std::shared_ptr additive_expression_r; + // RelOp op; + + node.additive_expression_l->accept(*this); /* 处理左边的expression */ + auto lres = Res; /* 获取结果存到lres中 */ + if (node.additive_expression_r == nullptr) + { + return; + } //* 若不存在右expression,则直接返回 */ + node.additive_expression_r->accept(*this); /* 处理右边的expression */ + auto rres = Res; /* 结果存到rres中 */ + + // 确保两个表达式的类型相同,若存在浮点和整型混存,全部转换为浮点型 + if (checkInt(lres) && checkInt(rres)) + { /* 确保两边都是整数 */ + switch (node.op) + { + /* 根据不同的比较操作,调用icmp进行处理 */ + // 比较的返回结果 + // enum RelOp: + // <= 对应 OP_LE, + // < 对应 OP_LT, + // > 对应 OP_GT, + // >= 对应 OP_GE, + // == 对应 OP_EQ, + // != 对应 OP_NEQ + case OP_LE: + Res = builder->create_icmp_le(lres, rres); + break; + case OP_LT: + Res = builder->create_icmp_lt(lres, rres); + break; + case OP_GT: + Res = builder->create_icmp_gt(lres, rres); + break; + case OP_GE: + Res = builder->create_icmp_ge(lres, rres); + break; + case OP_EQ: + Res = builder->create_icmp_eq(lres, rres); + break; + case OP_NEQ: + Res = builder->create_icmp_ne(lres, rres); + break; + } + } + else + { /* 若有一边是浮点类型,则需要先将另一边也转为浮点数,再进行比较 */ + if (checkInt(lres)) /* 若左边是整数,则将其转为浮点数 */ + lres = builder->create_sitofp(lres, FloatType); + if (checkInt(rres)) /* 若右边是整数,则将其转为浮点数 */ + rres = builder->create_sitofp(rres, FloatType); + switch (node.op) + { + /* 根据不同的比较操作,调用fcmp进行处理 */ + case OP_LE: + Res = builder->create_fcmp_le(lres, rres); + break; + case OP_LT: + Res = builder->create_fcmp_lt(lres, rres); + break; + case OP_GT: + Res = builder->create_fcmp_gt(lres, rres); + break; + case OP_GE: + Res = builder->create_fcmp_ge(lres, rres); + break; + case OP_EQ: + Res = builder->create_fcmp_eq(lres, rres); + break; + case OP_NEQ: + Res = builder->create_fcmp_ne(lres, rres); + break; + } + } + Res = builder->create_zext(Res, Int32Type); /* 将结果作为整数保存(可作为判断语句中的判断条件) */ +} +``` +#### (14) void CminusfBuilder::visit(ASTAdditiveExpression &node) +对应的语法为: + +==$additive-expression {\longrightarrow} additive-expression \quad addop \quad term | term$== + +在ast.hpp中查看ASTAdditiveExpression的结构定义: +``` +struct ASTAdditiveExpression: ASTNode { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr additive_expression; + AddOp op; + std::shared_ptr term; +}; +``` +加法表达式中包含了一个乘法表达式`term→term mulop factor | factor`,一个加法表达式和一个运算符。如果加法表达式指针为空,则表达式的值就是乘法表达式的值,否则分别计算两个表达式,调用相应的accept函数,然后进行根据运算符生成加或减指令。 + +根据要求,实现如下: +``` +void CminusfBuilder::visit(ASTAdditiveExpression &node) +{ + // ASTAdditiveExpression有3个成员变量: + // std::shared_ptr additive_expression; + // AddOp op; + // std::shared_ptr term; + + if (node.additive_expression == nullptr) + { /* 若无加减法运算 */ + node.term->accept(*this); + return; /* 则直接去做乘除法 */ + } + node.additive_expression->accept(*this); /* 处理左expression */ + auto lres = Res; /* 结果保存在lres中 */ + node.term->accept(*this); /* 处理右term */ + auto rres = Res; /* 结果保存在rres中 */ + + // 分为整型-整型,和存在浮点数类型,这两种情况进行讨论 + // 若存在浮点数,则全部强制转换为浮点数实现 + if (checkInt(lres) && checkInt(rres)) + { /* 确保两边都是整数 */ + switch (node.op) + { /* 根据对应加法或是减法,调用iadd或是isub进行处理 */ + case OP_PLUS: + Res = builder->create_iadd(lres, rres); + break; + case OP_MINUS: + Res = builder->create_isub(lres, rres); + break; + } + } + else + { /* 若有一边是浮点类型,则需要先将另一边也转为浮点数,再进行处理 */ + if (checkInt(lres)) /* 若左边是整数,则将其转为浮点数 */ + lres = builder->create_sitofp(lres, FloatType); + if (checkInt(rres)) /* 若右边是整数,则将其转为浮点数 */ + rres = builder->create_sitofp(rres, FloatType); + switch (node.op) + { /* 根据对应加法或是减法,调用fadd或是fsub进行处理 */ + case OP_PLUS: + Res = builder->create_fadd(lres, rres); + break; + case OP_MINUS: + Res = builder->create_fsub(lres, rres); + break; + } + } +} +``` +#### (15) void CminusfBuilder::visit(ASTTerm &node) +对应的语法为: + +==$term {\longrightarrow} term \quad mulop \quad factor | factor$== + +在ast.hpp中查看ASTTerm的结构定义: +``` +struct ASTTerm : ASTNode { + virtual void accept(ASTVisitor &) override final; + std::shared_ptr term; + MulOp op; + std::shared_ptr factor; +}; +``` +乘法表达式由乘法表达式和因子或单独一个因子构成。与加法表达式的处理相同。 + +根据要求,实现如下: +``` +void CminusfBuilder::visit(ASTTerm &node) +{ + // ASTTerm有3个成员变量: + // std::shared_ptr term; + // MulOp op; + // std::shared_ptr factor; + + if (node.term == nullptr) + { /* 若无乘法运算 */ + node.factor->accept(*this); + return; /* 则直接去处理元素 */ + } + node.term->accept(*this); /* 处理左term */ + auto lres = Res; /* 结果保存在lres中 */ + node.factor->accept(*this); /* 处理右factor */ + auto rres = Res; /* 结果保存在rres中 */ + if (checkInt(lres) && checkInt(rres)) + { /* 确保两边都是整数 */ + switch (node.op) + { /* 根据对应乘法或是除法,调用imul或是idiv进行处理 */ + case OP_MUL: + Res = builder->create_imul(lres, rres); + break; + case OP_DIV: + Res = builder->create_isdiv(lres, rres); + break; + } + } + else + { /* 若有一边是浮点类型,则需要先将另一边也转为浮点数,再进行处理 */ + if (checkInt(lres)) /* 若左边是整数,则将其转为浮点数 */ + lres = builder->create_sitofp(lres, FloatType); + if (checkInt(rres)) /* 若右边是整数,则将其转为浮点数 */ + rres = builder->create_sitofp(rres, FloatType); + switch (node.op) + { /* 根据对应乘法或是除法,调用fmul或是fdiv进行处理 */ + case OP_MUL: + Res = builder->create_fmul(lres, rres); + break; + case OP_DIV: + Res = builder->create_fdiv(lres, rres); + break; + } + } +} +``` +#### (16) void CminusfBuilder::visit(ASTCall &node) +对应的语法为: + +==$call {\longrightarrow} ID ( args)$== + +在ast.hpp中查看ASTCall的结构定义: +``` +struct ASTCall: ASTFactor { + virtual void accept(ASTVisitor &) override final; + std::string id; + std::vector> args; +}; +``` +call节点需要创建一条函数调用call指令,从作用域中取出函数,然后根据函数的参数将节点的实参传入,并检查类型是否与函数参数的类型一致,不一致则需要转换为函数的形参类型。创建一个参数列表,将转换好的参数存入列表来调用函数。 + +根据要求,实现如下: +``` +/* Call, 函数调用, call -> ID (args) */ +void CminusfBuilder::visit(ASTCall &node) +{ + // ASTCall有3个成员变量: + // std::string id; + // std::vector> args; + + auto function = static_cast(scope.find(node.id)); /* 获取需要调用的函数 */ + auto paramType = function->get_function_type()->param_begin(); /* 获取其参数类型 */ + if (function == nullptr) + std::cout << "ERROR: 函数" << node.id << "未定义\n"; + + // 处理参数列表 + std::vector args; /* 创建args用于存储函数参数的值,构建调用函数的参数列表 */ + for (auto arg : node.args) + { /* 遍历形参列表 */ + arg->accept(*this); /* 对每一个参数进行处理,获取参数对应的值 */ + if (Res->get_type()->is_pointer_type()) + { /* 若参数是指针 */ + args.push_back(Res); /* 则直接将值加入到参数列表 */ + } + else + { /* 若不是指针,则需要判断形参和实参的类型是否符合。若不符合则需要类型转换 */ + if (*paramType == FloatType && checkInt(Res)) + Res = builder->create_sitofp(Res, FloatType); + if (*paramType == Int32Type && checkFloat(Res)) + Res = builder->create_fptosi(Res, Int32Type); + args.push_back(Res); + } + paramType++; /* 查看下一个形参 */ + } + Res = builder->create_call(static_cast(function), args); /* 创建函数调用 */ +} +``` +### 4. 实验测试 + +#### (1) 构建 +在build文件夹下输入如下指令,使我们写的cminusf_builder.cpp文件纳入编译。 +``` +make clean +sudo make install +``` +在Unix-like系统(如Linux)中,/usr/local/lib/ 通常需要管理员权限才能写入。因此,当你尝试以普通用户身份安装软件到这个目录时,会遇到权限问题。 + +要解决这个问题,你可以: + +使用sudo命令: +如果你确定要将文件安装到 /usr/local/lib/,并且你有管理员权限,你可以在 make install 命令前加上 sudo 来提升权限: +sudo make install -1. 如何设计全局变量 -2. 遇到的难点以及解决方案 -3. 如何降低生成 IR 中的冗余 -4. ... +![输入图片说明](../../image.png) +#### (2) 自动测试 +助教贴心地为大家准备了自动测试脚本,它在 `tests/lab4` 目录下,使用方法如下: +``` +# 在 tests/lab4 目录下运行: +./lab4_test.py +``` +如果完全正确,它会输出: +``` +===========TEST START=========== +Case 01: Success +Case 02: Success +Case 03: Success +Case 04: Success +Case 05: Success +Case 06: Success +Case 07: Success +Case 08: Success +Case 09: Success +Case 10: Success +Case 11: Success +Case 12: Success +============TEST END============ +``` +![输入图片说明](../../image-1.png) ### 实验总结 -此次实验有什么收获 +本次实验具有一定的难度,很考验对于实验3知识的融汇贯通。 -### 实验反馈 (可选 不会评分) +通过本次实验,理解了从抽象语法树产生中间IR的方法,并进行了实现。在实现过程中,对于IR的指令有了进一步的熟悉与理解,掌握了使用C++接口创建不同IR指令的方法,以及在访问者模式下遍历抽象语法树,完成IR生成的过程。在完成实现时需要多次跳转,阅读相关的头文件,语义规则,原型函数定义,原型结构体定义等。 -对本次实验的建议 +经过四次实验,结合课程所学的原理,理解了编译器的词法分析,语法分析,中间代码生成的过程,也学习了相关工具的使用并进行了实践,了解了编译器工作的每一个部分的原理和相互之间的配合。 -### 同学交流 (可选) +但感觉还是没有融汇贯通,还是有很多需要学的,如果投入的时间能再多一些,感觉能学到更多。 -本次实验和哪些同学(记录同学学号)交流了哪一部分信息 diff --git a/image-1.png b/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d694e9e069d834fc73b1792688b84a032f5842 Binary files /dev/null and b/image-1.png differ diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..a34ac7a2127f6c1e10721173d11661ff19d1e79d Binary files /dev/null and b/image.png differ