编译原理实验报告
实验四:RISC-V 目标代码生成 2025 年 4 月 28 日
姓名:俞仲炜 学号:3220104929
功能清单
本次实验使用 Venus 模拟器
ASM 相关类定义
在 codegen/asm.hpp
中,每个类负责存储一段代码逻辑信息,并打印出对应的 RISCV 汇编代码片段。定义的具体实现重复且相对容易,故不在此赘述。
指令选择
在 codegen/inst_selector
中,实现了指令选择的相关函数,与翻译 AST 为 IR 类似,针对各个的 IR 结点分别进行处理,将代码逻辑信息从 IR 结点类转入 ASM 结点类进行存储。
具体实现均按图索骥地实现即可,此处仅简述部分需要注意之处的实现。
在处理函数调用前,存在若干个实参设置语句,需要检查参数数量,若超过 8 个则应将额外的参数通过栈传递。我设置了一个 vector 用于跟踪存储一次调用所用到的所有参数,然后在处理 CALL 时一次性将所有参数处理掉,即 selectArg
函数仅负责接收语句,将其存储到相应的数据结构,其实际职能合并到了 selectCall
中。
此外,RV32I 仅支持直接获取 -2048~2047 的立即数,故在生成内存访问指令时,需要检查偏移量大小,若超过可直接获取的范围,需要特殊处理。这里有多种实现方式,我采用了最直接的分步获取并累积的方式。
寄存器分配
在 codegen/reg_allocator.cpp
中,我实现了朴素的寄存器分配逻辑,将所有的临时变量存储在了栈上,然后用一个 vector 存储各数据的偏移量,借此访问其值。具体实现逻辑相当简单,故不在此赘述。
由于在中间代码生成部分实现了简单的临时变量分配方式,中间代码的临时变量数量极少,反映过来的栈空间占用情况也十分局限。但是这种方法最大的问题在于需要进行频繁的内存访问,导致运行时间严重增长,还导致内存访问指令泛滥,运行步数膨胀。
栈帧管理
我实现了简单的栈帧管理机制,在函数调用前后更新 sp
和 fp
,并进行上下文的保存与恢复。具体实现相对简单,故不在此赘述。
亮点摘选
长距离条件跳转
RV32I 的条件跳转指令包含 12 bit 的立即数,仅支持-4096 到 +4094 范围内的跳转,而由于我采用的是朴素寄存器分配,添加了大量的内存访问指令,于是出现了超出跳转范围的情况。
解决方法很简单,无条件跳转指令有 20 bit 立即数,借助其跳转即可。我将原指令的条件反转,后面添加一条无条件跳转指令跳转至原目标地址,然后再跟一个新的 label,将其设置为条件跳转指令的目标地址,如此就解决了长距离条件跳转问题。
AI 使用情况
- debug 时借助 AI 了解报错信息的意思及其引发原因,例如
- 有三个测试点借助了 AI 帮忙找出生成的汇编代码的错误,有两个正确找出了错误之处,分别为
add.sy
,array_init2.sy
,还有一个 AI 没找出问题,为array_parameter.sy
当时还没用 Venus,用的 Qemu,出现了无法提供输入且没有任何输出的情况,导致无法进行人工调试,所以虽然代码简单但还是选择问 AI 了
后面问了助教,改用 Venus ,然后都是自己 debug 的了