vector_mul3测试已通过

master
tangttangtang 4 weeks ago
parent 1ed7ab0d1b
commit abcae58661

@ -1,19 +1,19 @@
# Lab3指令选择与汇编生成说明
## 1. 文档目标
## 1. 文档范围
本文档说明当前仓库中 Lab3 后端的真实实现方案,重点回答四个问题
本文档描述当前仓库中 Lab3 后端的真实实现,而不是计划中的设计。内容覆盖以下四部分
1. Lab3 后端的整体流水线是什么。
2. 当前实现与 `Reference` 目录下三份资料的对应关系是什么。
3. 指令选择、寄存器分配、栈布局分别是如何落地的。
4. 当前测试结果收敛到了什么程度。
- Lab3 后端的整体流水线与模块划分
- 当前实现与 `Reference` 目录下三份资料的对应关系
- 近期关键正确性问题的定位与修复
- 当前测试规范与最新测试结论
本文档只描述仓库当前代码,不把“计划中的优化”写成“已经完成的实现”
本文档对应的是仓库当前代码状态
---
## 2. 参考依据
## 2. 参考资料与采用方式
Lab3 当前实现主要参考以下三份资料:
@ -21,52 +21,52 @@ Lab3 当前实现主要参考以下三份资料:
- `Reference/lecture05-instruction selection-169.pdf`
- `Reference/lecture11-register allocation-part2-169.pdf`
对这三份资料的吸收方式如下:
这三份资料在项目中的落点分别如下:
- 栈布局与函数调用约定参考 `lab03`
采用 AArch64 / AAPCS64 基本规则,使用 `x29` 作为帧指针,栈帧按 16 字节对齐,区分 caller-saved 与 callee-saved 寄存器
- 指令选择参考 `lecture05`
当前实现采用“宏扩展式 lowering + 局部 peephole / 融合优化”的工程化方案,而不是完整树覆盖或 SelectionDAG 风格的树模式匹配
- 寄存器分配参考 `lecture11`
当前实现采用 George 风格图着色寄存器分配,包含 `build / simplify / coalesce / freeze / spill / select` 这一套核心流程
- `lab03`
主要对应栈布局、函数序言和尾声、AAPCS64 调用约定、栈上传参和 16 字节对齐
- `lecture05`
主要对应 instruction selection 的方法论。当前仓库采用的是“宏扩展式 lowering + 局部模式融合”的工程化方案,而不是完整树覆盖或 SelectionDAG。
- `lecture11`
主要对应寄存器分配。当前仓库使用的是 George 风格图着色分配,而不是线性扫描
因此,当前项目不是“逐页复刻讲义代码”,而是在讲义方法论基础上完成了适合本仓库的实现
因此,当前实现不是逐页照搬讲义,而是按讲义方法论落到本项目结构中
---
## 3. 当前后端总览
## 3. 后端整体流水线
当前 `compiler --emit-asm`真实执行流程如下:
当前 `compiler --emit-asm`流程如下:
1. ANTLR 前端解析源程序。
2. 语义分析构建符号与类型信息。
3. IR 生成得到 LLVM 风格中间表示。
4. IR Pass Pipeline 做中端优化。
1. 前端基于 ANTLR 解析 SysY 源程序。
2. 语义分析建立类型和符号信息。
3. IR 生成阶段产出 LLVM 风格中间表示。
4. IR Pass Pipeline 做中端标量优化。
5. `LowerToMIR` 将 IR 降到自定义 MIR。
6. `RunRegAlloc` 对 MIR 虚拟寄存器做图着色分配。
7. `RunFrameLowering` 计算栈对象偏移与最终栈帧大小。
7. `RunFrameLowering` 计算栈对象偏移和最终帧大小。
8. `PrintAsm` 输出 AArch64 汇编。
对应入口见 [src/main.cpp](/home/hw/nudt-compiler-cpp/src/main.cpp:1)。
入口在 [src/main.cpp](../src/main.cpp)。
这意味着当前 Lab3 后端已经是项目内自研后端,不依赖外部 LLVM 后端生成目标汇编。
这意味着 Lab3 已经不依赖 LLVM 后端生成汇编,而是使用仓库内自研的 MIR 后端
---
## 4. 模块划分
## 4. 核心模块划分
### 4.1 MIR 基础设施
核心文件:
- [include/mir/MIR.h](/home/hw/nudt-compiler-cpp/include/mir/MIR.h:1)
- [src/mir/MIRInstr.cpp](/home/hw/nudt-compiler-cpp/src/mir/MIRInstr.cpp:1)
- [src/mir/MIRBasicBlock.cpp](/home/hw/nudt-compiler-cpp/src/mir/MIRBasicBlock.cpp:1)
- [src/mir/MIRFunction.cpp](/home/hw/nudt-compiler-cpp/src/mir/MIRFunction.cpp:1)
- [src/mir/MIRContext.cpp](/home/hw/nudt-compiler-cpp/src/mir/MIRContext.cpp:1)
- [src/mir/Register.cpp](/home/hw/nudt-compiler-cpp/src/mir/Register.cpp:1)
- [include/mir/MIR.h](../include/mir/MIR.h)
- [src/mir/MIRInstr.cpp](../src/mir/MIRInstr.cpp)
- [src/mir/MIRBasicBlock.cpp](../src/mir/MIRBasicBlock.cpp)
- [src/mir/MIRFunction.cpp](../src/mir/MIRFunction.cpp)
- [src/mir/MIRContext.cpp](../src/mir/MIRContext.cpp)
- [src/mir/Register.cpp](../src/mir/Register.cpp)
这一层定义了后端使用的核心对象
这一层定义了:
- `MachineOperand`
- `AddressExpr`
@ -77,48 +77,56 @@ Lab3 当前实现主要参考以下三份资料:
- `StackObject`
- `Allocation`
当前 MIR 能表达:
当前 MIR 能表达的核心语义包括
- 整数算术与位运算
- 浮点算术
- `load/store`
- `lea`
- 比较与条件跳转
- 函数调用与返回
- 比较、跳转与返回
- `load/store/lea`
- 函数调用
- `memset`
- 类型转换
- 整浮转换
MIR 的作用不是完全等价于 AArch64 汇编,而是作为“比 IR 更接近机器、但仍保留寄存器和地址表达式抽象”的中间层,便于后续做寄存器分配和栈帧落地。
### 4.2 IR 到 MIR 的 lowering
核心文件:
- [src/mir/Lowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/Lowering.cpp:1)
- [src/mir/Lowering.cpp](../src/mir/Lowering.cpp)
职责包括:
- IR 指令到 MIR 指令的逐条翻译
- `alloca` 到栈对象的转换
- `load/store/gep` 到地址表达式的转换
- `phi` 结点结果预分配与并行 copy lowering
- 直接调用的 MIR 生成
- `phi` 结点预分配与并行 copy lowering
- 控制流和分支的 MIR 化
- 直接调用的 MIR 构造
### 4.3 寄存器分配
核心文件:
- [src/mir/RegAlloc.cpp](/home/hw/nudt-compiler-cpp/src/mir/RegAlloc.cpp:1)
- [src/mir/RegAlloc.cpp](../src/mir/RegAlloc.cpp)
当前实现的是 `GeorgeColoringAllocator`,负责:
当前实现的是 `GeorgeColoringAllocator`,不是线性扫描。
- 活跃性分析
- 干涉图构建
- move-related coalescing
- spill 选择
- 颜色分配
### 4.4 栈帧与对象布局
核心文件:
- [src/mir/FrameLowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/FrameLowering.cpp:1)
- [src/mir/FrameLowering.cpp](../src/mir/FrameLowering.cpp)
职责包括
这一层负责
- 局部对象布局
- 局部对象布局
- spill 槽布局
- callee-saved 保存槽布局
- 栈对象偏移计算
@ -128,245 +136,392 @@ Lab3 当前实现主要参考以下三份资料:
核心文件:
- [src/mir/AsmPrinter.cpp](/home/hw/nudt-compiler-cpp/src/mir/AsmPrinter.cpp:1)
- [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp)
职责包括
这一层负责
- MIR 到 AArch64 汇编文本的最终选择
- 地址模式发射
- MIR 到 AArch64 汇编文本的最终映射
- 地址模式选择
- 调用约定落地
- 序言 / 尾声生成
- 序言尾声生成
- 全局变量与常量区输出
从实现风格上说,真正的“最终 instruction selection”并不只发生在 `Lowering.cpp`,而是由 `Lowering.cpp``AsmPrinter.cpp` 共同完成。
---
## 5. 与 `lab03` 的对应关系
## 5. IR 到 MIR 的实现方式
### 5.1 标量指令 lowering
在 [src/mir/Lowering.cpp](../src/mir/Lowering.cpp) 中,以下 IR 会逐条映射成 MIR
- `Add/Sub/Mul/Div/Rem`
- `FAdd/FSub/FMul/FDiv/FNeg`
- `ICmp/FCmp`
- `Zext/IToF/FtoI`
- `Call/Ret`
- `Br/CondBr`
这种做法对应 `lecture05` 中的 macro-expansion / one-by-one translation。
`lab03` 的关键点是“基于宏扩展、自顶向下逐条翻译、正确实现 ARMv8 调用约定与栈帧”。
### 5.2 地址表达式 lowering
当前实现与其对应关系如下:
内存访问不会立即固定成某一条 AArch64 指令,而是先保存在 `AddressExpr` 中。它可以表达
- 自顶向下逐条翻译:符合
在 [src/mir/Lowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/Lowering.cpp:1) 中IR 指令被逐条转换为 MIR。
- 栈帧按 16 字节对齐:符合
在 [src/mir/FrameLowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/FrameLowering.cpp:1) 中对最终 `frame_size` 做了 16 字节对齐。
- 使用 `x29` 作为帧指针:符合
在 [src/mir/AsmPrinter.cpp](/home/hw/nudt-compiler-cpp/src/mir/AsmPrinter.cpp:889) 附近可以看到标准的 `stp x29, x30`、`mov x29, sp`、`mov sp, x29`、`ldp x29, x30`。
- 整型参数 / 返回值遵守 AAPCS64符合
整型参数按 `x0-x7`,浮点参数按 `v0-v7`,超出部分走栈,见 [src/mir/AsmPrinter.cpp](/home/hw/nudt-compiler-cpp/src/mir/AsmPrinter.cpp:843) 起的参数位置计算逻辑。
- caller-saved / callee-saved 区分:符合
当前分配器主要使用 `x19-x28``v8-v15` 作为可分配寄存器,并在需要时保存恢复。
- 基址来自栈对象
- 基址来自全局符号
- 基址来自寄存器
- 常量偏移
- 缩放索引寄存器
因此,从“课程实验的基础代码生成要求”来看,当前实现总体符合 `lab03` 的设计方向。
这样做的好处是:
- `getelementptr` 可以先降成统一地址表达式
- 寄存器分配完成后再决定能否发成直接 indexed addressing
- `lea + load/store` 是否融合可以推迟到汇编打印阶段
### 5.3 `phi` lowering
`phi` 不是直接发成 MIR 指令,而是在 lowering 时分两步处理:
1. 先为每个 `phi` 结果预分配目标 vreg。
2. 再按 CFG 边收集 copy并在前驱边上发射并行 copy。
对于条件跳转前驱,如果直接在原块尾部插入 copy 可能破坏 terminator 结构,因此实现里会在需要时插入专用边块。
这是 Lab3 正确性最关键的一部分之一,后文会专门说明修复细节。
---
## 6. 与 `lecture05` 的对应关系
## 6. 指令选择实现说明
`lecture05` 讲的是 instruction selection 的方法论,尤其区分了:
### 6.1 与 `lecture05` 的关系
`lecture05` 讲的是 instruction selection 的三类主要思路:
- 宏扩展
- 树模式匹配
- 窥孔优化
当前实现最接近下面这条路线:
- 先做宏扩展式 lowering
将 IR 指令逐条翻译成 MIR 指令。
- 再做局部模式融合
在汇编打印阶段把 MIR 指令组合成更自然的 AArch64 指令序列。
当前仓库最接近的路线是:
这体现在两层:
- 先做宏扩展 lowering
- 再在汇编发射阶段做局部模式融合
### 6.1 宏扩展 lowering
因此,当前实现符合 `lecture05` 的思想范围,但不是树覆盖式 instruction selector。
在 [src/mir/Lowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/Lowering.cpp:1) 中,以下 IR 指令会直接降成对应 MIR
### 6.2 当前实际做了哪些选择和融合
- `Add/Sub/Mul/Div/Rem`
- `FAdd/FSub/FMul/FDiv`
- `ICmp/FCmp`
- `Zext/IToF/FtoI`
- `Load/Store`
- `GetElementPtr`
- `Call/Return`
在 [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp) 中,当前已经实现了多类工程化 instruction selection
这种做法符合讲义中“macro-expansion / one-by-one translation”的基本思路。
- `icmp/fcmp + condbr` 的融合发射
- `lea + load/store` 的直接访存融合
- 基址加缩放索引的直接寻址
- `add/sub` 的立即数特化
- `rem``sdiv + msub` 的展开
- 立即数物化到寄存器
- spill/load/store 到统一的帧地址访问
### 6.2 局部模式融合
因此Lab3 当前不是“先生成一份一比一 MIR再无脑打印汇编”而是保留了机器相关的组合空间。
在 [src/mir/AsmPrinter.cpp](/home/hw/nudt-compiler-cpp/src/mir/AsmPrinter.cpp:699) 之后,当前实现做了若干局部优化:
### 6.3 当前实现与 LLVM 后端的差异
- 比较 + 条件跳转融合
- `lea + load/store` 融合
- 直接 indexed addressing 发射
- `add/sub` 的立即数特化
- `rem` 选择为 `sdiv + msub`
虽然当前全量样例已经通过,但代码生成质量和 LLVM 后端仍然不是同一层级。当前实现仍然有这些特征:
因此,当前实现属于:
- 没有完整树模式匹配
- 没有 SelectionDAG 或 GlobalISel
- 没有大规模机器级组合优化
- 可分配寄存器集合偏保守
- 符合 `lecture05` 的宏扩展与局部模式优化思想
- 但不是完整的树模式匹配 / 瓦片平铺实现
所以更准确的描述是:
如果要求“完全按树覆盖方式实现 instruction selection”则当前实现并不属于那一类但如果要求“以讲义方法论为参考完成工程化指令选择”则当前实现是符合的。
- 当前实现已经满足 Lab3 的正确性与基本性能要求
- 但不是 LLVM 级别的工业后端
---
## 7. `lecture11` 的对应关系
## 7. 调用约定与栈布局
这部分是当前实现与参考资料对齐程度最高的一部分。
### 7.1 与 `lab03` 的关系
在 [src/mir/RegAlloc.cpp](/home/hw/nudt-compiler-cpp/src/mir/RegAlloc.cpp:166) 中,当前使用 `GeorgeColoringAllocator` 完成寄存器分配。核心步骤包括
`lab03` 的重点是
1. 基于基本块的 `use/def/live_in/live_out` 活跃性分析
2. 构建干涉图
3. 收集 `Copy` 指令形成 move 关系
4. `simplify`
5. `coalesce`
6. `freeze`
7. `select spill`
8. `assign colors`
9. spill 槽分配与最终 `Allocation` 提交
- 正确的 AArch64 / AAPCS64 调用约定
- 正确的栈帧构造
- 16 字节对齐
- caller-saved 与 callee-saved 的区分
这和 `lecture11` 中 George 改进算法的讲义结构是一致的。
当前仓库在这些点上总体是符合的。
当前具体策略还包括:
### 7.2 当前调用约定实现
- GPR / FPR 分开着色
- spill cost 按基本块权重估计
- 被分配到 callee-saved 的物理寄存器会记录到函数对象中,供后续序言 / 尾声保存恢复
参数与返回值规则主要由 [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp) 负责落地。
因此,寄存器分配部分可以判断为
当前已经实现:
- 不只是“概念上参考 George”
- 而是代码结构上已经明显按 George 图着色路线实现
- 整型参数优先使用 `x0-x7`
- 浮点参数优先使用 `s0-s7`
- 超出寄存器容量的参数走栈
- 整型返回值走 `x0`
- 浮点返回值走 `s0`
- 调用前按需要扩栈,调用后回收
---
形参接收通过 `MachineInstr::Arg` 发射,调用点搬参与返回值接收通过 `MachineInstr::Call` 发射。
## 8. 栈布局实现说明
### 7.3 当前栈对象来源
当前栈对象来源主要有三类:
栈对象主要来自三类:
- `alloca` 降低得到的局部对象
- 寄存器分配产生的 spill 槽
- 需要保存的 callee-saved 寄存器槽
- 被使用到的 callee-saved 寄存器保存槽
### 7.4 当前帧布局方式
布局过程在 [src/mir/FrameLowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/FrameLowering.cpp:1) 中完成,基本策略为:
在 [src/mir/FrameLowering.cpp](../src/mir/FrameLowering.cpp) 中,当前布局策略为:
1. 遍历栈对象列表。
2. 按对象对齐要求推进 `cursor`
3. 将对象偏移记为相对 `x29` 的负偏移。
4. 最终 `frame_size` 向上对齐到 16 字节
1. 遍历所有栈对象
2. 按对象对齐要求推进 `cursor`
3. 记录相对帧指针的对象偏移
4. 最终 `frame_size` 向上对齐到 16 字节
汇编层使用方式如下
在汇编发射时
- 栈对象地址通过 `EmitFrameAddress` 生成。
- spill load/store 使用 `x17` 作为内部临时地址寄存器。
- 序言中先保存 `x29/x30`,再下移 `sp`,再保存实际使用到的 callee-saved 寄存器。
- `x29` 作为帧指针
- `x30` 作为返回地址寄存器
- 需要保存的 callee-saved GPR/FPR 会出现在序言和尾声中
- spill/load/store 通过统一的帧地址访问例程发射
这与 `lab03` 文档中的 ARMv8 栈帧思路是一致的。
### 7.5 当前寄存器选择策略对调用的影响
当前寄存器分配器对 GPR 主要使用 `x19-x28`,对 FPR 主要使用 `s8-s15`。这是一种偏保守但稳定的策略,优点是:
- 调用边界更容易处理
- caller-saved 污染更少
- 实现复杂度低
代价是:
- 可分配寄存器集合比 LLVM 更小
- 高压代码里更容易 spill
---
## 9. 调用约定实现说明
## 8. George 图着色寄存器分配
当前 AArch64 调用约定的落地主要在 [src/mir/AsmPrinter.cpp](/home/hw/nudt-compiler-cpp/src/mir/AsmPrinter.cpp:843) 之后。
这部分与 `lecture11` 的对应关系最强
已经实现的规则包括:
在 [src/mir/RegAlloc.cpp](../src/mir/RegAlloc.cpp) 中,当前实现包含以下典型步骤
- 整型参数优先使用 `x0-x7`
- 浮点参数优先使用 `v0-v7`
- 超过寄存器容量的参数按栈传递
- 整型返回值走 `x0`
- 浮点返回值走 `s0`
- 栈上传参调用前先扩栈,调用后回收
1. 基本块级 `use/def/live_in/live_out` 活跃性分析
2. 干涉图构建
3. `Copy` 指令诱导的 move 关系收集
4. `simplify`
5. `coalesce`
6. `freeze`
7. `select spill`
8. `assign colors`
9. spill 槽创建与最终 `Allocation` 提交
当前设计中:
当前实现还有几个重要特征
- 参数位置计算由 `ComputeArgLocation` 完成
- 调用点参数搬运在 `MachineInstr::Call` 的汇编发射中完成
- 形参读取在 `MachineInstr::Arg` 的汇编发射中完成
- GPR 和 FPR 分开着色
- spill cost 会参考基本块权重
- 分配到 callee-saved 的物理寄存器会记录回函数对象,供后续序言和尾声保存恢复
整体上已经满足 Lab3 基础调用约定要求
因此,这里不是“概念上参考了图着色”,而是代码结构上就已经沿着 George 算法在实现
---
## 10. 近期关键修正
## 9. 近期关键正确性修复
### 9.1 `phi` 并行 copy 修复
修复位置:
- [src/mir/Lowering.cpp](../src/mir/Lowering.cpp)
原始问题是:多个 `phi copy` 被按普通顺序赋值发射,旧值可能在后续 copy 使用前就被提前覆盖。
这在复杂循环头里会表现为:
近期最重要的 Lab3 正确性修正发生在 [src/mir/Lowering.cpp](/home/hw/nudt-compiler-cpp/src/mir/Lowering.cpp:1) 的 `phi` lowering。
- `a' <- t`
- `b' <- a`
- `d' <- c`
- `e' <- d`
### 10.1 修正前的问题
如果先发 `a' <- t`,后面的 `b' <- a` 读到的就不是旧 `a`,而是已经被覆盖的新值。
原先 `phi` lowering` 会把前驱到后继的 copy 当作普通顺序赋值处理。
在复杂循环头中,多个 `phi` 之间存在“旧值仍要被后续 copy 使用”的情况,如果拷贝顺序错误,就会提前覆盖旧值,生成错误结果。
当前修复后的策略是:
这一问题直接导致了:
- 先按 CFG 边收集所有 `phi copy`
- 优先发“目的寄存器不再被其他待发 copy 当作源使用”的 copy
- 如有环,则引入临时 vreg 打破
- 对条件边在必要时插入专用边块
这个问题直接导致过:
- `crypto-1.sy`
- `crypto-2.sy`
- `crypto-3.sy`
三个高性能样例的汇编结果错误,而对应 IR 结果是正确的。
修复后,这三个样例已经恢复通过。
### 9.2 有序浮点比较的 NaN 语义修复
修复位置:
- [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp)
这个问题比表面上看起来更隐蔽。IR 层的浮点比较打印是:
- `FCmpEQ -> fcmp oeq`
- `FCmpNE -> fcmp one`
- `FCmpLT -> fcmp olt`
- `FCmpGT -> fcmp ogt`
- `FCmpLE -> fcmp ole`
- `FCmpGE -> fcmp oge`
见 [src/ir/IRPrinter.cpp](../src/ir/IRPrinter.cpp)。
这里的关键字是 `ordered`。也就是说,比较一旦遇到 `NaN`,这些条件不应该按普通整数式条件码去理解。
原来的 Lab3 后端把 `FCmp` 的结果物化和 `FCmp + CondBr` 融合分支都简单映射成了:
- `eq/ne/lt/gt/le/ge`
这会在 AArch64 上引入错误的 `NaN` 语义。对照 LLVM AArch64 后端后,当前修正为:
- `oeq -> eq`
- `olt -> mi`
- `ogt -> gt`
- `ole -> ls`
- `oge -> ge`
- `one -> 复合逻辑,不是单一条件码`
也就是说,浮点比较不能直接照抄整数比较的条件码名称。
### 9.3 `vector_mul3` 超时的真实原因
`vector_mul3` 最开始表现为超时,很容易误判成:
- 热点循环代码生成太慢
- spill 太多
- 指令选择不够激进
但实际定位后发现,真正原因不是主循环慢,而是浮点比较语义错了。
### 10.2 修正内容
定位过程中的关键事实有两点:
当前 `phi` lowering` 已改为:
- Lab2 全量 `214 PASS / 0 FAIL`
- `vector_mul3` 在 Lab2 不超时
- 先按边收集 `phi` copy`
- 对条件跳转前驱建立专门的边块
- 按并行 copy 规则发射
- 优先发“目的寄存器不再被其他 copy 当作源使用”的 copy
- 如果形成环,则使用临时 vreg 打破
对应日志见 [output/logs/lab2/lab2_20260412_183222/whole.log](../output/logs/lab2/lab2_20260412_183222/whole.log)。
这个修正已经使 `crypto-1/2/3` 三个样例重新通过。
这说明:
- 算法本身并不必然超时
- 前端、语义和 IR 也不是根因
- 真正问题在 Lab3 后端生成的汇编语义
进一步缩小后发现:
- `vector_mul3` 的主循环和点积本身能够结束
- 真正卡住的是 `my_sqrt`
- 根因是 `my_sqrt` 在输入为 `NaN` 时,循环条件被后端错误判真,导致死循环
因此,这不是“性能优化不够”的问题,而是“浮点有序比较语义错误导致的超时型正确性 bug”。
修复后,`vector_mul3` 已正常通过。
---
## 11. 当前测试状态
## 10. 测试脚本与日志规则
当前 Lab3 的批量测试脚本为:
Lab3 当前使用的脚本为:
- [scripts/lab3_build_test.sh](/home/hw/nudt-compiler-cpp/scripts/lab3_build_test.sh:1)
- [scripts/verify_asm.sh](/home/hw/nudt-compiler-cpp/scripts/verify_asm.sh:1)
- [scripts/lab3_build_test.sh](../scripts/lab3_build_test.sh)
- [scripts/verify_asm.sh](../scripts/verify_asm.sh)
测试规则为:
测试规则已经固定为:
- 每次运行生成独立目录 `lab3_日期_时间`
- 每次运行生成独立目录 `output/logs/lab3/lab3_YYYYMMDD_HHMMSS/`
- 目录中保留完整 `whole.log`
- 成功样例的中间文件自动删除
- 失败样例保留中间目录与 `error.log`
- 成功样例中间文件自动删除
- 失败样例保留中间目录
- 每个失败样例目录必须包含 `error.log`
也就是说,当前脚本已经符合“只保留失败用例中间文件”的要求。
---
## 11. 当前测试结果
### 11.1 `crypto-*` 修复后的失败集复查
在先修完 `phi` lowering 后,失败集复查日志为:
- [output/logs/lab3/lab3_20260412_143811/whole.log](../output/logs/lab3/lab3_20260412_143811/whole.log)
最新一次失败集复查日志位于:
当时的结果是
- [output/logs/lab3/lab3_20260412_143811/whole.log](/home/hw/nudt-compiler-cpp/output/logs/lab3/lab3_20260412_143811/whole.log:1)
- `crypto-1.sy` 通过
- `crypto-2.sy` 通过
- `crypto-3.sy` 通过
- `vector_mul3.sy` 仍失败
当前失败集复查结果为:
这一步证明 `crypto-*` 的根因确实在 `phi` 并行 copy。
- `PASS test/test_case/h_performance/crypto-1.sy`
- `PASS test/test_case/h_performance/crypto-2.sy`
- `PASS test/test_case/h_performance/crypto-3.sy`
- `FAIL test/class_test_case/performance/vector_mul3.sy`
### 11.2 `vector_mul3` 修复后的单项复查
也就是说,当前 Lab3 后端已经解决了前期的 `crypto-*` 正确性问题,但仍然保留一个性能型尾项:
只重跑失败集时,日志为:
- `vector_mul3.sy`
- [output/logs/lab3/lab3_20260412_185610/whole.log](../output/logs/lab3/lab3_20260412_185610/whole.log)
该样例当前表现为超时,而不是输出错误。
结果为:
- `vector_mul3.sy` 通过
这一步证明浮点比较修复已经消除了剩余尾项。
### 11.3 最新 Lab3 全量结果
最新全量运行日志为:
- [output/logs/lab3/lab3_20260412_185655/whole.log](../output/logs/lab3/lab3_20260412_185655/whole.log)
全量结果为:
- `214 PASS / 0 FAIL / total 214`
因此,当前 Lab3 后端在现有测试集上已经全部通过。
---
## 12. 当前结论
综合来看,当前 Lab3 后端可以准确概括为:
综合来看,当前项目中的 Lab3 后端可以准确概括为:
- 已经完成自研 MIR 后端主链路
- 栈布局与调用约定总体符合 `lab03`
- 指令选择符合 `lecture05` 的宏扩展与局部模式优化思路,但不是完整树匹配版本
- 寄存器分配高度符合 `lecture11` 的 George 图着色路线
- `crypto-*` 正确性问题已经修复
- 仍保留 `vector_mul3` 一个性能型尾项
- `phi` 并行 copy 正确性问题已经修复
- 有序浮点比较的 NaN 语义问题已经修复
- `crypto-*``vector_mul3` 均已通过
- 最新 Lab3 全量测试结果为 `214 PASS / 0 FAIL`
因此当前更准确的表述已经不是“Lab3 框架基本成型”,而是:
- Lab3 后端功能链路已经完整
- 当前测试集下正确性已经收敛
- 实现风格清晰地对应 `lab03 + lecture05 + lecture11`
如果后续继续做优化,重点就不再是“修正明显错误”,而是:
因此当前项目最准确的表述不是“Lab3 完全结束”,而是:
- 提升生成代码质量
- 扩大可分配寄存器利用范围
- 增加更强的机器相关优化
- Lab3 的代码生成框架已经成型并基本可用
- 正确性主体已经收敛
- 仍有少量性能尾项待继续优化
但这些属于后续优化方向,不影响当前 Lab3 已经完成并通过现有测试集这一结论。

@ -790,11 +790,50 @@ bool TryEmitDirectStore(const MachineFunction& function, const MachineInstr& ins
return TryEmitDirectMemoryAccess(function, inst.GetAddress(), type, src.c_str(), true, os);
}
const char* GetCondMnemonic(CondCode code) {
const char* GetIntCondMnemonic(CondCode code) {
static const char* kCond[] = {"eq", "ne", "lt", "gt", "le", "ge"};
return kCond[static_cast<int>(code)];
}
const char* GetOrderedFloatCondMnemonic(CondCode code) {
switch (code) {
case CondCode::EQ:
return "eq";
case CondCode::LT:
return "mi";
case CondCode::GT:
return "gt";
case CondCode::LE:
return "ls";
case CondCode::GE:
return "ge";
case CondCode::NE:
break;
}
throw std::runtime_error(FormatError("mir", "unsupported simple float condition"));
}
void EmitOrderedFloatBranch(CondCode code, const std::string& true_label,
const std::string& false_label, std::ostream& os) {
if (code == CondCode::NE) {
os << " b.mi " << true_label << "\n";
os << " b.gt " << true_label << "\n";
os << " b " << false_label << "\n";
return;
}
os << " b." << GetOrderedFloatCondMnemonic(code) << " " << true_label << "\n";
os << " b " << false_label << "\n";
}
void EmitOrderedFloatSet(const char* dst, CondCode code, std::ostream& os) {
if (code == CondCode::NE) {
os << " cset w10, mi\n";
os << " csinc " << dst << ", w10, wzr, le\n";
return;
}
os << " cset " << dst << ", " << GetOrderedFloatCondMnemonic(code) << "\n";
}
bool TryEmitFusedCompareBranch(const MachineFunction& function, const MachineInstr& cmp,
const MachineInstr& branch,
const std::unordered_map<int, int>& use_counts,
@ -832,9 +871,13 @@ bool TryEmitFusedCompareBranch(const MachineFunction& function, const MachineIns
const auto lhs = MaterializeFprUse(function, cmp.GetOperands()[1], 16, 10, os);
const auto rhs = MaterializeFprUse(function, cmp.GetOperands()[2], 17, 11, os);
os << " fcmp " << lhs << ", " << rhs << "\n";
EmitOrderedFloatBranch(cmp.GetCondCode(),
BlockLabel(function, branch.GetOperands()[1].GetText()),
BlockLabel(function, branch.GetOperands()[2].GetText()), os);
return true;
}
os << " b." << GetCondMnemonic(cmp.GetCondCode()) << " "
os << " b." << GetIntCondMnemonic(cmp.GetCondCode()) << " "
<< BlockLabel(function, branch.GetOperands()[1].GetText()) << "\n";
os << " b " << BlockLabel(function, branch.GetOperands()[2].GetText()) << "\n";
return true;
@ -1272,8 +1315,7 @@ void EmitFunction(const MachineFunction& function, std::ostream& os) {
const auto lhs = MaterializeFprUse(function, inst.GetOperands()[1], 16, 10, os);
const auto rhs = MaterializeFprUse(function, inst.GetOperands()[2], 17, 11, os);
os << " fcmp " << lhs << ", " << rhs << "\n";
static const char* kCond[] = {"eq", "ne", "lt", "gt", "le", "ge"};
os << " cset " << def.reg_name << ", " << kCond[static_cast<int>(inst.GetCondCode())] << "\n";
EmitOrderedFloatSet(def.reg_name.c_str(), inst.GetCondCode(), os);
FinalizeDef(function, vreg, def, os);
break;
}

Loading…
Cancel
Save