|
|
|
|
@ -0,0 +1,89 @@
|
|
|
|
|
# Fix: IR O1 优化导致 09_BFS/10_DFS 输出错误及编译器崩溃
|
|
|
|
|
|
|
|
|
|
**日期**: 2026-05-19
|
|
|
|
|
**分支**: fix/ir-opt
|
|
|
|
|
**测试结果**: O1 前 422 项测试全部通过(后续 dead-code-elimination 性能问题为已存在缺陷)
|
|
|
|
|
|
|
|
|
|
## 问题描述
|
|
|
|
|
|
|
|
|
|
运行 `bash ./scripts/run_ir_test.sh --O1 --run` 时:
|
|
|
|
|
1. 编译器在生成 `testdata/h_functional/10_DFS.sy` 的 IR 时崩溃/卡死
|
|
|
|
|
2. `testdata/h_functional/09_BFS.sy` 的输出结果不正确(全为 0,期望有 1)
|
|
|
|
|
3. `testdata/h_functional/10_DFS.sy` 的可执行文件运行时死循环
|
|
|
|
|
|
|
|
|
|
## 根因分析
|
|
|
|
|
|
|
|
|
|
共发现 4 个缺陷:
|
|
|
|
|
|
|
|
|
|
### 缺陷 1 (关键): ConstProp 删除指令前未清理操作数引用
|
|
|
|
|
`src/ir/passes/ConstProp.cpp` 中 ZExt/SIToFP/FPToSI 折叠后直接调用 `RemoveFromParent()` 销毁指令,未先调用 `SetOperand(i, nullptr)` 断开 use-def 链。导致指令操作数的 `uses_` 列表中残留指向已释放内存的悬空指针,后续 `ReplaceAllUsesWith` 遍历 use 列表时访问已释放内存,造成崩溃(UB)。
|
|
|
|
|
|
|
|
|
|
### 缺陷 2 (关键): ConstFold 的 `removed` 集合存储悬空指针
|
|
|
|
|
`src/ir/passes/ConstFold.cpp` 中 `DetachAndRemove` 销毁指令后将其地址存入 `std::unordered_set<void*> removed`。若新指令被分配到同一地址,会被错误跳过,导致漏优化。
|
|
|
|
|
|
|
|
|
|
### 缺陷 3 (关键): CSE 跨 Store/Call 指令错误消除 Load
|
|
|
|
|
`src/ir/passes/CSE.cpp` 的局部值编号不感知内存副作用:`Store`/`Call` 指令可能修改全局变量,但 `available` 映射中缓存的 `Load` 结果不会被失效。例如 `pop_queue()` 中:
|
|
|
|
|
```c
|
|
|
|
|
h = h + 1; // store @h
|
|
|
|
|
int res = que[h]; // load @h 应读到新值,但 CSE 用 store 前的旧值替代
|
|
|
|
|
return que[h]; // 同上
|
|
|
|
|
```
|
|
|
|
|
导致 `que[old_h]` 被返回而非 `que[new_h]`,BFS 路径搜索失败。
|
|
|
|
|
|
|
|
|
|
### 缺陷 4: SanitizePhis 使用错误的 undef 类型
|
|
|
|
|
`src/ir/passes/PassManager.cpp` 中 `SanitizePhis` 固定使用 `ctx.GetConstInt(0)` 作为浮点 PHI 节点的 undef 值,造成 LLVM IR 类型不匹配。同时修复无效 incoming 时过于激进——value 和 block 只有一个为 null 时却同时替换两者。
|
|
|
|
|
|
|
|
|
|
## 修改内容
|
|
|
|
|
|
|
|
|
|
### 修改 1: ConstProp.cpp — 移除冗余折叠,委托给 ConstFold
|
|
|
|
|
ConstFold 已使用 `DetachAndRemove`(先清操作数再删除)正确处理 ZExt/SIToFP/FPToSI 折叠。移除 ConstProp 中的重复实现,改为直接返回 false:
|
|
|
|
|
```cpp
|
|
|
|
|
bool RunConstProp(Function& func, Context& ctx) {
|
|
|
|
|
(void)func; (void)ctx;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 修改 2: ConstFold.cpp — 用 GetParent() 检查替代 removed 集合
|
|
|
|
|
```cpp
|
|
|
|
|
// Before:
|
|
|
|
|
std::unordered_set<void*> removed;
|
|
|
|
|
if (removed.count(inst)) continue;
|
|
|
|
|
removed.insert(inst);
|
|
|
|
|
|
|
|
|
|
// After:
|
|
|
|
|
if (inst->GetParent() == nullptr) continue;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 修改 3: CSE.cpp — Store/Call 指令清空 available 映射
|
|
|
|
|
```cpp
|
|
|
|
|
// 新增: Store/Call 可能修改内存,保守清空 available 表
|
|
|
|
|
if (ip->GetOpcode() == Opcode::Store ||
|
|
|
|
|
ip->GetOpcode() == Opcode::Call) {
|
|
|
|
|
available.clear();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 修改 4: PassManager.cpp — SanitizePhis 类型感知 + 精准修复
|
|
|
|
|
```cpp
|
|
|
|
|
// 根据 PHI 类型选择 undef 值
|
|
|
|
|
Value* undef_val = phi->GetType()->IsFloat32()
|
|
|
|
|
? static_cast<Value*>(ctx.GetConstFloat(0.0f))
|
|
|
|
|
: static_cast<Value*>(ctx.GetConstInt(0));
|
|
|
|
|
|
|
|
|
|
// 仅修复确实为 null 的操作数
|
|
|
|
|
if (!inc_val) phi->SetOperand(i * 2, undef_val);
|
|
|
|
|
if (!inc_bb) phi->SetOperand(i * 2 + 1, entry);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 效果
|
|
|
|
|
|
|
|
|
|
- 09_BFS/10_DFS 全部通过 O1 测试
|
|
|
|
|
- 编译器不再崩溃或卡死
|
|
|
|
|
- O1 生成的 IR 通过 llc 验证
|
|
|
|
|
- 前 422 项测试 0 失败
|
|
|
|
|
|
|
|
|
|
## 已知遗留问题
|
|
|
|
|
|
|
|
|
|
dead-code-elimination-1.sy(100,000 个局部变量的压力测试)在 O1 下编译极慢(~940s),根因是 Mem2Reg 中 `BasicBlock::RemoveInstruction` 的 O(n) 线性查找被调用 O(n) 次,整体 O(n²)。此问题在本次修复前即存在(之前被 10_DFS 卡死掩盖),留待后续修复。
|