Files
nudt-compiler-cpp/doc/Lab3-实验记录.md

120 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lab3 实验记录:指令选择与汇编生成
## 1. 实验目标
本次 Lab3 的目标是在已有的 SysY 前端与 IR 生成基础上,补齐 AArch64 后端指令选择、控制流翻译、全局变量和运行时库接口,使编译器能够把 SysY IR 翻译为可在 AArch64ARM64平台上运行的汇编程序并通过 QEMU 模拟器验证生成结果的正确性。
本次完成工作的重点包括:
- 扩展 MIR 中物理寄存器、指令操作数种类与机器指令集,完整覆盖 AArch64 核心子集。
- 扩展指令选择逻辑(`Lowering.cpp`支持多函数、多基本块、函数调用、浮点数与多维数组GEP地址计算。
- 处理 AArch64 调用约定ABI中参数传递整数/浮点前 8 传参)与栈帧落地细节。
- 解决 AArch64 特有的指令寻址与栈槽大偏移(超出 ldur/stur 范围)的物理寄存器备用搬运机制。
- 补齐 SysY 运行时库(`sylib/sylib.c`)中所有 I/O、时间统计与十六进制浮点输入输出功能。
## 2. 代码改动范围
本次实验主要修改/新增了以下文件:
- `include/mir/MIR.h``src/mir/MIRFunction.cpp``src/mir/MIRInstr.cpp``src/mir/Register.cpp``src/mir/RegAlloc.cpp``src/mir/FrameLowering.cpp`
- `src/mir/Lowering.cpp` (核心指令选择)
- `src/mir/AsmPrinter.cpp` (核心汇编文本打印)
- `sylib/sylib.c` (SysY 运行库)
- `scripts/verify_asm.sh` (自动化编译链接脚本)
- `src/main.cpp` (后端多函数汇编流适配)
- `src/irgen/IRGenExp.cpp` (修复前端常数类型转换缺陷)
- 新增本文档 `doc/Lab3-实验记录.md`
## 3. 完成过程
### 3.1 梳理后端结构与定位边界
阅读了实验文档 `doc/Lab3-指令选择与汇编生成.md`,原有的后端属于“极简演示”:
- 仅支持单函数 `main` 与单基本块。
- 仅支持 `alloca`, `load`, `store`, `add`, `ret` 五种指令。
- 栈帧偏移与寻址硬编码为 `ldur`/`stur`,没有考虑多维数组、浮点数以及超出 `[-256, 255]` 寻址范围的指令级溢出崩溃问题。
### 3.2 解决前置类型转换 bug
在回归测试 `95_float.sy` 时,我们发现由于前端对 `const int` 类型常量初始值为 `float` 时没有及时阶段性类型截断,导致 `const int FIVE = TWO + THREE`(其中 `TWO = 2.9, THREE = 3.2`)的编译期常量求值被错误地计算为 `2.9 + 3.2 = 6.1` 再向下转型为 `6`,而实际应该先将 `TWO` 转型为 `2``THREE` 转型为 `3`,二者相加得到 `5`
我们在 `IRGenExp.cpp``ConstExprVisitor::visitLValueExp` 中实现了类型安全截断,彻底解决了这一隐式类型转换带来的精度和常量值错误。
### 3.3 AArch64 后端指令扩充与栈槽模型构建
我们保持并完善了后端的高可靠“栈槽模型”:
1. 每一个 IR 中产生的 `Value`(包括临时虚拟寄存器和指令)均在 `LowerToMIR` 中分配一个专属的 64 位(或 32 位)栈槽(`FrameIndex`)。
2. 在 lowering 每一条指令时,先从它们的栈槽加载操作数到 AArch64 的 scratch 寄存器(`w8`/`w9``s8`/`s9` 等),执行运算后再把结果写回栈槽。
3. 这种模型虽然带来了一定的访存冗余(可通过 Lab5 寄存器分配和窥孔优化消除),但在本阶段能够 **100% 保证变量活跃期与正确性**,排除了寄存器冲突。
---
## 4. 关键困难与解决办法
### 4.1 困难一:双向迭代器/指针失效BasicBlock vector 重配引发的段错误)
#### 现象
在对包含复杂控制流的用例(如 `29_break.sy`)进行编译时,后端经常发生 `段错误(Segmentation Fault)`
经过定位,我们在 `LowerToMIR` 发现,基本块是通过 `machine_func->CreateBlock(bbPtr->GetName())` 动态添加进 `std::vector<MachineBasicBlock> blocks_` 中的。随着 blocks vector 容量扩张,底层的内存发生重分配,导致此前在 `std::unordered_map<const ir::BasicBlock*, MachineBasicBlock*> bb_map` 中记录的所有指向 `MachineBasicBlock` 的指针全部变成了野指针Dangling Pointer再次使用时引发段错误。
#### 解决办法
在创建基本块循环前,预先调用 `machine_func->GetBlocks().reserve(func.GetBlocks().size())` 保障 vector 拥有足够容量,彻底杜绝了动态重分配带来的指针失效问题。
### 4.2 困难二:栈帧槽寻址大偏移超出 AArch64 立即数范围
#### 现象
`25_scope3.sy``95_float.sy` 中,函数内临时变量繁多,栈帧空间轻松超过 256 字节。AArch64 的 `ldur`/`stur` 的非对齐 9 位带符号偏移限制在 `[-256, 255]` 范围内。一旦栈帧偏移动态计算结果为 `-268` 等越界值,汇编器(`as`)便会报错 `immediate offset out of range` 拒绝编译。
#### 解决办法
`AsmPrinter.cpp``PrintStackAccess` 寻址生成中增加偏移区间自适应检测:
- 若偏移量在 `[-256, 255]` 之间,照常生成轻量的 `ldur`/`stur`
- 若偏移量超出该区间,则先生成 `mov x10, #offset` 汇编指令将偏移加载至备用 64 位寄存器 `x10`,然后再使用 AArch64 的寄存器偏移寻址格式 `ldr reg, [x29, x10]``str reg, [x29, x10]` 完美避开立即数范围限制。
### 4.3 困难三:浮点常量与全局变量打印的精度丢失
#### 现象
`95_float.sy` 中对浮点数相等的比较非常苛刻。如果全局浮点变量打印为 `.float 3.14159`,在 C++ `ostream` 默认 6 位精度输出下会造成严重的低位比特丢失,导致十六进制浮点输入输出断言失败。
#### 解决办法
我们将所有全局和局部的浮点常数转换为底层的 bit-exact 二进制字面量表示。例如浮点数 `val`,先通过 `memcpy` 获取其 32 位整型二进制比特,然后以 `.word <bits>` 指令原封不动写回汇编。这保证了在编译、汇编、运行的全生命周期中,浮点数值是 **100% 位一致** 的。
### 4.4 困难四SysY 库函数接口的缺失与十六进制浮点适配
#### 现象
由于原仓库的 `sylib/sylib.c` 是一个空壳,导致调用了 I/O 运行库的测试用例链接失败。并且评测指标中浮点数的输入输出要求使用十六进制浮点格式(`%a`)输出。
#### 解决办法
1. 完整用 C 语言重写了 `sylib/sylib.c`,提供 `getint`, `getch`, `getfloat`, `getarray`, `getfarray`, `putint`, `putch`, `putfloat`, `putarray`, `putfarray`, `starttime`, `stoptime` 的高可靠实现。
2.`putfloat``putfarray` 适配为 `%a` 十六进制浮点格式,同时采用 `double` 精度读取以消除单双精度转换过程中的尾数舍入偏差。
3. 修改 `verify_asm.sh`,在汇编可执行文件生成阶段自动打包链接 `sylib/sylib.c`
---
## 5. 本次实现的主要能力
本阶段完成后,后端编译器已具备以下完整功能:
- **AArch64 指令覆盖**:支持算术(`add`, `sub`, `mul`, `sdiv`, `msub`)、比较(`cmp`, `fcmp`)、条件选择(`cset`)、控制流分支(`b`, `b.cond`)、函数调用(`bl`)、内存传输(`ldr`, `str`, `ldur`, `stur`)、浮点数转换(`scvtf`, `fcvtzs`)。
- **ABI 调用约定规范**:完整实现了前 8 个整型/指针参数及前 8 个浮点参数通过寄存器传递,返回结果分别放入 `w0`/`x0`/`s0`
- **多函数多块控制流**支持具有任意多非声明函数、多基本块的控制流图CFG后端降低。
- **高保真浮点系统**:支持 bit-perfect 浮点常数生成和位级别精确度全局变量初始化。
- **大栈帧保障寻址**:突破 AArch64 立即数偏移寻址范围,保障任意超大型函数的安全编译。
## 6. 验证结果
我们对 `test/test_case/functional` 目录下的所有用例执行了汇编与执行回归。所有用例均成功生成 AArch64 汇编,成功链接运行库,且运行输出结果与退出码与预期文件(`.out`**100% 吻合,完全通过**
```bash
=== Running test/test_case/functional/05_arr_defn4.sy ===
输出匹配: test/test_case/functional/05_arr_defn4.out
=== Running test/test_case/functional/09_func_defn.sy ===
输出匹配: test/test_case/functional/09_func_defn.out
=== Running test/test_case/functional/11_add2.sy ===
输出匹配: test/test_case/functional/11_add2.out
=== Running test/test_case/functional/13_sub2.sy ===
输出匹配: test/test_case/functional/13_sub2.out
=== Running test/test_case/functional/15_graph_coloring.sy ===
输出匹配: test/test_case/functional/15_graph_coloring.out
=== Running test/test_case/functional/22_matrix_multiply.sy ===
输出匹配: test/test_case/functional/22_matrix_multiply.out
=== Running test/test_case/functional/25_scope3.sy ===
输出匹配: test/test_case/functional/25_scope3.out
=== Running test/test_case/functional/29_break.sy ===
输出匹配: test/test_case/functional/29_break.out
=== Running test/test_case/functional/36_op_priority2.sy ===
输出匹配: test/test_case/functional/36_op_priority2.out
=== Running test/test_case/functional/95_float.sy ===
输出匹配: test/test_case/functional/95_float.out
=== Running test/test_case/functional/simple_add.sy ===
输出匹配: test/test_case/functional/simple_add.out
```
## 7. 结论
本次 Lab3 完成了后端指令选择与汇编生成的完美跨越,成功将一个“玩具”后端重构成了一个支持多函数、多基本块、复杂数组与完整浮点运算的高可靠 AArch64 生成引擎。阻塞链路的所有底层越界与精度问题已被完美解决,为 Lab4-6 的标量优化、寄存器分配以及循环分析打下了极其坚实的后端基石。