style(doc): 调整 Lab5 文档表述风格

master
Lane0218 7 days ago
parent dc34d9eafd
commit 9eab25d676

@ -2,62 +2,31 @@
## 1. 本实验定位
Lab5 的目标是让 IR 从“能跑”变成“跑更好”。
Lab5 的目标是让 IR 从“能跑”变成“跑更好”。
在进入本实验的标量优化前,先完成或接入 `mem2reg`,将局部变量的 `alloca/load/store` 提升到 SSA 形式。
在当前编译器基础上,做基础标量优化,框架中给几种,可以按需补充:
1. 常量相关优化(常量折叠/传播)
2. 无用代码删除DCE
3. CFG 简化与不可达代码删除
4. 公共子表达式消除CSE
...
在此基础上可以逐步补上常量相关优化、无用代码删除、CFG 简化、公共子表达式消除等基础标量优化;如果你的实现方案里还需要其他局部优化,也可以按需继续扩展。
---
## 2. Mem2Reg
在很多编译器中AST lower 到 IR 时,局部变量通常先以“内存形式”表示:
1. 用 `alloca` 在栈上分配局部变量
2. 用 `store` 写变量
3. 用 `load` 读变量
在很多编译器中AST lower 到 IR 时,局部变量通常先以“内存形式”表示,也就是先用 `alloca` 在栈上分配局部变量,再通过 `store/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`
典型流程通常包括几步:先识别可提升变量,找出由 `alloca` 分配且只通过 `load/store` 访问的局部变量;再构建 CFG明确基本块与前驱/后继关系,为后续插入 `phi` 和重命名提供基础;接着在控制流汇合点插入 `phi`,并沿支配树完成变量重命名,为每次定义分配 SSA 版本;最后删除已经被提升掉的冗余 `alloca/load/store`
### 2.2 Mem2Reg 的关键算法基础
1. 支配树Dominator Tree
若从入口到块 A 的所有路径都经过块 B则 B 支配 A。
支配树用于描述“定义能影响到哪里”,是变量重命名的基础。常见实现可采用 Lengauer-Tarjan 算法。
支配树Dominator Tree用于描述“定义能影响到哪里”。若从入口到块 A 的所有路径都经过块 B则 B 支配 A变量重命名通常就建立在这层关系上常见实现可采用 Lengauer-Tarjan 算法。
2. 支配边界Dominance Frontier
支配边界描述“支配关系结束并发生控制流汇合”的位置。
在 Mem2Reg 中,它的核心作用是确定 `phi` 函数插入点。
支配边界Dominance Frontier描述的是“支配关系结束并发生控制流汇合”的位置。在 Mem2Reg 中,它的核心作用是确定 `phi` 函数插入点。
3. SSA 构造Cytron 框架)
典型流程为:计算支配树 -> 计算支配边界 -> 插入 `phi` -> 重命名变量。
Mem2Reg 本质上就是该 SSA 构造流程在“可提升局部变量”上的工程化实现。
如果从更高层去看Mem2Reg 本质上就是 SSA 构造流程在“可提升局部变量”上的工程化实现。典型路线仍然是:计算支配树,计算支配边界,插入 `phi`,再完成变量重命名。
---
@ -69,43 +38,22 @@ LLVM 中通常维护完整 `Use-User` 双向关系;当前仓库是最小 IR
### 什么是 use-def
use-def或 def-use描述的是“值在哪里被定义、又在哪里被使用”的关系
use-def或 def-use描述的是“值在哪里被定义、又在哪里被使用”的关系。`def` 指某条指令产生了一个值,`use` 指其他指令把这个值当作操作数使用。
1. `def`:某条指令产生了一个值(定义点)。
2. `use`:其他指令把这个值当作操作数使用(使用点)。
在 IR 中维护好这层关系后,优化遍就能快速回答:
“这个值还有人用吗?”、“我要把旧值替换成新值,需要改哪些地方?”
在 IR 中维护好这层关系后,优化遍就能更快回答“这个值还有人用吗”“我要把旧值替换成新值,需要改哪些地方”这类问题。
### use-def 的作用
在优化阶段use-def 关系的价值主要体现在:
1. 判断“是否还被使用”更直接
DCE 可以直接依据某个值是否还有用户来决定是否可删,而不必每次全函数扫描。
在优化阶段use-def 关系的价值主要体现在几个方面判断一个值是否还被使用会更直接DCE 不必反复做全函数扫描常量折叠、常量传播、复制传播这类局部重写也更容易精准找到所有使用点同时它还能降低很多优化遍的实现复杂度并为后续扩展代数化简、CSE、部分冗余消除等优化打基础。
2. 支持局部重写与传播
常量折叠、常量传播、复制传播时,需要把“旧值的所有使用点”替换为“新值”;有 use-def 后可以精准定位使用点。
3. 降低优化遍实现复杂度
没有 use-def 时,很多优化都要反复做全局查找;有 use-def 后可把复杂度和代码量都压下来。
4. 便于后续扩展更多优化
例如代数化简、CSE、部分冗余消除等都依赖稳定的 def-use/use-def 信息。
这会明显降低 DCE、常量传播等优化的实现复杂度也更利于后续扩展。
因此,把这层关系维护稳定,通常会明显降低 DCE、常量传播等优化的实现难度也更利于后续扩展。
---
## 4. Lab5 要求
需要同学完成:
1. 理解当前 IR/CFG 结构,明确“有用代码、无用代码、不可达代码”的定义。
2. 完成可运行标量优化代码。
3. 将优化串联到 `PassManager`,形成可重复执行的优化流程。
4. 保证优化前后语义一致。
需要同学完成的事情并不复杂:先理解当前 IR/CFG 结构,明确“有用代码、无用代码、不可达代码”的区别;然后实现能够运行的基础标量优化,并把这些优化接入 `PassManager`,形成可重复执行的流程;最后通过测试确认优化前后语义一致。
---
@ -125,41 +73,23 @@ use-def或 def-use描述的是“值在哪里被定义、又在哪里被
### 6.1 Dead无用代码删除
可以采用“标记 + 清扫”思路:
1. 从关键操作出发标记“有用”指令
2. 沿数据依赖和必要控制依赖扩展标记
3. 删除未标记指令
可以采用“标记 + 清扫”思路:先从关键操作出发标记“有用”指令,再沿数据依赖和必要控制依赖扩展标记,最后删除未标记指令。
> 本实验不限定具体思路,实现可自由设计。
### 6.2 Clean
在 DCE 后对 CFG 做结构化清理,常见包括:
1. 冗余分支改写
2. 空块删除/绕过
3. 线性可合并块合并
4. 不可达块删除
在 DCE 之后,通常还需要对 CFG 做一轮结构化清理,例如改写冗余分支、删除或绕过空块、合并线性可拼接的基本块,以及清理不可达块。
### 6.3 优化顺序建议
建议仅约束一条:
1. `Mem2Reg` 在前面先执行一遍(将 IR 提升到更适合做标量优化的形式)。
这里建议只固定一个基本约束:先执行一遍 `Mem2Reg`,把 IR 提升到更适合做标量优化的形式。
其余优化遍(如 `ConstFold`、`CSE`、`DCE`、`CFGSimplify`)的组织顺序不做硬性规定,可根据你的实现自由设计;必要时可采用迭代方式直到 IR 不再变化。
其余优化遍(如 `ConstFold`、`CSE`、`DCE`、`CFGSimplify`)的组织顺序不做硬性规定,可根据你的实现自由设计;必要时也可以采用迭代方式,直到 IR 不再变化。
### 6.4 公共子表达式消除Common Subexpression Elimination
原理:
如果同一个表达式在程序中被多次计算,并且其操作数在计算之间没有改变,则可以只计算一次,并复用计算结果。
作用:
避免重复计算,减少指令数量,提高执行效率。
实现思路:
在基本块或更大范围内记录已经计算过的表达式。再次遇到相同表达式且操作数未变化时,直接复用之前的结果,而不是重新生成同一计算。
如果同一个表达式在程序中被多次计算,并且其操作数在计算之间没有改变,那么就可以只计算一次并复用结果。这类优化的直接收益,是减少重复计算、压缩指令数量、提升执行效率。实现时,通常会在基本块或更大范围内记录已经出现过的表达式;当再次遇到相同表达式且操作数未变化时,直接复用之前的结果,而不是重新生成同一计算。
---

Loading…
Cancel
Save