# Lab5：基本标量优化

## 1. 本实验定位

Lab5 的目标是让 IR 从“能跑”变成“跑的更好”。
在进入本实验的标量优化前，先完成或接入 `mem2reg`，将局部变量的 `alloca/load/store` 提升到 SSA 形式。

在当前编译器基础上，做基础标量优化，框架中给几种，可以按需补充：


1. 常量相关优化（常量折叠/传播）
2. 无用代码删除（DCE）
3. CFG 简化与不可达代码删除
4. 公共子表达式消除（CSE）
...

---

## 2. Mem2Reg 

在很多编译器中，AST lower 到 IR 时，局部变量通常先以“内存形式”表示：

1. 用 `alloca` 在栈上分配局部变量
2. 用 `store` 写变量
3. 用 `load` 读变量

这种表示语义正确、实现直接，但会引入大量冗余内存访问，不利于常量传播、DCE、CSE 等标量优化。

`mem2reg`（memory to register）的目标，就是把这类 `alloca/load/store` 形式提升到 SSA 形式，让值尽量直接在 SSA Value 上传递。

### 2.1 Mem2Reg 的核心过程

1. 识别可提升变量  
   找出由 `alloca` 分配、且只通过 `load/store` 访问的局部变量。

2. 构建 CFG  
   明确基本块与前驱/后继关系，为后续插入 `phi` 和重命名提供基础。

3. 插入 `phi`  
   在控制流汇合点合并来自不同路径的定义。

4. 变量重命名  
   沿支配树遍历，为每次定义分配 SSA 版本，保证“单次赋值”。

5. 删除冗余内存操作  
   提升完成后，移除对应的 `alloca/load/store`。

### 2.2 Mem2Reg 的关键算法基础

1. 支配树（Dominator Tree）  
   若从入口到块 A 的所有路径都经过块 B，则 B 支配 A。  
   支配树用于描述“定义能影响到哪里”，是变量重命名的基础。常见实现可采用 Lengauer-Tarjan 算法。

2. 支配边界（Dominance Frontier）  
   支配边界描述“支配关系结束并发生控制流汇合”的位置。  
   在 Mem2Reg 中，它的核心作用是确定 `phi` 函数插入点。

3. SSA 构造（Cytron 框架）  
   典型流程为：计算支配树 -> 计算支配边界 -> 插入 `phi` -> 重命名变量。  
   Mem2Reg 本质上就是该 SSA 构造流程在“可提升局部变量”上的工程化实现。


---

## 3. IR 的 use-def 关系


LLVM 中通常维护完整 `Use-User` 双向关系；当前仓库是最小 IR，实现较轻量。

### 什么是 use-def

use-def（或 def-use）描述的是“值在哪里被定义、又在哪里被使用”的关系：

1. `def`：某条指令产生了一个值（定义点）。
2. `use`：其他指令把这个值当作操作数使用（使用点）。

在 IR 中维护好这层关系后，优化遍就能快速回答：  
“这个值还有人用吗？”、“我要把旧值替换成新值，需要改哪些地方？”


### use-def 的作用

在优化阶段，use-def 关系的价值主要体现在：

1. 判断“是否还被使用”更直接  
   DCE 可以直接依据某个值是否还有用户来决定是否可删，而不必每次全函数扫描。

2. 支持局部重写与传播  
   常量折叠、常量传播、复制传播时，需要把“旧值的所有使用点”替换为“新值”；有 use-def 后可以精准定位使用点。

3. 降低优化遍实现复杂度  
   没有 use-def 时，很多优化都要反复做全局查找；有 use-def 后可把复杂度和代码量都压下来。

4. 便于后续扩展更多优化  
   例如代数化简、CSE、部分冗余消除等，都依赖稳定的 def-use/use-def 信息。

这会明显降低 DCE、常量传播等优化的实现复杂度，也更利于后续扩展。

---

## 4. Lab5 要求

需要同学完成：

1. 理解当前 IR/CFG 结构，明确“有用代码、无用代码、不可达代码”的定义。
2. 完成可运行标量优化代码。
3. 将优化串联到 `PassManager`，形成可重复执行的优化流程。
4. 保证优化前后语义一致。

---

## 5. 当前代码框架（与 Lab5 相关）

1. IR 核心
   - `include/ir/IR.h`
   - `src/ir/Instruction.cpp`
   - `src/ir/BasicBlock.cpp`
   - `src/ir/Function.cpp`
   - `src/ir/Module.cpp`
   - `src/ir/IRPrinter.cpp`

2. 分析与优化
   - `src/ir/analysis/DominatorTree.cpp`
   - `src/ir/analysis/LoopInfo.cpp`
   - `src/ir/passes/Mem2Reg.cpp`
   - `src/ir/passes/ConstFold.cpp`
   - `src/ir/passes/CSE.cpp`
   - `src/ir/passes/DCE.cpp`
   - `src/ir/passes/CFGSimplify.cpp`
   - `src/ir/passes/PassManager.cpp`

3. 入口
   - `src/main.cpp`

---

## 6. 需要修改的文件

1. 核心优化实现
   - `src/ir/passes/Mem2Reg.cpp`（建议先完成，作为后续标量优化前置）
   - `src/ir/passes/ConstFold.cpp`
   - `src/ir/passes/CSE.cpp`
   - `src/ir/passes/DCE.cpp`
   - `src/ir/passes/CFGSimplify.cpp`
   - `src/ir/passes/PassManager.cpp`

2. 视实现需要可能修改
   - `include/ir/IR.h`、`src/ir/Instruction.cpp`（补充副作用/可删除性信息）
   - `src/ir/IRPrinter.cpp`（调试输出增强）
   - `src/ir/analysis/DominatorTree.cpp`、`src/ir/analysis/LoopInfo.cpp`（辅助分析）
   - `src/ir/Value.cpp`（若补充 use-def 关系）

---

## 7. 算法说明

### 7.1 Dead（无用代码删除）

可以采用“标记 + 清扫”思路：

1. 从关键操作出发标记“有用”指令
2. 沿数据依赖和必要控制依赖扩展标记
3. 删除未标记指令

> 本实验不限定具体思路，实现可自由设计。

### 7.2 Clean

在 DCE 后对 CFG 做结构化清理，常见包括：

1. 冗余分支改写
2. 空块删除/绕过
3. 线性可合并块合并
4. 不可达块删除

### 7.3 优化顺序建议

建议仅约束一条：

1. `Mem2Reg` 在前面先执行一遍（将 IR 提升到更适合做标量优化的形式）。

其余优化遍（如 `ConstFold`、`CSE`、`DCE`、`CFGSimplify`）的组织顺序不做硬性规定，可根据你的实现自由设计；必要时可采用迭代方式直到 IR 不再变化。

### 7.4 公共子表达式消除（Common Subexpression Elimination）

原理：  
如果同一个表达式在程序中被多次计算，并且其操作数在计算之间没有改变，则可以只计算一次，并复用计算结果。

作用：  
避免重复计算，减少指令数量，提高执行效率。

实现思路：  
在基本块或更大范围内记录已经计算过的表达式。再次遇到相同表达式且操作数未变化时，直接复用之前的结果，而不是重新生成同一计算。

---

## 8. 构建与验证

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```

### 8.1 观察 IR

```bash
./build/bin/compiler --emit-ir test/test_case/simple_add.sy
```

### 8.2 语义回归

```bash
./scripts/verify_ir.sh test/test_case/simple_add.sy test/test_result/ir --run
./scripts/verify_asm.sh test/test_case/simple_add.sy test/test_result/asm --run
```

目标：优化后程序行为与优化前保持一致。

---
