From a16120dbdf00472f264112376350522e6086069e Mon Sep 17 00:00:00 2001 From: jing <3030349106@qq.com> Date: Mon, 9 Mar 2026 21:21:55 +0800 Subject: [PATCH] =?UTF-8?q?docs(doc):=20=E6=96=87=E6=A1=A3=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0mem2reg=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/Lab5-基本标量优化.md | 104 ++++++++++++++++++++++++++------- src/ir/passes/CMakeLists.txt | 1 + src/ir/passes/CSE.cpp | 5 ++ src/ir/passes/PassManager.cpp | 7 ++- 4 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 src/ir/passes/CSE.cpp diff --git a/doc/Lab5-基本标量优化.md b/doc/Lab5-基本标量优化.md index f8661e0..643c4cc 100644 --- a/doc/Lab5-基本标量优化.md +++ b/doc/Lab5-基本标量优化.md @@ -3,15 +3,66 @@ ## 1. 本实验定位 Lab5 的目标是让 IR 从“能跑”变成“跑的更好”。 -在当前编译器基础上,做基础标量优化,框架中给出了三种,可以按需补充: +在进入本实验的标量优化前,先完成或接入 `mem2reg`,将局部变量的 `alloca/load/store` 提升到 SSA 形式。 + +在当前编译器基础上,做基础标量优化,框架中给几种,可以按需补充: + 1. 常量相关优化(常量折叠/传播) 2. 无用代码删除(DCE) 3. CFG 简化与不可达代码删除 +4. 公共子表达式消除(CSE) +... --- -## 2. IR 的 use-def 关系 +## 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,实现较轻量。 @@ -47,18 +98,18 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 --- -## 3. Lab5 要求 +## 4. Lab5 要求 需要同学完成: 1. 理解当前 IR/CFG 结构,明确“有用代码、无用代码、不可达代码”的定义。 2. 完成可运行标量优化代码。 3. 将优化串联到 `PassManager`,形成可重复执行的优化流程。 -4. 保证优化前后语义一致(功能不回归)。 +4. 保证优化前后语义一致。 --- -## 4. 当前代码框架(与 Lab5 相关) +## 5. 当前代码框架(与 Lab5 相关) 1. IR 核心 - `src/ir/IR.h` @@ -71,7 +122,9 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 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` @@ -81,10 +134,12 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 --- -## 5. 需要修改的文件 +## 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` @@ -97,9 +152,9 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 --- -## 6. 算法说明 +## 7. 算法说明 -### 6.1 Dead(无用代码删除) +### 7.1 Dead(无用代码删除) 可以采用“标记 + 清扫”思路: @@ -109,7 +164,7 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 > 本实验不限定具体思路,实现可自由设计。 -### 6.2 Clean +### 7.2 Clean 在 DCE 后对 CFG 做结构化清理,常见包括: @@ -118,32 +173,41 @@ use-def(或 def-use)描述的是“值在哪里被定义、又在哪里被 3. 线性可合并块合并 4. 不可达块删除 -### 6.3 优化顺序建议 +### 7.3 优化顺序建议 -可采用迭代顺序: +建议仅约束一条: -1. `ConstFold` -2. `DCE` -3. `CFGSimplify` -... -必要时重复多轮,直到 IR 不再变化。 +1. `Mem2Reg` 在前面先执行一遍(将 IR 提升到更适合做标量优化的形式)。 + +其余优化遍(如 `ConstFold`、`CSE`、`DCE`、`CFGSimplify`)的组织顺序不做硬性规定,可根据你的实现自由设计;必要时可采用迭代方式直到 IR 不再变化。 + +### 7.4 公共子表达式消除(Common Subexpression Elimination) + +原理: +如果同一个表达式在程序中被多次计算,并且其操作数在计算之间没有改变,则可以只计算一次,并复用计算结果。 + +作用: +避免重复计算,减少指令数量,提高执行效率。 + +实现思路: +在基本块或更大范围内记录已经计算过的表达式。再次遇到相同表达式且操作数未变化时,直接复用之前的结果,而不是重新生成同一计算。 --- -## 7. 构建与验证 +## 8. 构建与验证 ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j "$(nproc)" ``` -### 7.1 观察 IR +### 8.1 观察 IR ```bash ./build/bin/compiler --emit-ir test/test_case/simple_add.sy ``` -### 7.2 语义回归 +### 8.2 语义回归 ```bash ./scripts/verify_ir_with_llvm.sh test/test_case/simple_add.sy out/ir --run @@ -153,5 +217,3 @@ cmake --build build -j "$(nproc)" 目标:优化后程序行为与优化前保持一致。 --- - - diff --git a/src/ir/passes/CMakeLists.txt b/src/ir/passes/CMakeLists.txt index cfb5093..4d8f509 100644 --- a/src/ir/passes/CMakeLists.txt +++ b/src/ir/passes/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(ir_passes STATIC PassManager.cpp Mem2Reg.cpp ConstFold.cpp + CSE.cpp DCE.cpp CFGSimplify.cpp ) diff --git a/src/ir/passes/CSE.cpp b/src/ir/passes/CSE.cpp new file mode 100644 index 0000000..58b8bdd --- /dev/null +++ b/src/ir/passes/CSE.cpp @@ -0,0 +1,5 @@ +// 公共子表达式消除(CSE): +// - 识别并复用重复计算的等价表达式 +// - 典型放置在 ConstFold 之后、DCE 之前 +// - 当前为 Lab5 的框架占位,具体算法由实验实现 + diff --git a/src/ir/passes/PassManager.cpp b/src/ir/passes/PassManager.cpp index d08d611..dd642d6 100644 --- a/src/ir/passes/PassManager.cpp +++ b/src/ir/passes/PassManager.cpp @@ -1,4 +1,9 @@ // IR Pass 管理: // - 按优化级别组织优化 pipeline // - 统一运行 pass、统计与调试输出(按需要扩展) - +// +// Lab5 推荐顺序(可迭代多轮): +// 1. ConstFold +// 2. CSE +// 3. DCE +// 4. CFGSimplify