本次任务要求你完成从 LLVM IR 到目标机器汇编的翻译过程。当前实验默认目标平台为 RV64(riscv64-unknown-linux-gnu)。
-
启用复活
clang 以
--target=riscv64-unknown-linux-gnu -march=rv64gc -mabi=lp64d -S -emit-llvm生成的 LLVM IR(.ll文件)。 -
禁用复活
任务 4 的输出,即优化后的 LLVM IR(
.ll文件)。
文本格式的 RV64 汇编(.s 文件),要求其语义与输入 LLVM IR 一致,并能够与实验提供的运行时库一起链接运行。
评测时会先生成标准答案,再将你的 output.s 与 libsysy.a 一起链接为 RV64 可执行文件,并通过 qemu-riscv64-static 运行。程序的标准输出和返回值必须与标准答案一致。
评测后,每个测例目录下会生成以下文件:
-
score.txt评测的得分详情。
-
answer.ll评测使用的 LLVM IR 输入。启用复活时由 clang 生成;禁用复活时由前置任务链路生成。
-
output.ll仅在禁用复活时出现,即任务 4 输出的 LLVM IR。
-
answer.s标准答案汇编。
-
output.s你的答案(如果 task5 程序正常运行)。
-
answer.exe和output.exe分别由
riscv64-linux-gnu-g++ --static链接得到的可执行文件。 -
answer.compile和output.compile链接
answer.s与output.s过程中的输出。 -
answer.out和answer.err运行
answer.exe时的标准输出和标准错误输出,其中answer.err末尾包含程序返回值。 -
output.out和output.err运行
output.exe时的标准输出和标准错误输出,其中output.err末尾包含程序返回值。
基础代码给出了一个简化的 RV64 后端框架,整体流程如下:
LLVM IR -> MIR(虚拟寄存器) -> 线性扫描寄存器分配 -> RV64 汇编
主要文件包括:
-
main.cpp读取并校验输入 LLVM IR,设置 target triple,然后调用后端主流程。
-
EmitMIR.h和EmitMIR.cpp将 LLVM IR 降低为简化的中间表示
VInst。这一层仍然使用虚拟寄存器,便于将指令生成与寄存器分配分开实现。 -
AllocReg.h和AllocReg.cpp使用线性扫描算法为虚拟寄存器分配物理寄存器;分配失败的值会落到栈上的 spill slot 中。
-
EmitAsm.h和EmitAsm.cpp根据寄存器分配结果输出最终 RV64 汇编,并处理函数序言/结语、访存、调用和分支等细节。
基础代码已经搭好了完整流程,并能覆盖一部分基础测例。你可以在此基础上继续补齐更多 LLVM IR 指令和边界情况。
-
先保证正确性,再考虑代码质量
本任务只要求输出汇编的语义正确,不要求与 clang 的指令序列完全一致。先让程序能正确运行,再考虑代码是否更紧凑。
-
先做带虚拟寄存器的指令生成
建议先把 LLVM IR 翻译为带虚拟寄存器的 MIR,再单独做寄存器分配。这样结构更清晰,也更符合本实验当前的代码组织方式。
-
优先处理调用约定和栈帧
需要特别注意
a0~a7、ra、sp、s0等寄存器的职责,以及局部变量、spill slot、保存寄存器在栈上的布局。 -
注意 RV64 上的数据宽度
指针和栈槽通常按 8 字节处理;而
i32的 load/store、比较与返回值传递仍然要保持原语义,不能简单全部当作 64 位整数处理。 -
控制流通常需要按基本块处理
条件分支、循环、短路求值和
phi节点都需要结合基本块边来翻译。一个常见做法是在前驱边上插入额外标签块来完成phi搬运。 -
不必一开始追求完整后端
当前基础代码是面向教学的简化实现。你可以先覆盖课程给出的基础测例,再逐步补齐更多 IR 指令和特殊情况。