0. 前言
本次实验和 Lab3 一样,需要使用 LightIR
框架自动产生 cminus-f
语言的LLVM IR。
经过 Lab3 的练手,相信大家已经掌握了 LightIR 的使用,并且对于 LLVM IR 也有了一定理解。在本次实验中,我们要使用访问者模式来实现 IR 自动生成。 对于产生的IR, 我们可以调用 clang 生成可执行文件,这样一个初级的 cminus-f 编译器就完成啦!
主要工作
- 阅读cminus-f 的语义规则
成为语言律师,我们将按照语义实现程度进行评分 - 阅读LightIR 核心类介绍
- 阅读实验框架,理解如何使用框架以及注意事项
- 修改
src/cminusfc/cminusf_builder.cpp
来实现自动 IR 产生的算法,使得它能正确编译任何合法的 cminus-f 程序 - 在
report.md
中解释你们的设计,遇到的困难和解决方案
1. 实验框架
本次实验使用了由C++编写的 LightIR 来生成 LLVM IR。为了便于大家进行实验,该框架自动完成了语法树到 C++ 上的抽象语法树的转换。 我们可以使用访问者模式来设计抽象语法树 中的算法。大家可以参考打印抽象语法树的算法, 以及运行 test_ast
来理解访问者模式下算法的执行流程。
在include/cminusf_builder.hpp
中,我还定义了一个用于存储作用域的类Scope
。它的作用是辅助我们在遍历语法树时,管理不同作用域中的变量。它提供了以下接口:
// 进入一个新的作用域
void enter();
// 退出一个作用域
void exit();
// 往当前作用域插入新的名字->值映射
bool push(std::string name, Value *val);
// 根据名字,寻找到值
Value* find(std::string name);
// 判断当前是否在全局作用域内
bool in_global();
你们需要根据语义合理调用enter
与exit
,并且在变量声明和使用时正确调用push
与find
。在类CminusfBuilder
中,有一个Scope
类型的成员变量scope
,它在初始化时已经将input
、output
等函数加入了作用域中。因此,你们在进行名字查找时不需要顾虑是否需要对特殊函数进行特殊操作。
2. 运行与调试
运行 cminusfc
mkdir build && cd build
cmake .. -DLLVM_DIR=/path/to/your/llvm/install/lib/cmake/llvm/
make -j
# 安装它以便于链接 libcminus_io.a
make install
编译后会产生 cminusfc
程序,它能将cminus文件输出为LLVM IR,也可以利用clang将IR编译成二进制。程序逻辑写在src/cminusfc/cminusfc.cpp
中。
当需要对 .cminus
文件测试时,可以这样使用:
# 假设 cminusfc 的路径在你的$PATH中
# 利用构建好的 Module 生成 test.ll
# 注意,如果调用了外部函数,如 input, output 等,则无法使用lli运行
cminusfc test.cminus -emit-llvm
# 假设libcminus_io.a的路径在$LD_LIBRARY_PATH中,clang的路径在$PATH中
# 1. 利用构建好的 Module 生成 test.ll
# 2. 调用 clang 来编译 IR 并链接上静态链接库 libcminus_io.a,生成二进制文件 test
cminusfc test.cminus
自动测试
助教贴心地为大家准备了自动测试脚本,它在 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============
通过修改脚本,还可以方便地添加自定义测试用例
请注意助教提供的测试样例仅涵盖了最基础的测试情况,请自行设计测试样例进行测试。
logging
logging 是帮助大家打印调试信息的工具,如有需求可以阅读文档后进行使用
建议
- 比较你们编写的编译器产生的 IR 和 clang 产生的IR来找出可能的问题或发现新的思路
- 使用 logging 工具来打印调试信息
- 使用 GDB 等软件进行单步调试来检查错误的原因
- 合理分工
3. 提交要求
目录结构
.
├── CMakeLists.txt
├── Documentations
│ ├── ...
│ ├── common
│ | ├── LightIR.md <- LightIR 相关文档
│ | ├── logging.md <- logging 工具相关文档
│ | └── cminusf.md <- cminus-f 的语法和语义文档
│ └── lab4
│ └── README.md <- lab4 实验文档说明(你在这里)
├── include <- 实验所需的头文件
│ ├── ...
│ ├── lightir/*
│ ├── cminusf_builder.hpp
| └── ast.hpp
├── Reports
│ ├── ...
│ └── lab4
│ └── report.md <- lab4 所需提交的实验报告,请详细说明你们的设计(需要上交)
├── src
│ ├── ...
│ └── cminusfc
│ ├── cminusfc.cpp <- cminusfc 的主程序文件
│ └── cminusf_builder.cpp <- lab4 需要修改的文件,你们要在该文件中用访问者模式实现自动 IR 生成的算法(需要上交)
└── tests
├── ...
└── lab4
├── testcases <- 助教提供的测试样例
└── lab4_test.py <- 助教提供的测试脚本
提交要求和评分标准
-
提交要求
- 实验部分:
- 需要填补
./src/cminusfc/cminusf_builder.cpp
- 需要在
./Reports/lab4/
目录下撰写实验报告 - 本次实验收取
./src/cminusfc/cminusf_builder.cpp
文件和./Reports/lab4
目录
- 需要填补
- 实验部分:
实验报告
实验要求
1、阅读cminus-f 的语义规则
2、阅读LightIR 核心类介绍
3、阅读实验框架,理解如何使用框架以及注意事项
4、修改 src/cminusfc/cminusf_builder.cpp 来实现自动 IR 产生的算法,使得它能正确编译任何合法的 cminus-f 程序
实验难点
1、lab4运行的时候报错了
可喜的是发现和lab2卡了很久的错误其实是同个道理,就是windows文件和linux文件不通用,用dos2unix转换一下就好了
复盘的时候才发现这个地方不应该改的,改了之后就成了下面这个报错情况
2、最后结果检验的时候有部分错了
找了好久好久好久,最后发现是/tests/lab4/testcases/有问题,心情复杂T T最后就是找回之前的然后替换掉。
实验设计
要编写的代码是cminusf_builder.cpp
,其内含各个 visit 函数,我们需要补全这些 visit 的函数。
要掌握 logging 工具,仔细看 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
/* 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 ] ;
要注意:
全局变量需要初始化为全 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
要注意:
函数声明包含了返回类型,标识符,由逗号分隔的形参列表,还有一个复合语句。
当函数的返回类型是 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 []
/* 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 }
/* 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 ; ∣ ;
/* 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
注意:
if语句中的表达式将被求值,若结果的值等于0,则第二个语句执行(如果存在的话),否则第一个语句会执行。
为了避免歧义,else将会匹配最近的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
注意:
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 ;
注意:
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 ]
注意:
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
注意:
一个简单表达式是一个加法表达式或者两个加法表达式的关系运算。当它是加法表达式时,它的值就是加法表达式的值。而当它是关系运算时,如果关系运算结果为真则值为整型值 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
注意:
浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
/* 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
注意:
浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型
/* 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 )
函数调用由一个函数的标识符与一组括号包围的实参组成。实参可以为空,也可以是由逗号分隔的的表达式组成的列表,这些表达式代表着函数调用时,传给形参的值。函数调用时的实参数量和类型必须与函数声明中的形参一致,必要时需要进行类型转换。
/* 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); /* 创建函数调用 */
}
编译
创建文件夹,进入文件夹,获取管理员权限
cmake .. -DLLVM_DIR=/path/to/your/llvm/install/lib/cmake/llvm/
make -j
make install
运行
实验总结与反馈
1、真的不要轻易修改点开不用动的文件!!最后lab4报错的时候我找了整整一个晚上的bug,最后发现是/tests/lab4/testcases/有问题的时候真的很心累T T
2、编译原理实验终于要结束了!!!好难啊!!!但是感觉学到了很多东西!就是一些纯软件学不到的,都是一些很底层的词法分析器啊语法分析器的从零开始设计
3、lab2相当于让我们熟练语法分析器,lab3就是熟悉LLVM、LightIR、Visitor Pattern等,lab4一下子就把全部集合起来了,好难好难!!而且这次实验时间还特别短。真的非常感谢CSDN博客(没有早八、HNU岳麓山大小姐),我很多地方都是靠着这两个作者的博客来学的,特别是没有早八写的非常详细,帮大忙了。还有很感谢我的舍友们,大家一起熬夜赶实验改bug互帮互助,有时候就会感慨这就是大学宿舍生活的好处之一吧,毕竟如果在家上网课的话可没法这么方便交流。