docs(doc): lab5, lab6文档
This commit is contained in:
157
doc/Lab5-基本标量优化.md
Normal file
157
doc/Lab5-基本标量优化.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Lab5:基本标量优化
|
||||||
|
|
||||||
|
## 1. 本实验定位
|
||||||
|
|
||||||
|
Lab5 的目标是让 IR 从“能跑”变成“跑的更好”。
|
||||||
|
在当前编译器基础上,做基础标量优化,框架中给出了三种,可以按需补充:
|
||||||
|
|
||||||
|
1. 常量相关优化(常量折叠/传播)
|
||||||
|
2. 无用代码删除(DCE)
|
||||||
|
3. CFG 简化与不可达代码删除
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 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、常量传播等优化的实现复杂度,也更利于后续扩展。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Lab5 要求
|
||||||
|
|
||||||
|
需要同学完成:
|
||||||
|
|
||||||
|
1. 理解当前 IR/CFG 结构,明确“有用代码、无用代码、不可达代码”的定义。
|
||||||
|
2. 完成可运行标量优化代码。
|
||||||
|
3. 将优化串联到 `PassManager`,形成可重复执行的优化流程。
|
||||||
|
4. 保证优化前后语义一致(功能不回归)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 当前代码框架(与 Lab5 相关)
|
||||||
|
|
||||||
|
1. IR 核心
|
||||||
|
- `src/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/ConstFold.cpp`
|
||||||
|
- `src/ir/passes/DCE.cpp`
|
||||||
|
- `src/ir/passes/CFGSimplify.cpp`
|
||||||
|
- `src/ir/passes/PassManager.cpp`
|
||||||
|
|
||||||
|
3. 入口
|
||||||
|
- `src/main.cpp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 需要修改的文件
|
||||||
|
|
||||||
|
1. 核心优化实现
|
||||||
|
- `src/ir/passes/ConstFold.cpp`
|
||||||
|
- `src/ir/passes/DCE.cpp`
|
||||||
|
- `src/ir/passes/CFGSimplify.cpp`
|
||||||
|
- `src/ir/passes/PassManager.cpp`
|
||||||
|
|
||||||
|
2. 视实现需要可能修改
|
||||||
|
- `src/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 关系)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 算法说明
|
||||||
|
|
||||||
|
### 6.1 Dead(无用代码删除)
|
||||||
|
|
||||||
|
可以采用“标记 + 清扫”思路:
|
||||||
|
|
||||||
|
1. 从关键操作出发标记“有用”指令
|
||||||
|
2. 沿数据依赖和必要控制依赖扩展标记
|
||||||
|
3. 删除未标记指令
|
||||||
|
|
||||||
|
> 本实验不限定具体思路,实现可自由设计。
|
||||||
|
|
||||||
|
### 6.2 Clean
|
||||||
|
|
||||||
|
在 DCE 后对 CFG 做结构化清理,常见包括:
|
||||||
|
|
||||||
|
1. 冗余分支改写
|
||||||
|
2. 空块删除/绕过
|
||||||
|
3. 线性可合并块合并
|
||||||
|
4. 不可达块删除
|
||||||
|
|
||||||
|
### 6.3 优化顺序建议
|
||||||
|
|
||||||
|
可采用迭代顺序:
|
||||||
|
|
||||||
|
1. `ConstFold`
|
||||||
|
2. `DCE`
|
||||||
|
3. `CFGSimplify`
|
||||||
|
...
|
||||||
|
必要时重复多轮,直到 IR 不再变化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 构建与验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j "$(nproc)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.1 观察 IR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build/bin/compiler --emit-ir test/test_case/simple_add.sy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 语义回归
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/verify_ir_with_llvm.sh test/test_case/simple_add.sy out/ir --run
|
||||||
|
./scripts/verify_asm_with_qemu.sh test/test_case/simple_add.sy out/asm --run
|
||||||
|
```
|
||||||
|
|
||||||
|
目标:优化后程序行为与优化前保持一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
174
doc/Lab6-并行与循环优化.md
Normal file
174
doc/Lab6-并行与循环优化.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# Lab6:并行与循环优化
|
||||||
|
|
||||||
|
## 1. 本实验定位
|
||||||
|
|
||||||
|
Lab6 的目标是在 Lab5 基本标量优化之后,面向“循环密集型代码”继续优化。
|
||||||
|
本实验重点不是再补语义覆盖,而是提升中后端对循环结构的处理能力,为性能优化打基础。
|
||||||
|
|
||||||
|
核心方向:
|
||||||
|
|
||||||
|
1. 循环识别与规范化
|
||||||
|
2. 循环相关优化(如不变代码外提、强度削弱等)
|
||||||
|
3. 可并行循环分析与并行化改造(可选,按课程要求推进)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 先看这一点:循环优化依赖分析结果
|
||||||
|
|
||||||
|
循环优化通常依赖一组基础分析:
|
||||||
|
|
||||||
|
1. CFG 与支配关系
|
||||||
|
2. 循环层次信息(LoopInfo)
|
||||||
|
3. def-use/use-def 与副作用信息(保证变换合法)
|
||||||
|
|
||||||
|
如果这些基础信息不稳定,循环优化很容易“优化错程序”。
|
||||||
|
因此 Lab6 建议先把分析链路和验证手段搭好,再做具体变换。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Lab6 要求
|
||||||
|
|
||||||
|
需要同学完成:
|
||||||
|
|
||||||
|
1. 在现有 IR 上识别循环结构(至少能区分循环头、循环体、回边)。
|
||||||
|
2. 实现有效的循环优化,并保证语义不变。
|
||||||
|
3. 将循环优化并入 `PassManager`,与 Lab5 的优化流程协同工作。
|
||||||
|
4. 对优化前后结果做回归验证,并给出性能或代码规模对比(至少一种指标)。
|
||||||
|
5. 可选:尝试实现可并行循环识别与并行化改造(按课程要求)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 当前代码框架(与 Lab6 相关)
|
||||||
|
|
||||||
|
1. IR 与分析
|
||||||
|
- `src/ir/IR.h`
|
||||||
|
- `src/ir/analysis/DominatorTree.cpp`
|
||||||
|
- `src/ir/analysis/LoopInfo.cpp`
|
||||||
|
|
||||||
|
2. IR 优化
|
||||||
|
- `src/ir/passes/...`
|
||||||
|
|
||||||
|
3. 入口与验证
|
||||||
|
- `src/main.cpp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 需要修改的文件
|
||||||
|
|
||||||
|
1. 核心文件
|
||||||
|
- `src/ir/analysis/LoopInfo.cpp`(完善循环分析)
|
||||||
|
- `src/ir/passes/PassManager.cpp`(接入新的循环优化 pass)
|
||||||
|
- 新增循环优化 pass 文件(建议放在 `src/ir/passes/` 下)
|
||||||
|
|
||||||
|
2. 视实现需要可能修改
|
||||||
|
- `src/ir/analysis/DominatorTree.cpp`(若分析信息不足)
|
||||||
|
- `src/ir/IR.h`、`src/ir/Instruction.cpp`(若需要补充指令属性)
|
||||||
|
- `src/ir/IRPrinter.cpp`(调试输出增强)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 可选优化方向
|
||||||
|
|
||||||
|
1. 循环不变代码外提(LICM)
|
||||||
|
2. 归纳变量简化与强度削弱
|
||||||
|
3. 循环展开(小规模、受控展开)
|
||||||
|
4. 循环分裂
|
||||||
|
5. 简单并行化识别(无跨迭代写后读/写后写冲突时标记可并行)
|
||||||
|
...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 简要优化原理
|
||||||
|
|
||||||
|
### 7.1 循环不变量外提(Loop Invariant Code Motion)
|
||||||
|
|
||||||
|
原理:
|
||||||
|
将循环中“每次迭代结果都不变”的表达式移动到循环外执行。若某表达式不依赖循环内变化的值,并且其操作数在循环内不被改写,则它是循环不变量。
|
||||||
|
|
||||||
|
作用:
|
||||||
|
减少循环体内重复计算,降低迭代开销。
|
||||||
|
|
||||||
|
实现思路:
|
||||||
|
先识别循环结构,再判断循环体中哪些表达式对所有迭代恒定。把这些表达式外提到循环前置块(或等价安全位置),循环体改用外提结果。
|
||||||
|
|
||||||
|
### 7.2 强度削弱(Strength Reduction)
|
||||||
|
|
||||||
|
原理:
|
||||||
|
将高开销运算替换为等价低开销运算。典型场景是把循环中的乘法/除法改写为增量更新(加减)。
|
||||||
|
|
||||||
|
作用:
|
||||||
|
降低每次迭代的算术成本,提高整体执行效率。
|
||||||
|
|
||||||
|
实现思路:
|
||||||
|
识别归纳变量与其线性相关表达式,判断是否可改写为递增/递减更新。引入辅助变量后,用加减替代高成本运算并保持语义一致。
|
||||||
|
|
||||||
|
### 7.3 循环展开(Loop Unrolling)
|
||||||
|
|
||||||
|
原理:
|
||||||
|
一次迭代中执行多份循环体副本。
|
||||||
|
|
||||||
|
作用:
|
||||||
|
减少控制指令比例,提高指令级并行机会,也有利于后续向量化或流水线优化。
|
||||||
|
|
||||||
|
实现思路:
|
||||||
|
选择展开因子,复制循环体并调整步长。若总迭代次数不能整除展开因子,需要保留余数迭代处理路径以保证结果正确。
|
||||||
|
|
||||||
|
### 7.4 循环分裂(Loop Fission)
|
||||||
|
|
||||||
|
原理:
|
||||||
|
把一个包含多类语句的循环拆成多个循环,每个循环执行原循环的一部分语句。
|
||||||
|
|
||||||
|
作用:
|
||||||
|
降低单个循环体复杂度,改善数据局部性,并为并行化/向量化创造条件。
|
||||||
|
|
||||||
|
实现思路:
|
||||||
|
先做数据依赖分析。若若干语句间不存在阻碍重排的依赖,可将其拆分到不同循环中,同时维持必要执行顺序保证语义不变。
|
||||||
|
|
||||||
|
### 7.5 循环并行化(Loop Parallelization)
|
||||||
|
|
||||||
|
原理:
|
||||||
|
把循环不同迭代同时执行,以利用多核并行能力。前提是迭代间不存在破坏语义的数据依赖。
|
||||||
|
|
||||||
|
作用:
|
||||||
|
在数据规模较大时可显著缩短运行时间,尤其适用于数值计算与批量数据处理。
|
||||||
|
|
||||||
|
实现思路:
|
||||||
|
先判定迭代间读写依赖。若可并行,则将迭代划分为任务并分发到线程/处理单元;结束后进行同步与归并,确保结果正确。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 推荐实验流程
|
||||||
|
|
||||||
|
1. 跑通循环分析。
|
||||||
|
2. 选择循环优化实现
|
||||||
|
3. 接入 `PassManager` 。
|
||||||
|
4. 用样例验证正确性。
|
||||||
|
5. 统计优化前后差异。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 构建与验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j "$(nproc)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.1 功能回归
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/verify_ir_with_llvm.sh test/test_case/simple_add.sy out/ir --run
|
||||||
|
./scripts/verify_asm_with_qemu.sh test/test_case/simple_add.sy out/asm --run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 优化效果对比(示例)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 对比优化前后 IR/汇编输出(按你实现的开关或日志方式执行)
|
||||||
|
./build/bin/compiler --emit-ir test/test_case/simple_add.sy
|
||||||
|
./build/bin/compiler --emit-asm test/test_case/simple_add.sy
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ cat /test/funcrparams.sysy
|
|||||||
- 修改任意代码后需要重新执行`cmake --build build`命令重新构建项目,ANTLR工具会从`SysY.g4`生成词法/语法分析器,生成的文件位于`./build/src`目录
|
- 修改任意代码后需要重新执行`cmake --build build`命令重新构建项目,ANTLR工具会从`SysY.g4`生成词法/语法分析器,生成的文件位于`./build/src`目录
|
||||||
- (进阶内容)修改`src/ASTPrinter.h`与`src/ASTPrinter.cpp`,实现从AST输出源程序,但输出的源程序是经过格式化的,测试用例为`test/format-test.sy`,格式化后的参考结果为`test/format-ref.sy`
|
- (进阶内容)修改`src/ASTPrinter.h`与`src/ASTPrinter.cpp`,实现从AST输出源程序,但输出的源程序是经过格式化的,测试用例为`test/format-test.sy`,格式化后的参考结果为`test/format-ref.sy`
|
||||||
|
|
||||||
## 实验2:从AST生成中间表示
|
## 实验2:生成中间表示
|
||||||
|
|
||||||
exp2分支为大家准备好了进行实验2的基本代码框架,包括
|
exp2分支为大家准备好了进行实验2的基本代码框架,包括
|
||||||
|
|
||||||
@@ -64,16 +64,9 @@ exp2分支为大家准备好了进行实验2的基本代码框架,包括
|
|||||||
在实验2中,同学们需要完成的任务包括
|
在实验2中,同学们需要完成的任务包括
|
||||||
|
|
||||||
- 熟悉掌握IR定义与相关数据结构
|
- 熟悉掌握IR定义与相关数据结构
|
||||||
- 从AST生成IR(基于visitor机制)
|
- 从语法树生成IR
|
||||||
|
请同学们仔细阅读代码学习IR的定义。
|
||||||
|
|
||||||
请同学们仔细阅读代码学习IR的定义。可以使用doxygen工具自动生成HTML文档
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install doxygen graphviz
|
|
||||||
doxygen doc/Doxyfile
|
|
||||||
```
|
|
||||||
|
|
||||||
上述命令执行完毕后,将在doxygen/html下找到生成的代码文档。
|
|
||||||
|
|
||||||
## 实验3:从SysY IR 生成ARMv7汇编代码
|
## 实验3:从SysY IR 生成ARMv7汇编代码
|
||||||
### 后端相关源码
|
### 后端相关源码
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace ir {
|
|||||||
|
|
||||||
class Type;
|
class Type;
|
||||||
class ConstantInt;
|
class ConstantInt;
|
||||||
|
class Instruction;
|
||||||
|
|
||||||
// IR 上下文:集中管理类型、常量等共享资源,便于复用与扩展。
|
// IR 上下文:集中管理类型、常量等共享资源,便于复用与扩展。
|
||||||
class Context {
|
class Context {
|
||||||
@@ -56,10 +57,13 @@ class Value {
|
|||||||
const std::shared_ptr<Type>& type() const { return type_; }
|
const std::shared_ptr<Type>& type() const { return type_; }
|
||||||
const std::string& name() const { return name_; }
|
const std::string& name() const { return name_; }
|
||||||
void set_name(std::string n) { name_ = std::move(n); }
|
void set_name(std::string n) { name_ = std::move(n); }
|
||||||
|
void AddUser(Instruction* user) { users_.push_back(user); }
|
||||||
|
const std::vector<Instruction*>& users() const { return users_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Type> type_;
|
std::shared_ptr<Type> type_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
std::vector<Instruction*> users_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConstantInt : public Value {
|
class ConstantInt : public Value {
|
||||||
|
|||||||
@@ -7,18 +7,40 @@ namespace ir {
|
|||||||
|
|
||||||
BinaryInst::BinaryInst(Opcode op, std::shared_ptr<Type> ty, Value* lhs,
|
BinaryInst::BinaryInst(Opcode op, std::shared_ptr<Type> ty, Value* lhs,
|
||||||
Value* rhs, std::string name)
|
Value* rhs, std::string name)
|
||||||
: Instruction(op, std::move(ty), std::move(name)), lhs_(lhs), rhs_(rhs) {}
|
: Instruction(op, std::move(ty), std::move(name)), lhs_(lhs), rhs_(rhs) {
|
||||||
|
if (lhs_) {
|
||||||
|
lhs_->AddUser(this);
|
||||||
|
}
|
||||||
|
if (rhs_) {
|
||||||
|
rhs_->AddUser(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReturnInst::ReturnInst(Value* val)
|
ReturnInst::ReturnInst(Value* val)
|
||||||
: Instruction(Opcode::Ret, Type::Void(), ""), value_(val) {}
|
: Instruction(Opcode::Ret, Type::Void(), ""), value_(val) {
|
||||||
|
if (value_) {
|
||||||
|
value_->AddUser(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AllocaInst::AllocaInst(std::string name)
|
AllocaInst::AllocaInst(std::string name)
|
||||||
: Instruction(Opcode::Alloca, Type::PtrInt32(), std::move(name)) {}
|
: Instruction(Opcode::Alloca, Type::PtrInt32(), std::move(name)) {}
|
||||||
|
|
||||||
LoadInst::LoadInst(Value* ptr, std::string name)
|
LoadInst::LoadInst(Value* ptr, std::string name)
|
||||||
: Instruction(Opcode::Load, Type::Int32(), std::move(name)), ptr_(ptr) {}
|
: Instruction(Opcode::Load, Type::Int32(), std::move(name)), ptr_(ptr) {
|
||||||
|
if (ptr_) {
|
||||||
|
ptr_->AddUser(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StoreInst::StoreInst(Value* val, Value* ptr)
|
StoreInst::StoreInst(Value* val, Value* ptr)
|
||||||
: Instruction(Opcode::Store, Type::Void(), ""), value_(val), ptr_(ptr) {}
|
: Instruction(Opcode::Store, Type::Void(), ""), value_(val), ptr_(ptr) {
|
||||||
|
if (value_) {
|
||||||
|
value_->AddUser(this);
|
||||||
|
}
|
||||||
|
if (ptr_) {
|
||||||
|
ptr_->AddUser(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ir
|
} // namespace ir
|
||||||
|
|||||||
Reference in New Issue
Block a user