跳转至

Lab3 实验指导

阶段一:预热实验

仓库目录结构

与预热实验相关的文件如下:

.
├── ...
├── include
│   ├── common
│   └── codegen/*
└── tests
    ├── ...
    └── 3-codegen
        └── warmup
            ├── CMakeLists.txt
            ├── ll_cases          <- 需要翻译的 ll 代码
            └── stu_cpp           <- 学生需要编写的汇编代码手动生成器

实验内容

实验在 tests/3-codegen/warmup/ll_cases/ 目录下提供了六个 .ll 文件。学生需要在 tests/3-codegen/warmup/stu_cpp/ 目录中,依次完成 assign_codegen.cppfloat_codegen.cppglobal_codegen.cppfunction_codegen.cppicmp_codegen.cppfcmp_codegen.cpp 六个 C++ 程序中的 TODO。这六个程序运行后应该能够生成 tests/3-codegen/warmup/ll_cases/ 目录下六个 .ll 文件对应的汇编程序。

编译

$ cd 2024ustc-jianmu-compiler
$ mkdir build
$ cd build
# 使用 cmake 生成 makefile 等文件
$ cmake ..
# 使用 make 进行编译
$ make

如果构建成功,你会在 build 文件夹下找到 stu_assign_codegen, stu_float_codegen 等可执行文件。

运行与测试

Note

在运行和测试之前,你应该确保你完成了后端环境配置

# 在 build 目录下操作
$ ./stu_assign_codegen > assign.s
$ loongarch64-unknown-linux-gnu-gcc -static assign.s -o assign
$ qemu-loongarch64 ./assign
$ echo $?

你可以通过观察原来的 .ll 代码来推断 echo $? 应该返回的正确结果,也可以直接使用 lli 执行 .ll 文件来获取正确结果。

Note

请注意,echo $? 返回的值是 qemu 执行完毕后的退出状态,也就是你的程序的返回值。由于 Linux 系统的限制,这个返回值只包含低 8 位,即范围在 0 到 255 之间。如果你的程序返回值超过了这个范围,那么 echo $? 将只显示该返回值的低 8 位。

阶段二:编译器后端

仓库目录结构

与实验第二阶段相关的文件如下:

.
├── include
│   └── codegen/*                   # 相关头文件
├── src
│   └── codegen
│       └── CodeGen.cpp         <-- 学生需要补全的文件
└── tests
    ├── 3-codegen
    │   └── autogen
    │       ├── eval_lab3.sh    <-- 测评脚本
    │       └── testcases       <-- lab3 第二阶段的测例目录一
    └── testcases_general       <-- lab3 第二阶段的测例目录二

实验内容

补全 src/codegen/CodeGen.cpp 中的 TODO,并按需修改 include/codegen/CodeGen.hpp 等文件,使编译器能够生成正确的汇编代码。

实现约定

出于简化实验的目的,我们只考核最核心的功能点,对如下情况不做要求:

  • phi 指令:本实验不考核对于 phi 指令的处理

  • 函数调用传参:本次实验只考核使用寄存器传参的情况,即对于超多参数的栈上传参不做要求

此外,存在部分 void main() {...} 的样例,其返回值是未定义的,我们要求这样的主函数通过寄存器 $a0 返回 0,以顺利进行测评。

编译

$ cd 2024ustc-jianmu-compiler
$ mkdir build
$ cd build
# 使用 cmake 生成 makefile 等文件
$ cmake ..
# 使用 make 进行编译
$ make
# 安装以链接 libcminus_io.a
$ sudo make install

如果构建成功,你会在 build 文件夹下找到 cminusfc 可执行文件,它能将 .cminus 文件输出为龙芯汇编文件。

Note

为了让 cminusfc 在 $PATH 中,一定要 sudo make install

运行

完成第二阶段,编译器能够将 .cminus 文件翻译成正确的龙芯汇编 .s 文件:

# 假设 cminusfc 的路径在你的 $PATH 中,并且你现在在 test.cminus 文件所在目录中
$ cminusfc test.cminus -S

此时会在同目录下生成同名的汇编文件,在这里即为 test.s

为了运行得到的汇编文件,需要调用龙芯交叉编译器编译链接生成二进制文件 test

# 假设你位于 2024ustc-jianmu-compiler 目录,否则你应该修改下面 src/io/io.c 到具体的路径
$ loongarch64-unknown-linux-gnu-gcc -static test.s src/io/io.c -o test

Note

上面的命令编译了 test.s,并链接了 io.c 中的函数。

然后你可以使用 qemu-loongarch64 运行二进制文件 test

$ qemu-loongarch64 ./test

测试

tests/3-codegen/autogen 目录下,使用我们提供的 eval_lab3.sh 进行本地评测,其接受两个参数

  1. 测试样例目录:考核的两个测例目录分别是 ./testcases../../testcases_general
  2. 评测模式:testdebug 两种模式都能正常测评,后者会额外生成 .ll 文件

脚本运行后,会留下如下文件/目录来辅助你 debug

  • output/ 目录:保存了评测中编译器产生的文件,如汇编文件、IR 文件和二进制文件等
  • log.txt 文件:该文件收录了编译器报错信息及评测的比较信息 (diff 的结果)

出现错误时请查阅相关文件。

在结束测评后,你可以使用 ./cleanup.sh 清空上述文件/目录,以保持仓库的清爽。

以下是评测脚本的使用示例

./testcases 为测例目录,使用评测脚本 test 模式测试(我们使用正确的实现,但是故意在 13-complex.cminus 中制造了语法错误):

$ ./eval_lab3.sh ./testcases test
[info] Start testing, using testcase dir: ./testcases
0-io...OK
1-return...OK
2-calculate...OK
3-output...OK
4-if...OK
5-while...OK
6-array...OK
7-function...OK
8-store...OK
9-fibonacci...OK
10-float...OK
11-floatcall...OK
12-global...OK
13-complex..../eval_lab3.sh: line 65: 40958 Segmentation fault      (core dumped) bash -c "cminusfc -S $case -o $asm_file" >> $LOG 2>&1
CE: cminusfc compiler error

从脚本输出中观察到样例 13-complex 没有通过,原因为 CE: cminusfc compiler error,表示 cminusfc 编译过程报错,打开 log.txt 检查报错信息:

==========./testcases/0-io.cminus==========
1234                            1234
0                               0
...省略多行中间输出
==========./testcases/13-complex.cminus==========
error at line 7 column 5: syntax error

根据输出信息,我们了解到 cminusfc 检测到语法错误而退出,具体为 13-complex.cminus 中第 7 行第 5 列。

直接使用初始代码尝试评测,每个样例都会报错 CE: cminusfc compiler error

使用初始代码评测后的 log 文件
==========./testcases/0-io.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_call
==========./testcases/1-return.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_call
==========./testcases/2-calculate.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/3-output.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_call
==========./testcases/4-if.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/5-while.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/6-array.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/7-function.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/8-store.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/9-fibonacci.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/10-float.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/11-floatcall.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca
==========./testcases/12-global.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_load
==========./testcases/13-complex.cminus==========
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_alloca