Skip to content

编译原理实验报告

:material-circle-edit-outline: 约 966 个字 :material-clock-time-two-outline: 预计阅读时间 3 分钟

实验四: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 存储各数据的偏移量,借此访问其值。具体实现逻辑相当简单,故不在此赘述。

由于在中间代码生成部分实现了简单的临时变量分配方式,中间代码的临时变量数量极少,反映过来的栈空间占用情况也十分局限。但是这种方法最大的问题在于需要进行频繁的内存访问,导致运行时间严重增长,还导致内存访问指令泛滥,运行步数膨胀。

栈帧管理

我实现了简单的栈帧管理机制,在函数调用前后更新 spfp,并进行上下文的保存与恢复。具体实现相对简单,故不在此赘述。

亮点摘选

长距离条件跳转

RV32I 的条件跳转指令包含 12 bit 的立即数,仅支持-4096 到 +4094 范围内的跳转,而由于我采用的是朴素寄存器分配,添加了大量的内存访问指令,于是出现了超出跳转范围的情况。

解决方法很简单,无条件跳转指令有 20 bit 立即数,借助其跳转即可。我将原指令的条件反转,后面添加一条无条件跳转指令跳转至原目标地址,然后再跟一个新的 label,将其设置为条件跳转指令的目标地址,如此就解决了长距离条件跳转问题。

AI 使用情况

  1. debug 时借助 AI 了解报错信息的意思及其引发原因,例如

image-20250428134927601

  1. 有三个测试点借助了 AI 帮忙找出生成的汇编代码的错误,有两个正确找出了错误之处,分别为 add.syarray_init2.sy ,还有一个 AI 没找出问题,为 array_parameter.sy

当时还没用 Venus,用的 Qemu,出现了无法提供输入且没有任何输出的情况,导致无法进行人工调试,所以虽然代码简单但还是选择问 AI 了

后面问了助教,改用 Venus ,然后都是自己 debug 的了

image-20250428170951743