Lab4 实验报告
一、实验要求
阅读cminus-f 的语义规则
成为语言律师,我们将按照语义实现程度进行评分阅读实验框架,理解如何使用框架以及注意事项
修改
src/cminusfc/cminusf_builder.cpp
来实现自动 IR 产生的算法,使得它能正确编译任何合法的 cminus-f 程序在
report.md
中解释你们的设计,遇到的困难和解决方案
二、实验难点
在执行
make install
指令的时候,权限不够:解决方法:进入 root 页面,即可获得权限
输入指令
su root
,随后输入密码,即可提高权限。如果是 WSL,则需要创建一个密码:
输入指令
sudo passwd
,随后输入 Windows 密码,正确之后,即可为 Ubuntu 创建一个超级用户。随后,再输入指令
su root
,输入之前设置的密码,即可提高权限。熟悉的段错误:
解决方法:
实际上问题特别多,经过找 bug,调用 LOG 反复测试,找出了大量的 bug。
有涉及到函数引用、有针对表达式语义翻译的,修修改改,解决了大量 bug。
这里贴出其中针对这个问题的 bug:
这种写法下,在 if 语句内的仅有
node.term->accept(*this);
,而没有return;
。因此,导致了没法正确地退出,而导致在无后续的情况下,仍旧对后续进行操作(即对空指针进行访问处理等操作),从而导致了违规访问(即段错误)。需要将语句用大括号括起来,即:
这样才能正确
return
。
类型转换
在生成代码时,许多语义制导都需要考虑数值的类型,需要对不符合要求的数值进行类型转换(例如,赋值语句中,浮点数赋值给整数)。
下图是简单的样例(具体看我编写的代码):
var,即变量结点
这个结点的处理,不仅要考虑
a[]
的情况(即expression
为空),还要考虑指针指向的是数组的情况(即int* a[10]
)。代码非常复杂,还很容易出错。下图为针对数组指针的问题,其他详见我编写的代码:
附上数组指针的问题(详见上一个实验):
请给出
IR.md
中提到的两种getelementptr用法的区别,并稍加解释:
%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
%2 = getelementptr i32, i32* %1 i32 %0
对于前一种,指针的类型为
[10 x i32]*
,代码中的第一个i32 0
是指,对于指针所指向的[10 x i32]*
这一数组([10 x i32][]
),偏移量为0
,即就是当前指向的地址(若不为0
,例如为5
,则其指向的地址是[10 x i32][5]
,也就是往后偏移5
个单位的[10 x i32]
);而第二个i32 %0
,则是表示在[10 x i32]
这一数组中,偏移量为%0
,即指向的地址是[%0]
。因此,若存在某二维数组,形如
i32 a[][10]
,则getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
取出的是a[0][%0]
;若存在某三维数组,形如i32 a[][20][10]
,则getelementptr [20 x [10 x i32]], [20 x [10 x i32]]* %1, i32 0, i32 0, i32 %0
取出的是a[0][0][%0]
;同理,多维数组以此类推。对于一维数组
i32 a[10]
,则可通过置第一个偏移参数为0
,置第二个偏移参数为所需要的%0
,利用[10 x i32]*
型的指针,取出a[%0]
(例如第一行代码所示)。而对于后一种,指针类型为
i32*
,即可直接对应i32 a[]
的数组,通过设置偏移量参数为%0
,即可直接取出a[%0]
中的值。因此,这两种
getelementptr
实际上是针对不同的指针类型[10 x i32]*
和i32*
,同样取出a[%0]
的方法。此外,这两种的用法实际上能算作是同一种用法,区别是在于前者是
[10 x i32]*
型,指针所指向的地址仍旧可以视为指针,从而除指针自身以[10 x i32]
为单位的偏移外,还有偏移单位为i32
的偏移;而后者是i32*
,仅存在i32
这一单位的偏移。而且,这种不仅适用于数组的寻址,也适用于结构体、向量等数据组织方式的寻址,方法和数组的类似。
各结点中处理需要相互传递的信息
这些信息我用全局变量进行存储,以便于各函数处理时能相互传递信息:
三、实验设计
从实验说明中知道,要编写的代码是
cminusf_builder.cpp
,其内含各个 visit 函数,我们需要补全这些 visit 的函数。具体思考过程我就不在这里写了,属实一把辛酸泪。这次几乎没有什么可以参考的样例代码(
打印抽象语法树的算法
的代码,真心参考价值不多,虽然的确大致提供了一些思路,但是真的和我们需要写的代码有“亿点点”差距,带点“女娲补天”的感觉了)。在写代码之前,要好好看看
打印抽象语法树的算法
的代码(虽然参考价值不多,但其中的 visit 的代码还是很有启发性的,需要仔细阅读),掌握 logging 工具(虽然掌握了之后还是特别难 debug),仔细看 Light IR 文档(主要看 C++ APIs 部分),仔细看cminus-f 的语法与语义
(这个特别重要)。
宏定义和一些全局变量
// use these macros to get constant value #define CONST_INT(num) \ ConstantInt::get(num, module.get()) /* 增加一个有关整型的宏 */ #define CONST_FP(num) \ ConstantFP::get((float)num, module.get()) #define CONST_ZERO(type) \ ConstantZero::get(type, module.get()) /* 此处要修改为type */ #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() /* 指针类型判断 */ // You can define global variables here // to store state Value* Res; /* 存储返回的结果 */ Value* arg; /* 存储参数指针,用于Param的处理 */ bool need_as_address = false; /* 标志是返回值还是返回地址 */
Program, 程序
program → declaration-list \text{program} \rightarrow \text{declaration-list} program→declaration-list
/* Program, 程序, program->declaration-list */ void CminusfBuilder::visit(ASTProgram &node) { for (auto decl : node.declarations) /* 遍历declaration-list */ decl->accept(*this); /* 处理每一个declaration */ }
Num,数值
/* Num,数值 */ void CminusfBuilder::visit(ASTNum &node) { if (node.type == TYPE_INT) /* 若为整型 */ Res = ConstantInt::get(node.i_val, module.get()); /* 获取结点中存储的整型数值 */ else if (node.type == TYPE_FLOAT) /* 若为浮点型 */ Res = ConstantFP::get(node.f_val, module.get()); /* 获取结点中存储的浮点型数值 */ }
Var-Declaration, 变量声明
var-declaration → type-specifier ID ; ∣ type-specifier ID [ INTEGER ] ; \text{var-declaration}\ \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{;}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[}\ \textbf{INTEGER}\ \textbf{]}\ \textbf{;} var-declaration →type-specifier ID ; ∣ type-specifier ID [ INTEGER ] ;
要注意:
全局变量需要初始化为全 0 。
cminus-f
的基础类型只有整型(int)、浮点型(float)和void
。而在变量声明中,只有整型和浮点型可以使用,void
仅用于函数声明。一个
变量声明
定义一个整型或者浮点型的变量,或者一个整型或浮点型的数组变量(这里整型指的是32位有符号整型,浮点数是指32位浮点数)。数组变量在声明时, INTEGER \textbf{INTEGER} INTEGER 应当大于0。
一次只能声明一个变量。
/* Var-Declaration, 变量声明, var-declaration -> type-specifier ID * | type-specifier ID [INTEGER] */ void CminusfBuilder::visit(ASTVarDeclaration &node) { Type* tmpType; /* 类型暂存变量,用于存储变量的类型,用于后续申请空间 */ if (node.type == TYPE_INT) /* 若为整型 */ tmpType = Int32Type; /* 则type为整数类型 */ else if (node.type == TYPE_FLOAT) /* 则为浮点型 */ tmpType = FloatType; /* 则type为浮点类型 */ if (node.num != nullptr) { /* 若为数组类型 */ /* 获取需开辟的对应大小的空间的类型指针 */ 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); /* 将获得变量加入域 */ } }
Fun-Declaration, 函数声明
fun-declaration → type-specifier ID ( params ) compound-stmt \text{fun-declaration} \rightarrow \text{type-specifier}\ \textbf{ID}\ \textbf{(}\ \text{params}\ \textbf{)}\ \text{compound-stmt} fun-declaration→type-specifier ID ( params ) compound-stmt
要注意:
函数声明
包含了返回类型,标识符,由逗号分隔的形参
列表,还有一个复合语句
。当函数的返回类型是
void
时,函数不返回任何值。函数的参数可以是
void
,也可以是一个列表。当函数的形参
是void
时,调用该函数时不用传入任何参数。
形参
中跟着中括号代表数组参数,它们可以有不同长度。整型参数通过值来传入函数(pass by value),而数组参数通过引用来传入函数(pass by reference,即指针)。
函数的
形参
拥有和函数声明
的复合语句
相同的作用域,并且每次函数调用都会产生一组独立内存的参数。(和C语言一致)函数可以递归调用。
/* Fun-Declaration, 函数声明, fun-declaration -> type-specifier ID ( params ) compound-stmt */ void CminusfBuilder::visit(ASTFunDeclaration &node) { Type* retType; /* 返回类型 */ /* 根据不同的返回类型,设置retType */ if (node.type == TYPE_INT) { retType = Int32Type; } if (node.type == TYPE_FLOAT) { retType = FloatType; } if (node.type == TYPE_VOID) { retType = Type::get_void_type(module.get()); } /* 根据函数声明,构造形参列表(此处的形参即参数的类型) */ std::vector<Type*> 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); /* 函数结构 */ 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<Value*> 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 */ 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.)); else builder->create_ret(CONST_INT(0)); } scope.exit(); /* 退出此函数作用域 */ }
Param, 参数
param → type-specifier ID ∣ type-specifier ID [] \text{param} \rightarrow \text{type-specifier}\ \textbf{ID}\ |\ \text{type-specifier}\ \textbf{ID}\ \textbf{[]} param→type-specifier ID ∣ type-specifier ID []
/* Param, 参数 */ void CminusfBuilder::visit(ASTParam &node) { Value* paramAlloca; /* 该参数的存储空间 */ 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到开辟的存储空间中 */ scope.push(node.id, paramAlloca); /* 将参数push到域中 */ }
CompoundStmt, 函数体语句
compound-stmt → { local-declarations statement-list } \text{compound-stmt} \rightarrow \textbf{\{}\ \text{local-declarations}\ \text{statement-list} \textbf{\}} compound-stmt→{ local-declarations statement-list}
/* CompoundStmt, 函数体语句, compound-stmt -> {local-declarations statement-list} */ void CminusfBuilder::visit(ASTCompoundStmt &node) { scope.enter(); /* 进入函数体内的作用域 */ for (auto local_declaration : node.local_declarations) /* 遍历 */ local_declaration->accept(*this); /* 处理每一个局部声明 */ for (auto statement : node.statement_list) /* 遍历 */ statement->accept(*this); /* 处理每一个语句 */ scope.exit(); /* 退出作用域 */ }
ExpressionStmt, 表达式语句
expression-stmt → expression ; ∣ ; \text{expression-stmt} \rightarrow \text{expression}\ \textbf{;}\ |\ \textbf{;} expression-stmt→expression ; ∣ ;
/* ExpressionStmt, 表达式语句, expression-stmt -> expression; * | ; */ void CminusfBuilder::visit(ASTExpressionStmt &node) { /* */ if (node.expression != nullptr) /* 若对应表达式存在 */ node.expression->accept(*this); /* 则处理该表达式 */ }
SelectionStmt, if 语句
selection-stmt → if ( expression ) statement ∣ if ( expression ) statement else statement \begin{aligned}\text{selection-stmt} \rightarrow\ &\textbf{if}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement}\\ &|\ \textbf{if}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement}\ \textbf{else}\ \text{statement}\end{aligned} selection-stmt→ if ( expression ) statement∣ if ( expression ) statement else statement
注意:
if
语句中的表达式
将被求值,若结果的值等于0,则第二个语句
执行(如果存在的话),否则第一个语句
会执行。为了避免歧义, else \textbf{else} else将会匹配最近的 if \textbf{if} if
/* SelectionStmt, if语句, selection-stmt -> if (expression) statement * | if (expression) statement else statement */ void CminusfBuilder::visit(ASTSelectionStmt &node) { auto function = builder->get_insert_block()->get_parent(); /* 获得当前所对应的函数 */ node.expression->accept(*this); /* 处理条件判断对应的表达式,得到返回值存到expression中 */ auto resType = Res->get_type(); /* 获取表达式得到的结果类型 */ Value* TrueFalse; /* 存储if判断的结果 */ if (resType->is_integer_type()) { /* 若结果为整型,则针对整型进行处理(bool类型视为整型) */ auto intType = Int32Type; TrueFalse = builder->create_icmp_gt(Res, CONST_ZERO(intType)); /* 大于0视为true */ } else if (resType->is_float_type()) { /* 若结果为浮点型,则针对浮点数进行处理 */ auto floatType = FloatType; TrueFalse = builder->create_fcmp_gt(Res, CONST_ZERO(floatType));/* 大于0视为true */ } if (node.else_statement != nullptr) { /* 若存在else语句 */ auto trueBB = BasicBlock::create(module.get(), "true", function); /* 创建符合条件块 */ auto falseBB = BasicBlock::create(module.get(), "false", function); /* 创建else块 */ builder->create_cond_br(TrueFalse, trueBB, falseBB); /* 设置跳转语句 */ builder->set_insert_point(trueBB); /* 符合if条件的块 */ node.if_statement->accept(*this); /* 处理符合条件后要执行的语句 */ auto curTrueBB = builder->get_insert_block(); /* 将块加入 */ builder->set_insert_point(falseBB); /* else的块 */ node.else_statement->accept(*this); /* 处理else语句 */ auto curFalseBB = builder->get_insert_block(); /* 将块加入 */ /* 处理返回,避免跳转到对应块后无return */ auto trueTerm = builder->get_insert_block()->get_terminator(); /* 判断true语句中是否存在ret语句 */ auto falseTerm = builder->get_insert_block()->get_terminator(); /* else语句中是否存在ret语句 */ BasicBlock* retBB; if (trueTerm == nullptr || falseTerm == nullptr) { /* 若有一方不存在return语句,则需要创建返回块 */ retBB = BasicBlock::create(module.get(), "ret", function); /* 创建return块 */ builder->set_insert_point(retBB); /* return块(即后续语句) */ } if (trueTerm == nullptr) { /* 若符号条件后要执行的语句中不存在return */ builder->set_insert_point(curTrueBB); /* 则设置跳转 */ builder->create_br(retBB); /* 跳转到刚刚设置的return块 */ } if (falseTerm == nullptr) { /* 若else语句中不存在return */ builder->set_insert_point(curFalseBB); /* 则设置跳转 */ builder->create_br(retBB); /* 跳转到刚刚设置的return块 */ } } else { /* 若不存在else语句,则只需要设置true语句块和后续语句块即可 */ auto trueBB = BasicBlock::create(module.get(), "true", function); /* true语句块 */ auto retBB = BasicBlock::create(module.get(), "ret", function); /* 后续语句块 */ builder->create_cond_br(TrueFalse, 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块(即后续语句) */ } }
IterationStmt, while 语句
iteration-stmt → while ( expression ) statement \text{iteration-stmt} \rightarrow \textbf{while}\ \textbf{(}\ \text{expression}\ \textbf{)}\ \text{statement} iteration-stmt→while ( expression ) statement
注意:
while
语句是cminus-f
中唯一的迭代语句
。它执行时,会不断对表达式
进行求值,并且在对表达式
的求值结果等于 0 前,循环执行执下面的语句
/* IterationStmt, while语句, iteration-stmt -> while (expression) statement */ void CminusfBuilder::visit(ASTIterationStmt &node) { 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); /* 创建后续语句块 */ builder->create_br(conditionBB); /* 跳转到条件判断块 */ builder->set_insert_point(conditionBB); /* 条件判断块 */ node.expression->accept(*this); /* 处理条件判断对应的表达式,得到返回值存到expression中 */ auto resType = Res->get_type(); /* 获取表达式得到的结果类型 */ Value* TrueFalse; /* 存储if判断的结果 */ if (resType->is_integer_type()) { /* 若结果为整型,则针对整型进行处理(bool类型视为整型) */ auto intType = Int32Type; TrueFalse = builder->create_icmp_gt(Res, CONST_ZERO(intType)); /* 大于0视为true */ } else if (resType->is_float_type()) { /* 若结果为浮点型,则针对浮点数进行处理 */ auto floatType = FloatType; TrueFalse = builder->create_fcmp_gt(Res, CONST_ZERO(floatType));/* 大于0视为true */ } builder->create_cond_br(TrueFalse, 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块(即后续语句) */ }
ReturnStmt, 返回语句
return-stmt → return ; ∣ return expression ; \text{return-stmt} \rightarrow \textbf{return}\ \textbf{;}\ |\ \textbf{return}\ \text{expression}\ \textbf{;} return-stmt→return ; ∣ return expression ;
注意:
return
语句可以返回值,也可以不返回值。未声明为 void \textbf{void} void类型的函数必须返回和函数返回类型相同的值
/* ReturnStmt, 返回语句, return-stmt -> return; * | return expression; */ void CminusfBuilder::visit(ASTReturnStmt &node) { auto function = builder->get_insert_block()->get_parent(); /* 获得当前所对应的函数 */ auto retType = function->get_return_type(); /* 获取返回类型 */ if (retType->is_void_type()) { /* 如果是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的结果进行返回 */ }
Var, 变量引用
var → ID ∣ ID [ expression ] \text{var} \rightarrow \textbf{ID}\ |\ \textbf{ID}\ \textbf{[}\ \text{expression} \textbf{]} var→ID ∣ ID [ expression]
注意:
var
可以是一个整型变量、浮点变量,或者一个取了下标的数组变量。数组的下标值是整型,它的值是表达式计算结果或结果进行类型转换后的整型值
一个负的下标会导致程序终止,需要调用框架中的内置函数
neg_idx_except
(该内部函数会主动退出程序,只需要调用该函数即可),但是对于上界并不做检查。/* Var, 变量引用, var -> ID * | ID [expression] */ void CminusfBuilder::visit(ASTVar &node) { auto var = scope.find(node.id); /* 从域中取出对应变量 */ bool should_return_lvalue = need_as_address; /* 判断是否需要返回地址(即是否由赋值语句调用) */ need_as_address = false; /* 重置全局变量need_as_address */ Value* index = CONST_INT(0); /* 初始化index */ if (node.expression != nullptr) { /* 若有expression */ 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); /* 设置跳转语句 */ builder->set_insert_point(failBB); /* fail块,即下标为负数 */ auto fail = scope.find("neg_idx_except"); /* 取出neg_idx_except函数 */ builder->create_call(static_cast<Function*>(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 (should_return_lvalue) { /* 若要返回值 */ Res = var; /* 则返回var对应的地址 */ need_as_address = false; /* 并重置全局变量need_as_address */ } else Res = builder->create_load(var);/* 否则则进行load */ return; } /* 处理无expression的情况 */ if (should_return_lvalue) { /* 若要返回值 */ Res = var; /* 则返回var对应的地址 */ need_as_address = false;/* 并重置全局变量need_as_address */ } else { /* 否则 */ 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 */ } }
AssignExpression, 赋值语句
注意:
赋值语义为:先找到
var
代表的变量地址(如果是数组,需要先对下标表达式求值),然后对右侧的表达式进行求值,求值结果将在转换成变量类型后存储在先前找到的地址中。同时,存储在var
中的值将作为赋值表达式的求值结果。在
C
中,赋值对象(即var
)必须是左值,而左值可以通过多种方式获得。cminus-f
中,唯一的左值就是通过var
的语法得到的,因此cminus-f
通过语法限制了var
为左值,而不是像C
中一样通过类型检查,这也是为什么cminus-f
中不允许进行指针算数。/* AssignExpression, 赋值语句, var = expression */ void CminusfBuilder::visit(ASTAssignExpression &node) { need_as_address = true; /* 设置need_as_address,表示需要返回值 */ node.var->accept(*this); /* 处理左var */ auto var = Res; /* 获取地址 */ node.expression->accept(*this); /* 处理右expression */ auto res = Res; /* 获得结果 */ auto varType = var->get_type()->get_pointer_element_type(); /* 获取var的类型 */ /* 若赋值语句左右类型不匹配,则进行匹配 */ if (varType == FloatType && checkInt(res)) res = builder->create_sitofp(res, FloatType); if (varType == Int32Type && checkFloat(res)) res = builder->create_fptosi(res, Int32Type); builder->create_store(res, var);/* 进行赋值 */ }
SimpleExpression, 比较表达式
simple-expression → additive-expression relop additive-expression ∣ additive-expression \text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression} simple-expression→additive-expression relop additive-expression ∣ additive-expression
注意:
一个
简单表达式
是一个加法表达式
或者两个加法表达式
的关系运算。当它是加法表达式
时,它的值就是加法表达式
的值。而当它是关系运算时,如果关系运算结果为真则值为整型值 1,反之则值为整型值 0。/* SimpleExpression, 比较表达式, simple-expression -> additive-expression relop additive-expression * | additive-expression */ void CminusfBuilder::visit(ASTSimpleExpression &node) { 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进行处理 */ 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); /* 将结果作为整数保存(可作为判断语句中的判断条件) */ }
AdditiveExpression, 加法表达式
additive-expression → additive-expression addop term ∣ term \text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term} additive-expression→additive-expression addop term ∣ term
注意:
浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
/* AdditiveExpression, 加法表达式, additive-expression -> additive-expression addop term * | term */ void CminusfBuilder::visit(ASTAdditiveExpression &node) { 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; } } }
Term, 乘除法语句
term → term mulop factor ∣ factor \text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor} term→term mulop factor ∣ factor
注意:
浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
/* Term, 乘除法语句, Term -> term mulop factor * | factor */ void CminusfBuilder::visit(ASTTerm &node) { 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; } } }
Call, 函数调用
call → ID ( args ) \text{call} \rightarrow \textbf{ID}\ \textbf{(}\ \text{args} \textbf{)} call→ID ( args)
注意:
函数调用
由一个函数的标识符
与一组括号包围的实参
组成。实参
可以为空,也可以是由逗号分隔的的表达式
组成的列表,这些表达式代表着函数调用时,传给形参
的值。函数调用时
的实参
数量和类型必须与函数声明
中的形参
一致,必要时需要进行类型转换。/* Call, 函数调用, call -> ID (args) */ void CminusfBuilder::visit(ASTCall &node) { auto function = static_cast<Function*>(scope.find(node.id)); /* 获取需要调用的函数 */ auto paramType = function->get_function_type()->param_begin(); /* 获取其参数类型 */ std::vector<Value*> 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*>(function), args); /* 创建函数调用 */ }
四、实验结果验证
先要获取权限(以确保共享库能复制到 usr 下)
输入指令
su root
,输入密码,提高权限:(如果是 WSL,先看之前“实验难点”中所写的)
编译
输入指令
mkdir build && cd build
:再输入指令
cmake .. -DLLVM_DIR=/path/to/your/llvm/install/lib/cmake/llvm/
:再输入指令
make -j
:最后输入指令
make install
:(若复制文件失败,提示缺少权限,则先去提高权限)
可以看到,编译成功。
运行自动测试程序,测试样例
在目录
tests/lab4
下输入指令./lab4_test.py
:可以看到,通过所有样例,运行正确。
五、实验反馈
属实,有“亿点点”难,主要给的启发信息有点少。(虽然有 lab3 的铺垫,但我一开始也完全不知道这个代码该怎么补,花了一下午加晚上认真看了几个参考文档,才有了一些思路)
设计思路也都写在了注释中(注释非常详细)。
写了两天,终归是做完了。