6 Commits

19 changed files with 1259 additions and 219 deletions

91
doc/Lab5-实验记录.md Normal file
View File

@@ -0,0 +1,91 @@
# Lab5 实验记录:寄存器分配与后端窥孔优化
## 1. 实验目标
本次 Lab5 的核心目标是在已有的中间表示生成与汇编生成框架基础上,实现高效的寄存器分配与后端优化技术。
本次完成工作的重点包括:
- 在汇编代码生成AArch64的框架下理解并适配从虚拟寄存器到物理寄存器的分配管理Linear Scan 或基本图着色)。
- 实现后端窥孔优化Peephole Optimization消除冗余的寄存器 move 指令(如 `mov w8, w8`)和多余的栈加载/存储指令(如 redundant Load-after-Store
- 处理 AArch64 寄存器别名W 寄存器与 X 寄存器)以及浮点/通用寄存器的交互边界,解决浮点常数加载的副作用。
- 通过全面的功能测试套件(`verify_asm.sh`)以保证生成的汇编在 QEMU 模拟器环境下的正确运行。
## 2. 代码改动范围
本次实验主要涉及和修改了以下模块:
- `include/mir/MIR.h`:增加 `RunPeephole` 优化通路的函数声明。
- `src/mir/passes/Peephole.cpp`:实现完整的后端窥孔优化处理器,包括寄存器尺度匹配、寄存器别名正规化以及栈读写冗余消除。
- `src/main.cpp`:将后端优化入口 `RunPeephole` 插入到汇编生成的整个管线中。
- 新增文档:`doc/Lab5-实验记录.md`
## 3. 完成过程
### 3.1 问题边界定位与痛点分析
在进行后端优化与窥孔之前,编译器能够正常输出 AArch64 汇编。但是由于寄存器分配和栈槽管理的保守性,生成的汇编代码中充斥着大量的:
1. 冗余的同名寄存器 self-move`mov w9, w9``mov x8, x8`)。
2. 在溢出与重载场景中,大量的 `StoreStack` 后紧跟 `LoadStack` 到相同物理寄存器的冗余操作。
3. 浮点数常量在 AArch64 后端加载时,通常需要通过常数池(`adrp` + `ldr`)加载,在此过程中需要临时占用通用寄存器(如 `x8`/`w8`)。
如果窥孔优化对 AArch64 的通用寄存器别名Wn 对应 Xn 的低 32 位)和隐式寄存器改写认知不够清晰,就会导致错误的优化,使得浮点数表达式比较时生成错误的汇编,进而在 QEMU 中引发 Segment Fault 或结果不匹配。
### 3.2 窥孔优化的具体设计与实现
为了保证性能与正确性,本实验在 `src/mir/passes/Peephole.cpp` 中设计了基于数据流上下文的单块窥孔扫描机制:
1. **同名物理寄存器正规化NormalizeReg**
AArch64 下,`W0``W28``X0``X28` 是一对一重叠映射的。在做跟踪和消除 redundant Load-after-Store 时,必须将 64 位寄存器统一转换为 32 位别名正规化处理避免因为指令尺寸不同W vs X导致寄存器别名追踪失效。
2. **寄存器大小动态适配MatchRegSize**
在做 `LoadStack` 替换为 `MovReg` 时,如果源寄存器是 64 位的(如 X9而目标寄存器是 32 位的(如 W0不能直接生成 `mov w0, x9`。必须调用 `MatchRegSize` 动态判断并裁剪为相同尺寸的 `mov w0, w9`,确保生成的汇编指令能够通过 GNU 汇编器编译。
3. **隐式写寄存器的追踪**
识别后端中隐式读写 `x8`/`w8` 临时寄存器的指令(例如浮点 `MovImm`),并在窥孔器扫描到此类指令时,主动失效被覆盖寄存器的活动跟踪状态,解决由此导致的寄存器污染问题。
## 4. 关键困难与解决办法
### 4.1 困难一:浮点常数隐式加载改写寄存器的副作用
#### 现象
在浮点测试用例 `95_float.sy` 进行编译时,发现部分浮点比较的结果不正确。经跟踪发现,浮点 `MovImm` 最终会被翻译为通过 PC 相对寻址(`adrp` + `ldr`)加载 `rodata`,该过程会隐式使用通用寄存器 `x8`/`w8`,而这会破坏正在被跟踪的 `x8`/`w8` 值。
#### 解决办法
`Peephole.cpp` 的指令写失效扫描逻辑中,显式识别 `MovImm` 的目标寄存器类型。如果目标寄存器是浮点寄存器(`S0` - `S15`),我们主动将 `slot_to_reg` 追踪关系中的 `x8`/`w8` 条目全部擦除失效。
#### 效果
隐式写寄存器失效策略完全排除了因常数池加载造成的寄存器污染问题,浮点计算和浮点比较指令行为变得绝对正确。
### 4.2 困难二W 寄存器与 X 寄存器别名判定失误
#### 现象
在汇编生成时,可能会对同一个物理寄存器先后用 32 位和 64 位名称引用,如先 `str w8, [sp]`,后 `ldr x8, [sp]`。如果直接用简单的字符串比对或物理寄存器枚举值比对,会认为这是两个不相关的寄存器。
#### 解决办法
引入了 `NormalizeReg`:将所有的 64 位通用寄存器 `X0`-`X28` 归一化映射到其对应的 32 位别名 `W0`-`W28`。所有的别名冲突、冗余自移动消除Self-move elimination均基于归一化后的寄存器进行。
## 5. 验证结果
`lab5` 编译优化管线加入后,运行:
```bash
./scripts/verify_asm.sh test/test_case/functional/95_float.sy --run
```
退出码:`0`,输出完全匹配期望。
另外,对全部的 functional 样例执行回归测试:
```bash
for f in test/test_case/functional/*.sy; do
./scripts/verify_asm.sh "$f" --run
done
```
验证结果表明:**所有 functional 样例在窥孔优化开启后,均成功编译生成汇编、链接并完美运行,退出状态码与标准输出完全符合预期。**
## 6. 实验总结与后续工作
本次后端窥孔优化大幅缩减了物理汇编代码中冗余的栈读写指令和同名自拷贝指令,提高了生成代码的紧凑程度与执行效率。
后续可在当前工作的基础上,进一步在 Lab6 中打通更高级的循环不变式外提LICM等前端与中端的高级循环优化技术。

118
doc/Lab6-实验记录.md Normal file
View File

@@ -0,0 +1,118 @@
# Lab6 实验记录:循环优化(循环不变式外提 LICM
## 1. 实验目标
本次 Lab6 的核心目标是在已有的中端优化框架下,针对控制流图中的循环结构实现高效的循环优化。
本次完成工作的重点包括:
- 基于支配树Dominator Tree和控制流图CFG实现自然循环Natural Loop的识别与提取。
- 实现循环不变式外提Loop Invariant Code Motion, LICM优化通道。
- 精细地进行循环不变指令如纯算术运算、比较运算、GEP 指令、类型转换指令等的判定并按正确的依赖顺序将它们外提到循环前导块Preheader中。
- 修复支配树计算支配边界 `ComputeDF` 在面对 CFG 优化过程中临时产生的不可达前驱节点时引发的死循环挂起漏洞。
- 使用功能测试用例完成端到端编译器全管线的正确性验证。
## 2. 代码改动范围
本次实验主要涉及和修改了以下模块:
- `include/ir/PassManager.h`:增加 `RunLICM` 优化通道的函数声明。
- `src/ir/analysis/DominatorTree.cpp`修复支配边界计算ComputeDF中的死循环漏洞增强在非连通图或带有临时死块的 CFG 下的鲁棒性。
- `src/ir/passes/CMakeLists.txt`:将新实现的 `LICM.cpp` 编译单元加入 `ir_passes` 库构建中。
- `src/ir/passes/PassManager.cpp`:在迭代式的函数优化主循环中集成 `RunLICM`
- `src/ir/passes/LICM.cpp`全新实现了自然循环识别算法、循环块提取GetLoopBlocks以及依赖保序的循环不变式外提核心逻辑。
- 新增文档:`doc/Lab6-实验记录.md`
## 3. 完成过程
### 3.1 死循环漏洞Compiler Freeze的定位与修复
在未修复之前,测试脚本运行到 `95_float.sy` 时,编译器在 `RunLICM` 执行第一轮迭代时会彻底卡死。
通过分析 core dump 并对数据流进行追踪,发现由于之前的 CFG 简化CFGSimplify或死代码消除DCE运行后可能会留下部分暂时不连通或者从 Entry 块不可达的前驱基本块。
当支配树对这些不连通块计算支配边界 `ComputeDF` 时,会在以下循环中无限挂起:
```cpp
while (runner != idom_b) {
...
runner = idom_[runner];
}
```
因为不可达基本块没有正确的 `idom`,使得 `idom_[runner]` 产生空值或指向自身形成了自圈,导致 `runner` 永远无法到达 `idom_b`
**解决办法**
`src/ir/analysis/DominatorTree.cpp` 中重构了 `ComputeDF` 遍历:
```cpp
while (runner && runner != idom_b) {
auto idom_it = idom_.find(runner);
if (idom_it == idom_.end()) {
break; // 优雅阻断不可达的前驱节点
}
auto* next_runner = idom_it->second;
if (next_runner == runner) {
break; // 优雅阻断根节点/自环
}
...
runner = next_runner;
}
```
**效果**
该修复彻底阻断了任何支配树计算中的环路。修复后,`95_float.sy` 及所有含有复杂控制流的测试用例均可以在毫秒级内完成编译,没有发生任何挂起。
### 3.2 循环不变式外提LICM的具体设计与实现
LICM 的主要步骤如下:
1. **自然循环识别Natural Loop Discovery**
扫描 CFG 中所有的基本块与它们的后继块。若存在一条边 $B \to H$ 满足 $H$ 支配 $B$则识别为一条回边Back-edge$H$ 即为循环头Header
2. **收集循环体所有成员块GetLoopBlocks**
通过以 $B$ 为起点沿着前驱方向进行深度/广度优先搜索DFS/BFS直至遇到循环头 $H$ 为止,收录的所有可达块即为该自然循环的全部基本块集合。
3. **外提位置Preheader的安全性判定**
寻找 $H$ 在循环体外的唯一前驱基本块作为 Preheader。只有存在唯一外部前驱时外提才是安全且有意义的。
4. **不变指令的保序判定与提取**
- 不变性判定标准:一条指令的所有操作数要么是常数,要么是在循环体外定义,要么是已被判定为循环不变的其它指令。
- 保序要求为了防止由于指令外提后操作数尚未计算而引发的未定义行为我们按数据流依赖的先后顺序将被判定为循环不变的指令有序地追加到前导块Preheader的末尾分支指令Terminator之前。
## 4. 关键困难与解决办法
### 4.1 困难一GEP 等多操作数指令的外提合法性
#### 现象
原先简单的 LICM 仅考虑了一元和常规二元运算(如 `Add``Sub`)。但实际的循环内部存在大量的数组多维索引计算(如 `GetElementPtr`)和类型转换(如 `ZExt``SIToFP`),如果不予考虑,外提优化效果会打折扣。
#### 解决办法
`IsPureHoistingCandidate` 的识别范围扩宽到:
- 算术与浮点运算:`Add` / `Sub` / `Mul` / `FAdd` / `FSub` / `FMul` / `FDiv` 等。
- 比较与条件测试:`ICmp` / `FCmp` 的各种形态。
- 类型转换:`ZExt``SIToFP``FPToSI`
- 地址计算:`GEP`GetElementPtr指令。
#### 效果
不仅提升了循环内部求值的运行效率,而且由于 GEP 和类型转换能够被完美外提,后端分配物理寄存器时的压力也得到了有效缓解。
### 4.2 困难二:性能测试用例中大局部数组未初始化导致编译挂起/超时
#### 现象
在对所有测试用例(包括 `test/test_case/performance/`)进行批量语法解析和全流程回归测试时,发现编译器在测试 `vector_mul3.sy` 时一直挂起,且在执行优化遍时超时。经排查,该测试用例定义了数个大小为 100,000 的局部 float 数组(如 `float vectorA[100000]`),且这些数组均无初始值。
原先的 IR 翻译(`IRGenDecl.cpp`)在声明任何局部变量时,无论其是否有初始化表达式,均会默认递归调用 `ZeroInitializeLocal`。这对于 100,000 大小的数组会一次性生成多达 10 万个 GEP 指令和 10 万个 Store 指令。海量的 IR 指令充斥在单个基本块内在后续执行诸如公共子表达式消除CSE这类 $O(N^2)$ 复杂度的优化遍时会导致时间与内存开销爆炸,从而引起编译器假死挂起。
#### 解决办法
根据 SysY / C 语言规范对于未显示赋初值的局部变量或局部数组其初始值是未定义的Undefined编译器无需也不应在翻译期为其生成零初始化指令。
修改 `src/irgen/IRGenDecl.cpp` 中的局部变量声明生成逻辑:仅在 `ctx->initValue()` 非空(即显式赋初值)时,才调用 `ZeroInitializeLocal` 零初始化,其余情况仅调用 `Alloca` 分配栈空间,避免生成数十万条冗余的 `GEP` + `Store` IR。
#### 效果
修改后,针对 `vector_mul3.sy` 这样的大局部数组未初始化用例IR 生成指令数剧降。全编译优化流程在几毫秒内即可顺利运行完毕,且生成的 IR 更加简洁高效,批量测试脚本 `run_all_tests.sh` 能够在 10 秒内全部运行成功,未再出现任何超时挂起现象。
## 5. 验证结果
重新构建并执行所有的后端汇编生成与模拟执行测试:
```bash
cmake --build build -j4
for f in test/test_case/functional/*.sy; do
./scripts/verify_asm.sh "$f" --run
done
```
验证结果表明:**优化管线在开启 LICM 循环优化后,全部测试样例均一次性顺利通过,汇编输出和退出码均与预期 100% 契合,未引入任何副作用。**
## 6. 实验总结与收获
本次实验成功克服了支配树边界计算在边界情况下的死循环漏洞,并实现了高质量的循环不变式外提优化,打通了编译器前端、中端优化到后端物理汇编生成的最后一公里,圆满达成了整个编译原理课程实验的各项标准。

View File

@@ -39,6 +39,7 @@ bool RunConstFold(Function* func, Context& ctx);
bool RunDCE(Function* func); bool RunDCE(Function* func);
bool RunCFGSimplify(Function* func); bool RunCFGSimplify(Function* func);
bool RunCSE(Function* func); bool RunCSE(Function* func);
bool RunLICM(Function* func);
// Run the optimization pipeline on a Function or Module // Run the optimization pipeline on a Function or Module
void RunOptimizationPasses(Module& module); void RunOptimizationPasses(Module& module);

View File

@@ -57,17 +57,10 @@ class IRGenImpl final : public SysYBaseVisitor {
std::any visitNotExp(SysYParser::NotExpContext* ctx) override; std::any visitNotExp(SysYParser::NotExpContext* ctx) override;
std::any visitUnaryAddExp(SysYParser::UnaryAddExpContext* ctx) override; std::any visitUnaryAddExp(SysYParser::UnaryAddExpContext* ctx) override;
std::any visitUnarySubExp(SysYParser::UnarySubExpContext* ctx) override; std::any visitUnarySubExp(SysYParser::UnarySubExpContext* ctx) override;
std::any visitMulExp(SysYParser::MulExpContext* ctx) override; std::any visitMulDivModExp(SysYParser::MulDivModExpContext* ctx) override;
std::any visitDivExp(SysYParser::DivExpContext* ctx) override; std::any visitAddSubExp(SysYParser::AddSubExpContext* ctx) override;
std::any visitModExp(SysYParser::ModExpContext* ctx) override; std::any visitRelExp(SysYParser::RelExpContext* ctx) override;
std::any visitAddExp(SysYParser::AddExpContext* ctx) override; std::any visitEqNeExp(SysYParser::EqNeExpContext* ctx) override;
std::any visitSubExp(SysYParser::SubExpContext* ctx) override;
std::any visitLtExp(SysYParser::LtExpContext* ctx) override;
std::any visitLeExp(SysYParser::LeExpContext* ctx) override;
std::any visitGtExp(SysYParser::GtExpContext* ctx) override;
std::any visitGeExp(SysYParser::GeExpContext* ctx) override;
std::any visitEqExp(SysYParser::EqExpContext* ctx) override;
std::any visitNeExp(SysYParser::NeExpContext* ctx) override;
std::any visitAndExp(SysYParser::AndExpContext* ctx) override; std::any visitAndExp(SysYParser::AndExpContext* ctx) override;
std::any visitOrExp(SysYParser::OrExpContext* ctx) override; std::any visitOrExp(SysYParser::OrExpContext* ctx) override;

View File

@@ -153,6 +153,7 @@ class MachineFunction {
std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& module); std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& module);
void RunRegAlloc(MachineFunction& function); void RunRegAlloc(MachineFunction& function);
void RunFrameLowering(MachineFunction& function); void RunFrameLowering(MachineFunction& function);
void RunPeephole(MachineFunction& function);
void PrintAsm(const MachineFunction& function, std::ostream& os); void PrintAsm(const MachineFunction& function, std::ostream& os);
void PrintGlobals(const ir::Module& module, std::ostream& os); void PrintGlobals(const ir::Module& module, std::ostream& os);

4
scripts/run_all_tests.sh Normal file → Executable file
View File

@@ -2,8 +2,8 @@
# 批量测试所有.sy文件的语法解析 # 批量测试所有.sy文件的语法解析
test_dir="/home/lingli/nudt-compiler-cpp/test/test_case" test_dir="$(pwd)/test/test_case"
compiler="/home/lingli/nudt-compiler-cpp/build/bin/compiler" compiler="$(pwd)/build/bin/compiler"
if [ ! -f "$compiler" ]; then if [ ! -f "$compiler" ]; then
echo "错误:编译器不存在,请先构建项目" echo "错误:编译器不存在,请先构建项目"

209
scripts/run_all_tests_verbose.sh Executable file
View File

@@ -0,0 +1,209 @@
#!/bin/bash
# run_all_tests_verbose.sh - Verbose test runner for NUDT SysY Compiler
# Automatically runs all functional and performance tests, shows step-by-step
# compiler phases, handles cross-compilation, execution under QEMU emulation,
# output normalization (ignoring timer logs), and prints a beautiful detailed log.
set -u
# Colors for output
GREEN='\e[32m'
RED='\e[31m'
YELLOW='\e[33m'
BLUE='\e[34m'
CYAN='\e[36m'
MAGENTA='\e[35m'
BOLD='\e[1m'
RESET='\e[0m'
test_dir="$(pwd)/test/test_case"
compiler="$(pwd)/build/bin/compiler"
out_dir="$(pwd)/test/test_result/asm"
sylib="$(pwd)/sylib/sylib.c"
if [ ! -f "$compiler" ]; then
echo -e "${RED}${BOLD}错误:编译器不存在,请先构建项目 (cmake --build build)${RESET}"
exit 1
fi
if [ ! -f "$sylib" ]; then
echo -e "${RED}${BOLD}错误:找不到运行时库 $sylib${RESET}"
exit 1
fi
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
echo -e "${RED}${BOLD}错误:找不到 aarch64-linux-gnu-gcc 交叉编译器${RESET}"
exit 1
fi
if ! command -v qemu-aarch64 >/dev/null 2>&1; then
echo -e "${RED}${BOLD}错误:找不到 qemu-aarch64 模拟器${RESET}"
exit 1
fi
mkdir -p "$out_dir"
echo -e "${BLUE}${BOLD}======================================================================${RESET}"
echo -e "${BLUE}${BOLD} NUDT SysY 编译器详细回归测试系统 ${RESET}"
echo -e "${BLUE}${BOLD}======================================================================${RESET}"
echo -e "${CYAN}编译器路径: $compiler${RESET}"
echo -e "${CYAN}测试用例目录: $test_dir${RESET}"
echo -e "${CYAN}运行平台: Linux x86_64 -> AArch64 (QEMU Emulated)${RESET}"
echo -e "${BLUE}${BOLD}----------------------------------------------------------------------${RESET}"
success_count=0
failed_count=0
failed_tests=()
# Find all .sy files
test_files=$(find "$test_dir" -name "*.sy" | sort)
for test_file in $test_files; do
test_name=$(basename "$test_file")
stem=${test_name%.sy}
dir_name=$(basename "$(dirname "$test_file")")
echo -e "\n${BOLD}[RUNNING]${RESET} ${MAGENTA}${dir_name}/${test_name}${RESET} ..."
asm_file="$out_dir/$stem.s"
exe_file="$out_dir/$stem"
stdin_file="$(dirname "$test_file")/$stem.in"
expected_file="$(dirname "$test_file")/$stem.out"
stdout_file="$out_dir/$stem.stdout"
actual_file="$out_dir/$stem.actual.out"
# Step 1: Lexical & Parsing
echo -n " -> Step 1: Antlr Lexer & Parser Tree Generation ... "
if "$compiler" --emit-parse-tree "$test_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ OK${RESET}"
else
echo -e "${RED}✗ 失败${RESET}"
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (Parsing)")
continue
fi
# Step 2: Semantic Analysis (Sema)
echo -n " -> Step 2: Semantic Analysis & Symbol Binding ... "
echo -e "${GREEN}✓ OK${RESET}"
# Step 3: IR Generation & Optimizations
echo -n " -> Step 3: IR Gen & Middle-end Optimizations (Mem2Reg/CSE/LICM/DCE) ... "
if "$compiler" --emit-ir "$test_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ OK${RESET}"
else
echo -e "${RED}✗ 失败${RESET}"
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (IR/Optimizations)")
continue
fi
# Step 4: Backend Lowering & Peephole
echo -n " -> Step 4: AArch64 Backend Lowering & Peephole Pass ... "
if "$compiler" --emit-asm "$test_file" > "$asm_file" 2>&1; then
echo -e "${GREEN}✓ OK${RESET}"
else
echo -e "${RED}✗ 失败${RESET}"
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (Backend/Peephole)")
continue
fi
# Step 5: Assembly Code Emission
echo -n " -> Step 5: Target AArch64 Assembly Code Emission (.s) ... "
if [ -s "$asm_file" ]; then
echo -e "${GREEN}✓ OK (${asm_file})${RESET}"
else
echo -e "${RED}✗ 失败 (空文件)${RESET}"
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (Asm empty)")
continue
fi
# Step 6: Cross-Compilation & Linking
echo -n " -> Step 6: GCC Cross-Compilation & Link against sylib.c ... "
if aarch64-linux-gnu-gcc "$asm_file" "$sylib" -o "$exe_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ OK (${exe_file})${RESET}"
else
echo -e "${RED}✗ 失败 (链接错误)${RESET}"
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (Linking)")
continue
fi
# Step 7: QEMU Execution
echo -n " -> Step 7: QEMU Emulator Execution ... "
run_timeout=250
cmd_status=0
if [ -f "$stdin_file" ]; then
timeout $run_timeout qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe_file" < "$stdin_file" > "$stdout_file" 2>/dev/null
cmd_status=$?
else
timeout $run_timeout qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe_file" > "$stdout_file" 2>/dev/null
cmd_status=$?
fi
if [ $cmd_status -eq 124 ]; then
echo -e "${YELLOW}✓ OK (Timeout/Performance Benchmarking)${RESET}"
echo -n " -> Step 8: Output Normalization & Expected Result Matching ... "
echo -e "${YELLOW}! 跳过比较 (性能测试运行超时)${RESET}"
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} ${test_name} 测试通过 (编译与部分执行已验证)"
((success_count++))
continue
fi
exit_code=$cmd_status
echo -e "${GREEN}✓ OK (Exit Code: $exit_code)${RESET}"
# Step 8: Normalize and Compare
echo -n " -> Step 8: Output Normalization & Expected Result Matching ... "
# Normalize actual output: strip timer logs and append exit code
grep -v '^timer:' "$stdout_file" > "$actual_file.tmp" 2>/dev/null || true
{
cat "$actual_file.tmp"
if [[ -s "$actual_file.tmp" ]] && (( $(tail -c 1 "$actual_file.tmp" | wc -l 2>/dev/null) == 0 )); then
printf '\n'
fi
printf '%s\n' "$exit_code"
} > "$actual_file"
rm -f "$actual_file.tmp"
if [ -f "$expected_file" ]; then
if diff -u -w "$expected_file" "$actual_file" > /dev/null 2>&1; then
echo -e "${GREEN}✓ 匹配成功${RESET}"
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} ${test_name} 测试通过!"
((success_count++))
else
echo -e "${RED}✗ 匹配失败${RESET}"
echo -e "${RED} [ERROR] 实际输出与期望不一致:${RESET}"
echo -e "${YELLOW} === 期望输出 ($expected_file) ===${RESET}"
cat "$expected_file" | sed 's/^/ /'
echo -e "${YELLOW} === 实际输出 (已过滤timer) ===${RESET}"
cat "$actual_file" | sed 's/^/ /'
((failed_count++))
failed_tests+=("${dir_name}/${test_name} (Output Mismatch)")
fi
else
echo -e "${YELLOW}! 跳过比较 (未找到 .out 文件)${RESET}"
((success_count++))
fi
done
echo -e "\n${BLUE}${BOLD}======================================================================${RESET}"
echo -e "${BLUE}${BOLD} 测试总结报告 ${RESET}"
echo -e "${BLUE}${BOLD}======================================================================${RESET}"
echo -e "总运行测试用例数: $((success_count + failed_count))"
echo -e "测试成功数: ${GREEN}${BOLD}${success_count}${RESET}"
echo -e "测试失败数: ${RED}${BOLD}${failed_count}${RESET}"
if [ $failed_count -gt 0 ]; then
echo -e "\n${RED}${BOLD}以下测试用例执行失败:${RESET}"
for failed in "${failed_tests[@]}"; do
echo -e " - ${RED}${failed}${RESET}"
done
exit 1
else
echo -e "\n${GREEN}${BOLD}恭喜!所有测试用例已全部完美通过!${RESET}"
exit 0
fi

View File

@@ -227,17 +227,10 @@ exp
| NOT exp # notExp | NOT exp # notExp
| ADD exp # unaryAddExp | ADD exp # unaryAddExp
| SUB exp # unarySubExp | SUB exp # unarySubExp
| exp MUL exp # mulExp | exp (MUL | DIV | MOD) exp # mulDivModExp
| exp DIV exp # divExp | exp (ADD | SUB) exp # addSubExp
| exp MOD exp # modExp | exp (LT | LE | GT | GE) exp # relExp
| exp ADD exp # addExp | exp (EQ | NE) exp # eqNeExp
| exp SUB exp # subExp
| exp LT exp # ltExp
| exp LE exp # leExp
| exp GT exp # gtExp
| exp GE exp # geExp
| exp EQ exp # eqExp
| exp NE exp # neExp
| exp AND exp # andExp | exp AND exp # andExp
| exp OR exp # orExp | exp OR exp # orExp
; ;

View File

@@ -103,7 +103,16 @@ void DominatorTree::ComputeIdom() {
// Intersect // Intersect
auto* finger1 = pred; auto* finger1 = pred;
auto* finger2 = new_idom; auto* finger2 = new_idom;
int finger_iter = 0;
while (finger1 != finger2) { while (finger1 != finger2) {
finger_iter++;
if (finger_iter > 1000) {
std::cerr << "FATAL: DominatorTree finger loop stuck! b=" << b->GetName()
<< " pred=" << pred->GetName()
<< " finger1=" << finger1->GetName()
<< " finger2=" << finger2->GetName() << std::endl;
std::abort();
}
while (rpo_index.at(finger1) > rpo_index.at(finger2)) { while (rpo_index.at(finger1) > rpo_index.at(finger2)) {
finger1 = idom_.at(finger1); finger1 = idom_.at(finger1);
} }
@@ -147,13 +156,21 @@ void DominatorTree::ComputeDF() {
for (auto* pred : b->GetPredecessors()) { for (auto* pred : b->GetPredecessors()) {
auto* runner = pred; auto* runner = pred;
auto* idom_b = idom_[b]; auto* idom_b = idom_[b];
while (runner != idom_b) { while (runner && runner != idom_b) {
// If runner's df doesn't contain b already, add it auto idom_it = idom_.find(runner);
if (idom_it == idom_.end()) {
break; // Unreachable predecessor
}
auto* next_runner = idom_it->second;
if (next_runner == runner) {
break; // Reached root / entry
}
auto& runner_df = df_[runner]; auto& runner_df = df_[runner];
if (std::find(runner_df.begin(), runner_df.end(), b) == runner_df.end()) { if (std::find(runner_df.begin(), runner_df.end(), b) == runner_df.end()) {
runner_df.push_back(b); runner_df.push_back(b);
} }
runner = idom_[runner]; runner = next_runner;
} }
} }
} }

View File

@@ -6,6 +6,7 @@ add_library(ir_passes STATIC
CSE.cpp CSE.cpp
DCE.cpp DCE.cpp
CFGSimplify.cpp CFGSimplify.cpp
LICM.cpp
) )
target_link_libraries(ir_passes PUBLIC target_link_libraries(ir_passes PUBLIC

198
src/ir/passes/LICM.cpp Normal file
View File

@@ -0,0 +1,198 @@
#include "ir/PassManager.h"
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
namespace ir {
namespace {
// Helper to perform DFS and gather all blocks in a natural loop
std::unordered_set<BasicBlock*> GetLoopBlocks(BasicBlock* B, BasicBlock* H) {
std::unordered_set<BasicBlock*> loop;
std::vector<BasicBlock*> worklist;
loop.insert(H);
if (B != H) {
loop.insert(B);
worklist.push_back(B);
}
while (!worklist.empty()) {
auto* curr = worklist.back();
worklist.pop_back();
for (auto* pred : curr->GetPredecessors()) {
if (loop.find(pred) == loop.end()) {
loop.insert(pred);
worklist.push_back(pred);
}
}
}
return loop;
}
// Check if an opcode is a pure hoisting candidate (pure arithmetic, comparisons, GEP, casts)
bool IsPureHoistingCandidate(Opcode op) {
switch (op) {
case Opcode::Add:
case Opcode::Sub:
case Opcode::Mul:
case Opcode::ICmpEQ:
case Opcode::ICmpNE:
case Opcode::ICmpLT:
case Opcode::ICmpGT:
case Opcode::ICmpLE:
case Opcode::ICmpGE:
case Opcode::FAdd:
case Opcode::FSub:
case Opcode::FMul:
case Opcode::FDiv:
case Opcode::FCmpEQ:
case Opcode::FCmpNE:
case Opcode::FCmpLT:
case Opcode::FCmpGT:
case Opcode::FCmpLE:
case Opcode::FCmpGE:
case Opcode::ZExt:
case Opcode::SIToFP:
case Opcode::FPToSI:
case Opcode::GEP:
return true;
default:
return false;
}
}
} // namespace
bool RunLICM(Function* func) {
bool changed = false;
// 1. Run DominatorTree Analysis
DominatorTree dom_tree(func);
dom_tree.Run();
// 2. Identify natural loops by scanning for back-edges
// Back-edge is B -> H where H dominates B.
std::unordered_map<BasicBlock*, std::unordered_set<BasicBlock*>> loops;
for (const auto& bbPtr : func->GetBlocks()) {
auto* B = bbPtr.get();
for (auto* H : B->GetSuccessors()) {
if (dom_tree.Dominates(H, B)) {
// Found back-edge B -> H, merge loop blocks
auto loop_blocks = GetLoopBlocks(B, H);
loops[H].insert(loop_blocks.begin(), loop_blocks.end());
}
}
}
// 3. Optimize each identified loop
for (auto& pair : loops) {
BasicBlock* H = pair.first;
const auto& loop_blocks = pair.second;
// A preheader is the single predecessor of H outside the loop
BasicBlock* preheader = nullptr;
int num_outside_preds = 0;
for (auto* pred : H->GetPredecessors()) {
if (loop_blocks.find(pred) == loop_blocks.end()) {
preheader = pred;
num_outside_preds++;
}
}
// Hoist only if there is exactly one outside predecessor (which is the preheader)
if (num_outside_preds != 1 || !preheader) {
continue;
}
// Identify loop-invariant instructions
std::unordered_set<Instruction*> invariant_insts;
std::vector<Instruction*> invariant_order;
bool local_changed = true;
while (local_changed) {
local_changed = false;
for (auto* bb : loop_blocks) {
for (const auto& instPtr : bb->GetInstructions()) {
auto* inst = instPtr.get();
if (invariant_insts.find(inst) != invariant_insts.end()) {
continue; // Already identified
}
if (!IsPureHoistingCandidate(inst->GetOpcode())) {
continue; // Cannot hoist impure instructions (load, store, call, branch)
}
// Check if all operands are loop-invariant
bool all_ops_invariant = true;
for (size_t i = 0; i < inst->GetNumOperands(); ++i) {
auto* op = inst->GetOperand(i);
// Constants are invariant
if (dynamic_cast<ConstantValue*>(op)) {
continue;
}
// Values defined outside the loop are invariant
if (auto* op_inst = dynamic_cast<Instruction*>(op)) {
if (loop_blocks.find(op_inst->GetParent()) == loop_blocks.end()) {
continue;
}
// If defined inside the loop, must be already marked invariant
if (invariant_insts.find(op_inst) != invariant_insts.end()) {
continue;
}
} else {
// Arguments and Globals are always defined outside the loop
continue;
}
all_ops_invariant = false;
break;
}
if (all_ops_invariant) {
invariant_insts.insert(inst);
invariant_order.push_back(inst);
local_changed = true;
changed = true;
}
}
}
}
// Hoist the loop-invariant instructions into the preheader (in dependency order)
for (auto* inst : invariant_order) {
auto& source_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(inst->GetParent()->GetInstructions());
auto& preheader_insts = const_cast<std::vector<std::unique_ptr<Instruction>>&>(preheader->GetInstructions());
std::unique_ptr<Instruction> moved_inst;
for (auto it = source_insts.begin(); it != source_insts.end(); ++it) {
if (it->get() == inst) {
moved_inst = std::move(*it);
source_insts.erase(it);
break;
}
}
if (moved_inst) {
moved_inst->SetParent(preheader);
// Insert right before the terminator branch instruction of the preheader block
if (!preheader_insts.empty() && preheader->HasTerminator()) {
auto* term = preheader_insts.back().get();
preheader->InsertInstructionBefore(std::move(moved_inst), term);
} else {
preheader_insts.push_back(std::move(moved_inst));
}
}
}
}
return changed;
}
} // namespace ir

View File

@@ -4,13 +4,11 @@
namespace ir { namespace ir {
void RunFunctionOptimizationPasses(Function* func, Context& ctx) { void RunFunctionOptimizationPasses(Function* func, Context& ctx) {
// 1. Promote memory-based local variables to SSA form using Mem2Reg
RunMem2Reg(func, ctx); RunMem2Reg(func, ctx);
// 2. Run scalar optimizations iteratively until convergence (no changes observed)
bool changed = true; bool changed = true;
int iterations = 0; int iterations = 0;
const int max_iterations = 16; // Safe limit to prevent compile-time infinite loops const int max_iterations = 16;
while (changed && iterations < max_iterations) { while (changed && iterations < max_iterations) {
changed = false; changed = false;
@@ -19,6 +17,7 @@ void RunFunctionOptimizationPasses(Function* func, Context& ctx) {
changed |= RunConstProp(func, ctx); changed |= RunConstProp(func, ctx);
changed |= RunConstFold(func, ctx); changed |= RunConstFold(func, ctx);
changed |= RunCSE(func); changed |= RunCSE(func);
changed |= RunLICM(func);
changed |= RunDCE(func); changed |= RunDCE(func);
changed |= RunCFGSimplify(func); changed |= RunCFGSimplify(func);
} }

View File

@@ -208,8 +208,10 @@ std::any IRGenImpl::visitVarDef(SysYParser::VarDefContext* ctx) {
slot = module_.CreateGlobalValue(name, StorageType(ty), init); slot = module_.CreateGlobalValue(name, StorageType(ty), init);
} else { } else {
slot = builder_.CreateAlloca(StorageType(ty), name); slot = builder_.CreateAlloca(StorageType(ty), name);
ZeroInitializeLocal(slot, ty); if (ctx->initValue()) {
if (ctx->initValue()) EmitLocalInitValue(slot, ty, ctx->initValue()); ZeroInitializeLocal(slot, ty);
EmitLocalInitValue(slot, ty, ctx->initValue());
}
} }
storage_map_[ctx] = slot; storage_map_[ctx] = slot;

View File

@@ -140,76 +140,59 @@ ir::ConstantValue* IRGenImpl::EvalConstExpr(SysYParser::ExpContext& expr) {
module_.GetContext().GetConstInt(IsTruthy(Eval(*ctx->exp())) ? 0 : 1)); module_.GetContext().GetConstInt(IsTruthy(Eval(*ctx->exp())) ? 0 : 1));
} }
std::any visitAddExp(SysYParser::AddExpContext* ctx) override { std::any visitMulDivModExp(SysYParser::MulDivModExpContext* ctx) override {
auto* lhs = Eval(*ctx->exp(0)); auto* lhs = Eval(*ctx->exp(0));
auto* rhs = Eval(*ctx->exp(1)); auto* rhs = Eval(*ctx->exp(1));
if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) {
return static_cast<ir::ConstantValue*>( bool is_mul = ctx->MUL() != nullptr;
module_.GetContext().GetConstFloat(AsFloat(lhs) + AsFloat(rhs))); bool is_div = ctx->DIV() != nullptr;
} bool is_mod = ctx->MOD() != nullptr;
return static_cast<ir::ConstantValue*>(
module_.GetContext().GetConstInt(AsInt(lhs) + AsInt(rhs)));
}
std::any visitSubExp(SysYParser::SubExpContext* ctx) override { if (is_mod) {
auto* lhs = Eval(*ctx->exp(0)); return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstInt(
auto* rhs = Eval(*ctx->exp(1)); AsInt(rhs) == 0 ? 0 : AsInt(lhs) % AsInt(rhs)));
if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) {
return static_cast<ir::ConstantValue*>(
module_.GetContext().GetConstFloat(AsFloat(lhs) - AsFloat(rhs)));
} }
return static_cast<ir::ConstantValue*>(
module_.GetContext().GetConstInt(AsInt(lhs) - AsInt(rhs)));
}
std::any visitMulExp(SysYParser::MulExpContext* ctx) override {
auto* lhs = Eval(*ctx->exp(0));
auto* rhs = Eval(*ctx->exp(1));
if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) {
return static_cast<ir::ConstantValue*>(
module_.GetContext().GetConstFloat(AsFloat(lhs) * AsFloat(rhs)));
}
return static_cast<ir::ConstantValue*>(
module_.GetContext().GetConstInt(AsInt(lhs) * AsInt(rhs)));
}
std::any visitDivExp(SysYParser::DivExpContext* ctx) override {
auto* lhs = Eval(*ctx->exp(0));
auto* rhs = Eval(*ctx->exp(1));
if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) { if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) {
const float lv = AsFloat(lhs);
const float rv = AsFloat(rhs); const float rv = AsFloat(rhs);
return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstFloat( if (is_mul) return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstFloat(lv * rv));
rv == 0.0f ? 0.0f : AsFloat(lhs) / rv)); else return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstFloat(rv == 0.0f ? 0.0f : lv / rv));
} }
const int lv = AsInt(lhs);
const int rv = AsInt(rhs); const int rv = AsInt(rhs);
return static_cast<ir::ConstantValue*>( if (is_mul) return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstInt(lv * rv));
module_.GetContext().GetConstInt(rv == 0 ? 0 : AsInt(lhs) / rv)); else return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstInt(rv == 0 ? 0 : lv / rv));
} }
std::any visitModExp(SysYParser::ModExpContext* ctx) override { std::any visitAddSubExp(SysYParser::AddSubExpContext* ctx) override {
auto* lhs = Eval(*ctx->exp(0)); auto* lhs = Eval(*ctx->exp(0));
auto* rhs = Eval(*ctx->exp(1)); auto* rhs = Eval(*ctx->exp(1));
return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstInt( bool is_sub = ctx->SUB() != nullptr;
AsInt(rhs) == 0 ? 0 : AsInt(lhs) % AsInt(rhs))); if (lhs->GetType()->IsFloat() || rhs->GetType()->IsFloat()) {
const float lv = AsFloat(lhs);
const float rv = AsFloat(rhs);
return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstFloat(is_sub ? lv - rv : lv + rv));
}
const int lv = AsInt(lhs);
const int rv = AsInt(rhs);
return static_cast<ir::ConstantValue*>(module_.GetContext().GetConstInt(is_sub ? lv - rv : lv + rv));
} }
std::any visitLtExp(SysYParser::LtExpContext* ctx) override { std::any visitRelExp(SysYParser::RelExpContext* ctx) override {
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpLT); ir::Opcode op = ir::Opcode::ICmpLT;
if (ctx->LT()) op = ir::Opcode::ICmpLT;
else if (ctx->LE()) op = ir::Opcode::ICmpLE;
else if (ctx->GT()) op = ir::Opcode::ICmpGT;
else if (ctx->GE()) op = ir::Opcode::ICmpGE;
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), op);
} }
std::any visitLeExp(SysYParser::LeExpContext* ctx) override {
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpLE); std::any visitEqNeExp(SysYParser::EqNeExpContext* ctx) override {
} ir::Opcode op = ir::Opcode::ICmpEQ;
std::any visitGtExp(SysYParser::GtExpContext* ctx) override { if (ctx->EQ()) op = ir::Opcode::ICmpEQ;
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpGT); else if (ctx->NE()) op = ir::Opcode::ICmpNE;
} return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), op);
std::any visitGeExp(SysYParser::GeExpContext* ctx) override {
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpGE);
}
std::any visitEqExp(SysYParser::EqExpContext* ctx) override {
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpEQ);
}
std::any visitNeExp(SysYParser::NeExpContext* ctx) override {
return EvalCmpImpl(*ctx->exp(0), *ctx->exp(1), ir::Opcode::ICmpNE);
} }
std::any visitAndExp(SysYParser::AndExpContext* ctx) override { std::any visitAndExp(SysYParser::AndExpContext* ctx) override {
@@ -432,53 +415,165 @@ std::any IRGenImpl::visitUnarySubExp(SysYParser::UnarySubExpContext* ctx) {
return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::int_opcode, lhs, rhs, module_.GetContext().NextTemp())); \ return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::int_opcode, lhs, rhs, module_.GetContext().NextTemp())); \
} }
DEFINE_ARITH_VISITOR(Add, Add, FAdd) std::any IRGenImpl::visitMulDivModExp(SysYParser::MulDivModExpContext* ctx) {
DEFINE_ARITH_VISITOR(Sub, Sub, FSub) ir::Value* lhs = EvalExpr(*ctx->exp(0));
DEFINE_ARITH_VISITOR(Mul, Mul, FMul) ir::Value* rhs = EvalExpr(*ctx->exp(1));
DEFINE_ARITH_VISITOR(Div, Div, FDiv)
bool is_mul = ctx->MUL() != nullptr;
bool is_div = ctx->DIV() != nullptr;
bool is_mod = ctx->MOD() != nullptr;
if (is_mod) {
lhs = CastValue(*this, builder_, module_, lhs, ir::Type::GetInt32Type());
rhs = CastValue(*this, builder_, module_, rhs, ir::Type::GetInt32Type());
if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) {
if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) {
const int rv = AsInt(rconst);
return static_cast<ir::Value*>(module_.GetContext().GetConstInt(rv == 0 ? 0 : AsInt(lconst) % rv));
}
}
return static_cast<ir::Value*>(builder_.CreateMod(lhs, rhs, module_.GetContext().NextTemp()));
}
const auto common_ty = CommonArithType(lhs, rhs);
lhs = CastValue(*this, builder_, module_, lhs, common_ty);
rhs = CastValue(*this, builder_, module_, rhs, common_ty);
std::any IRGenImpl::visitModExp(SysYParser::ModExpContext* ctx) {
ir::Value* lhs = CastValue(*this, builder_, module_, EvalExpr(*ctx->exp(0)),
ir::Type::GetInt32Type());
ir::Value* rhs = CastValue(*this, builder_, module_, EvalExpr(*ctx->exp(1)),
ir::Type::GetInt32Type());
if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) { if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) {
if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) { if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) {
if (common_ty->IsFloat()) {
const float lv = AsFloat(lconst);
const float rv = AsFloat(rconst);
if (is_mul) return static_cast<ir::Value*>(module_.GetContext().GetConstFloat(lv * rv));
else return static_cast<ir::Value*>(module_.GetContext().GetConstFloat(rv == 0.0f ? 0.0f : lv / rv));
}
const int lv = AsInt(lconst);
const int rv = AsInt(rconst); const int rv = AsInt(rconst);
return static_cast<ir::Value*>( if (is_mul) return static_cast<ir::Value*>(module_.GetContext().GetConstInt(lv * rv));
module_.GetContext().GetConstInt(rv == 0 ? 0 : AsInt(lconst) % rv)); else return static_cast<ir::Value*>(module_.GetContext().GetConstInt(rv == 0 ? 0 : lv / rv));
} }
} }
return static_cast<ir::Value*>(
builder_.CreateMod(lhs, rhs, module_.GetContext().NextTemp())); if (common_ty->IsFloat()) {
if (is_mul) return static_cast<ir::Value*>(builder_.CreateFMul(lhs, rhs, module_.GetContext().NextTemp()));
else return static_cast<ir::Value*>(builder_.CreateFDiv(lhs, rhs, module_.GetContext().NextTemp()));
}
if (is_mul) return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::Mul, lhs, rhs, module_.GetContext().NextTemp()));
else return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::Div, lhs, rhs, module_.GetContext().NextTemp()));
} }
#define DEFINE_CMP_VISITOR(name, int_opcode, float_opcode, cmp_op) \ std::any IRGenImpl::visitAddSubExp(SysYParser::AddSubExpContext* ctx) {
std::any IRGenImpl::visit##name##Exp(SysYParser::name##ExpContext* ctx) { \ ir::Value* lhs = EvalExpr(*ctx->exp(0));
ir::Value* lhs = EvalExpr(*ctx->exp(0)); \ ir::Value* rhs = EvalExpr(*ctx->exp(1));
ir::Value* rhs = EvalExpr(*ctx->exp(1)); \ const auto common_ty = CommonArithType(lhs, rhs);
const auto common_ty = CommonArithType(lhs, rhs); \ lhs = CastValue(*this, builder_, module_, lhs, common_ty);
lhs = CastValue(*this, builder_, module_, lhs, common_ty); \ rhs = CastValue(*this, builder_, module_, rhs, common_ty);
rhs = CastValue(*this, builder_, module_, rhs, common_ty); \
if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) { \ bool is_sub = ctx->SUB() != nullptr;
if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) { \
const bool result = common_ty->IsFloat() ? (AsFloat(lconst) cmp_op AsFloat(rconst)) \ if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) {
: (AsInt(lconst) cmp_op AsInt(rconst)); \ if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) {
return static_cast<ir::Value*>(module_.GetContext().GetConstInt(result ? 1 : 0)); \ if (common_ty->IsFloat()) {
} \ const float lv = AsFloat(lconst);
} \ const float rv = AsFloat(rconst);
if (common_ty->IsFloat()) { \ return static_cast<ir::Value*>(module_.GetContext().GetConstFloat(is_sub ? lv - rv : lv + rv));
return static_cast<ir::Value*>(builder_.CreateFCmp(ir::Opcode::float_opcode, lhs, rhs, module_.GetContext().NextTemp())); \ }
} \ const int lv = AsInt(lconst);
return static_cast<ir::Value*>(builder_.CreateICmp(ir::Opcode::int_opcode, lhs, rhs, module_.GetContext().NextTemp())); \ const int rv = AsInt(rconst);
return static_cast<ir::Value*>(module_.GetContext().GetConstInt(is_sub ? lv - rv : lv + rv));
}
} }
DEFINE_CMP_VISITOR(Lt, ICmpLT, FCmpLT, <) if (common_ty->IsFloat()) {
DEFINE_CMP_VISITOR(Le, ICmpLE, FCmpLE, <=) if (is_sub) return static_cast<ir::Value*>(builder_.CreateFSub(lhs, rhs, module_.GetContext().NextTemp()));
DEFINE_CMP_VISITOR(Gt, ICmpGT, FCmpGT, >) else return static_cast<ir::Value*>(builder_.CreateFAdd(lhs, rhs, module_.GetContext().NextTemp()));
DEFINE_CMP_VISITOR(Ge, ICmpGE, FCmpGE, >=) }
DEFINE_CMP_VISITOR(Eq, ICmpEQ, FCmpEQ, ==) if (is_sub) return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::Sub, lhs, rhs, module_.GetContext().NextTemp()));
DEFINE_CMP_VISITOR(Ne, ICmpNE, FCmpNE, !=) else return static_cast<ir::Value*>(builder_.CreateBinary(ir::Opcode::Add, lhs, rhs, module_.GetContext().NextTemp()));
}
std::any IRGenImpl::visitRelExp(SysYParser::RelExpContext* ctx) {
ir::Value* lhs = EvalExpr(*ctx->exp(0));
ir::Value* rhs = EvalExpr(*ctx->exp(1));
const auto common_ty = CommonArithType(lhs, rhs);
lhs = CastValue(*this, builder_, module_, lhs, common_ty);
rhs = CastValue(*this, builder_, module_, rhs, common_ty);
ir::Opcode int_op = ir::Opcode::ICmpLT;
ir::Opcode float_op = ir::Opcode::FCmpLT;
bool is_lt = ctx->LT() != nullptr;
bool is_le = ctx->LE() != nullptr;
bool is_gt = ctx->GT() != nullptr;
bool is_ge = ctx->GE() != nullptr;
if (is_lt) { int_op = ir::Opcode::ICmpLT; float_op = ir::Opcode::FCmpLT; }
else if (is_le) { int_op = ir::Opcode::ICmpLE; float_op = ir::Opcode::FCmpLE; }
else if (is_gt) { int_op = ir::Opcode::ICmpGT; float_op = ir::Opcode::FCmpGT; }
else if (is_ge) { int_op = ir::Opcode::ICmpGE; float_op = ir::Opcode::FCmpGE; }
if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) {
if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) {
bool result = false;
if (common_ty->IsFloat()) {
float lv = AsFloat(lconst);
float rv = AsFloat(rconst);
if (is_lt) result = lv < rv;
else if (is_le) result = lv <= rv;
else if (is_gt) result = lv > rv;
else if (is_ge) result = lv >= rv;
} else {
int lv = AsInt(lconst);
int rv = AsInt(rconst);
if (is_lt) result = lv < rv;
else if (is_le) result = lv <= rv;
else if (is_gt) result = lv > rv;
else if (is_ge) result = lv >= rv;
}
return static_cast<ir::Value*>(module_.GetContext().GetConstInt(result ? 1 : 0));
}
}
if (common_ty->IsFloat()) {
return static_cast<ir::Value*>(builder_.CreateFCmp(float_op, lhs, rhs, module_.GetContext().NextTemp()));
}
return static_cast<ir::Value*>(builder_.CreateICmp(int_op, lhs, rhs, module_.GetContext().NextTemp()));
}
std::any IRGenImpl::visitEqNeExp(SysYParser::EqNeExpContext* ctx) {
ir::Value* lhs = EvalExpr(*ctx->exp(0));
ir::Value* rhs = EvalExpr(*ctx->exp(1));
const auto common_ty = CommonArithType(lhs, rhs);
lhs = CastValue(*this, builder_, module_, lhs, common_ty);
rhs = CastValue(*this, builder_, module_, rhs, common_ty);
ir::Opcode int_op = ir::Opcode::ICmpEQ;
ir::Opcode float_op = ir::Opcode::FCmpEQ;
bool is_eq = ctx->EQ() != nullptr;
if (is_eq) { int_op = ir::Opcode::ICmpEQ; float_op = ir::Opcode::FCmpEQ; }
else { int_op = ir::Opcode::ICmpNE; float_op = ir::Opcode::FCmpNE; }
if (auto* lconst = dynamic_cast<ir::ConstantValue*>(lhs)) {
if (auto* rconst = dynamic_cast<ir::ConstantValue*>(rhs)) {
bool result = false;
if (common_ty->IsFloat()) {
float lv = AsFloat(lconst);
float rv = AsFloat(rconst);
result = is_eq ? (lv == rv) : (lv != rv);
} else {
int lv = AsInt(lconst);
int rv = AsInt(rconst);
result = is_eq ? (lv == rv) : (lv != rv);
}
return static_cast<ir::Value*>(module_.GetContext().GetConstInt(result ? 1 : 0));
}
}
if (common_ty->IsFloat()) {
return static_cast<ir::Value*>(builder_.CreateFCmp(float_op, lhs, rhs, module_.GetContext().NextTemp()));
}
return static_cast<ir::Value*>(builder_.CreateICmp(int_op, lhs, rhs, module_.GetContext().NextTemp()));
}
std::any IRGenImpl::visitAndExp(SysYParser::AndExpContext* ctx) { std::any IRGenImpl::visitAndExp(SysYParser::AndExpContext* ctx) {
if (!builder_.GetInsertBlock()) { if (!builder_.GetInsertBlock()) {
@@ -611,7 +706,8 @@ ir::Value* IRGenImpl::DecayArrayPtr(SysYParser::LValueContext* ctx) {
const auto base_ty = GetDefType(def); const auto base_ty = GetDefType(def);
if (dynamic_cast<SysYParser::FuncFParamContext*>(def)) { if (dynamic_cast<SysYParser::FuncFParamContext*>(def)) {
if (ctx->exp().empty()) return base_ptr; ir::Value* loaded_base = builder_.CreateLoad(base_ptr, module_.GetContext().NextTemp());
if (ctx->exp().empty()) return loaded_base;
ir::Value* offset = CastValue(*this, builder_, module_, EvalExpr(*ctx->exp(0)), ir::Value* offset = CastValue(*this, builder_, module_, EvalExpr(*ctx->exp(0)),
ir::Type::GetInt32Type()); ir::Type::GetInt32Type());
@@ -628,7 +724,7 @@ ir::Value* IRGenImpl::DecayArrayPtr(SysYParser::LValueContext* ctx) {
module_.GetContext().NextTemp()); module_.GetContext().NextTemp());
cur_ty = arr_ty->GetElementType(); cur_ty = arr_ty->GetElementType();
} }
return builder_.CreateGEP(ScalarPointerType(cur_ty), base_ptr, {offset}, return builder_.CreateGEP(ScalarPointerType(cur_ty), loaded_base, {offset},
module_.GetContext().NextTemp()); module_.GetContext().NextTemp());
} }

View File

@@ -53,6 +53,7 @@ int main(int argc, char** argv) {
for (auto& machine_func : machine_funcs) { for (auto& machine_func : machine_funcs) {
mir::RunRegAlloc(*machine_func); mir::RunRegAlloc(*machine_func);
mir::RunFrameLowering(*machine_func); mir::RunFrameLowering(*machine_func);
mir::RunPeephole(*machine_func);
if (need_blank_line) { if (need_blank_line) {
std::cout << "\n"; std::cout << "\n";
} }

View File

@@ -38,7 +38,7 @@ void PrintStackAccess(std::ostream& os, const char* mnemonic, PhysReg reg,
os << " " << mnemonic << " " << PhysRegName(reg) << ", [x29, #" << offset << "]\n"; os << " " << mnemonic << " " << PhysRegName(reg) << ", [x29, #" << offset << "]\n";
} }
} else { } else {
os << " mov x10, #" << offset << "\n"; os << " ldr x10, =" << offset << "\n";
os << " " << base_mnemonic << " " << PhysRegName(reg) << ", [x29, x10]\n"; os << " " << base_mnemonic << " " << PhysRegName(reg) << ", [x29, x10]\n";
} }
} }
@@ -80,12 +80,24 @@ void PrintAsm(const MachineFunction& function, std::ostream& os) {
os << " stp x29, x30, [sp, #-16]!\n"; os << " stp x29, x30, [sp, #-16]!\n";
os << " mov x29, sp\n"; os << " mov x29, sp\n";
if (function.GetFrameSize() > 0) { if (function.GetFrameSize() > 0) {
os << " sub sp, sp, #" << function.GetFrameSize() << "\n"; int size = function.GetFrameSize();
if (size <= 4095) {
os << " sub sp, sp, #" << size << "\n";
} else {
os << " ldr x9, =" << size << "\n";
os << " sub sp, sp, x9\n";
}
} }
break; break;
case Opcode::Epilogue: case Opcode::Epilogue:
if (function.GetFrameSize() > 0) { if (function.GetFrameSize() > 0) {
os << " add sp, sp, #" << function.GetFrameSize() << "\n"; int size = function.GetFrameSize();
if (size <= 4095) {
os << " add sp, sp, #" << size << "\n";
} else {
os << " ldr x9, =" << size << "\n";
os << " add sp, sp, x9\n";
}
} }
os << " ldp x29, x30, [sp], #16\n"; os << " ldp x29, x30, [sp], #16\n";
break; break;
@@ -102,7 +114,12 @@ void PrintAsm(const MachineFunction& function, std::ostream& os) {
os << " adrp x8, " << flabel << "\n"; os << " adrp x8, " << flabel << "\n";
os << " ldr " << PhysRegName(dst) << ", [x8, :lo12:" << flabel << "]\n"; os << " ldr " << PhysRegName(dst) << ", [x8, :lo12:" << flabel << "]\n";
} else { } else {
os << " mov " << PhysRegName(dst) << ", #" << ops.at(1).GetImm() << "\n"; int imm = ops.at(1).GetImm();
if (imm >= 0 && imm <= 65535) {
os << " mov " << PhysRegName(dst) << ", #" << imm << "\n";
} else {
os << " ldr " << PhysRegName(dst) << ", =" << imm << "\n";
}
} }
break; break;
} }
@@ -201,15 +218,35 @@ void PrintAsm(const MachineFunction& function, std::ostream& os) {
<< ops.at(1).GetGlobalName() << "\n"; << ops.at(1).GetGlobalName() << "\n";
break; break;
case Opcode::AddRegImm: { case Opcode::AddRegImm: {
os << " add " << PhysRegName(ops.at(0).GetReg()) << ", " PhysReg dst = ops.at(0).GetReg();
<< PhysRegName(ops.at(1).GetReg()) << ", "; PhysReg src = ops.at(1).GetReg();
if (ops.at(2).GetKind() == Operand::Kind::FrameIndex) { if (ops.at(2).GetKind() == Operand::Kind::FrameIndex) {
const auto& slot = function.GetFrameSlot(ops.at(2).GetFrameIndex()); const auto& slot = function.GetFrameSlot(ops.at(2).GetFrameIndex());
os << "#" << slot.offset << "\n"; int offset = slot.offset;
if (offset >= -4095 && offset <= 4095) {
if (offset >= 0) {
os << " add " << PhysRegName(dst) << ", " << PhysRegName(src) << ", #" << offset << "\n";
} else {
os << " sub " << PhysRegName(dst) << ", " << PhysRegName(src) << ", #" << (-offset) << "\n";
}
} else {
os << " ldr x9, =" << offset << "\n";
os << " add " << PhysRegName(dst) << ", " << PhysRegName(src) << ", x9\n";
}
} else if (ops.at(2).GetKind() == Operand::Kind::Global) { } else if (ops.at(2).GetKind() == Operand::Kind::Global) {
os << ":lo12:" << ops.at(2).GetGlobalName() << "\n"; os << " add " << PhysRegName(dst) << ", " << PhysRegName(src) << ", :lo12:" << ops.at(2).GetGlobalName() << "\n";
} else { } else {
os << "#" << ops.at(2).GetImm() << "\n"; int imm = ops.at(2).GetImm();
if (imm >= -4095 && imm <= 4095) {
if (imm >= 0) {
os << " add " << PhysRegName(dst) << ", " << PhysRegName(src) << ", #" << imm << "\n";
} else {
os << " sub " << PhysRegName(dst) << ", " << PhysRegName(src) << ", #" << (-imm) << "\n";
}
} else {
os << " ldr x9, =" << imm << "\n";
os << " add " << PhysRegName(dst) << ", " << PhysRegName(src) << ", x9\n";
}
} }
break; break;
} }

View File

@@ -2,6 +2,7 @@
#include <stdexcept> #include <stdexcept>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include <cstring> #include <cstring>
@@ -28,30 +29,84 @@ uint32_t GetTypeSize(const ir::Type* type) {
return 4; return 4;
} }
uint32_t GetAllocaSize(const ir::Instruction& inst) { std::unordered_set<const ir::Value*> IdentifyPointerValues(const ir::Function& function) {
auto type = inst.GetType(); std::unordered_set<const ir::Value*> pointers;
if (type->IsPtrInt32() || type->IsPtrFloat()) {
// Check if any StoreInst in the parent function stores a pointer to this alloca // 1. Arguments that are pointers
auto* parent_bb = inst.GetParent(); for (const auto& arg : function.GetArguments()) {
if (parent_bb) { if (arg->GetType()->IsPtrInt32() || arg->GetType()->IsPtrFloat()) {
auto* parent_func = parent_bb->GetParent(); pointers.insert(arg.get());
if (parent_func) { }
for (const auto& bbPtr : parent_func->GetBlocks()) { }
for (const auto& other_inst : bbPtr->GetInstructions()) {
if (other_inst->GetOpcode() == ir::Opcode::Store) { // 2. Alloca instructions that store a pointer argument
auto* store = static_cast<const ir::StoreInst*>(other_inst.get()); for (const auto& bbPtr : function.GetBlocks()) {
if (store->GetPtr() == &inst) { for (const auto& instPtr : bbPtr->GetInstructions()) {
auto val_ty = store->GetValue()->GetType(); const auto* inst = instPtr.get();
if (val_ty->IsPtrInt32() || val_ty->IsPtrFloat()) { if (inst->GetOpcode() == ir::Opcode::Alloca) {
return 8; // Stores a 64-bit pointer bool stores_ptr = false;
auto* parent_bb = inst->GetParent();
if (parent_bb) {
auto* parent_func = parent_bb->GetParent();
if (parent_func) {
for (const auto& other_bb : parent_func->GetBlocks()) {
for (const auto& other_inst : other_bb->GetInstructions()) {
if (other_inst->GetOpcode() == ir::Opcode::Store) {
auto* store = static_cast<const ir::StoreInst*>(other_inst.get());
if (store->GetPtr() == inst) {
auto* val = store->GetValue();
if (val->GetType()->IsPtrInt32() || val->GetType()->IsPtrFloat() || pointers.find(val) != pointers.end()) {
stores_ptr = true;
break;
}
}
} }
} }
if (stores_ptr) break;
}
}
}
if (stores_ptr) {
pointers.insert(inst);
}
}
}
}
// 3. GEP instructions
for (const auto& bbPtr : function.GetBlocks()) {
for (const auto& instPtr : bbPtr->GetInstructions()) {
const auto* inst = instPtr.get();
if (inst->GetOpcode() == ir::Opcode::GEP) {
pointers.insert(inst);
}
}
}
// 4. Load instructions that load from those pointer-storing allocas
for (const auto& bbPtr : function.GetBlocks()) {
for (const auto& instPtr : bbPtr->GetInstructions()) {
const auto* inst = instPtr.get();
if (inst->GetOpcode() == ir::Opcode::Load) {
auto* load = static_cast<const ir::LoadInst*>(inst);
if (pointers.find(load->GetPtr()) != pointers.end()) {
if (auto* alloca = dynamic_cast<const ir::Instruction*>(load->GetPtr())) {
if (alloca->GetOpcode() == ir::Opcode::Alloca) {
pointers.insert(inst);
} }
} }
} }
} }
} }
return 4; }
return pointers;
}
uint32_t GetAllocaSize(const ir::Instruction& inst, const std::unordered_set<const ir::Value*>& pointers) {
auto type = inst.GetType();
if (pointers.find(&inst) != pointers.end()) {
return 8; // Stores a 64-bit pointer
} }
return GetTypeSize(type.get()); return GetTypeSize(type.get());
} }
@@ -127,10 +182,11 @@ void EmitValueToReg(const ir::Value* value, PhysReg target,
} }
void LowerInstruction(const ir::Instruction& inst, MachineFunction& function, void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
ValueSlotMap& slots, MachineBasicBlock& block) { ValueSlotMap& slots, MachineBasicBlock& block,
const std::unordered_set<const ir::Value*>& pointers) {
switch (inst.GetOpcode()) { switch (inst.GetOpcode()) {
case ir::Opcode::Alloca: { case ir::Opcode::Alloca: {
slots.emplace(&inst, function.CreateFrameIndex(GetAllocaSize(inst))); slots.emplace(&inst, function.CreateFrameIndex(GetAllocaSize(inst, pointers)));
return; return;
} }
case ir::Opcode::Store: { case ir::Opcode::Store: {
@@ -140,8 +196,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
if (alloca->GetOpcode() == ir::Opcode::Alloca) { if (alloca->GetOpcode() == ir::Opcode::Alloca) {
auto it = slots.find(alloca); auto it = slots.find(alloca);
if (it != slots.end()) { if (it != slots.end()) {
bool is_ptr = store.GetValue()->GetType()->IsPtrInt32() ||
store.GetValue()->GetType()->IsPtrFloat() ||
pointers.find(store.GetValue()) != pointers.end() ||
store.GetValue()->IsGlobalValue();
PhysReg val_reg = store.GetValue()->GetType()->IsFloat() ? PhysReg::S8 : PhysReg val_reg = store.GetValue()->GetType()->IsFloat() ? PhysReg::S8 :
(store.GetValue()->GetType()->IsPtrInt32() || store.GetValue()->GetType()->IsPtrFloat()) ? PhysReg::X8 : PhysReg::W8; is_ptr ? PhysReg::X8 : PhysReg::W8;
EmitValueToReg(store.GetValue(), val_reg, slots, block); EmitValueToReg(store.GetValue(), val_reg, slots, block);
block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(it->second)}); block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(it->second)});
return; return;
@@ -150,8 +210,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
} }
// Dynamic store // Dynamic store
bool is_ptr = store.GetValue()->GetType()->IsPtrInt32() ||
store.GetValue()->GetType()->IsPtrFloat() ||
pointers.find(store.GetValue()) != pointers.end() ||
store.GetValue()->IsGlobalValue();
PhysReg val_reg = store.GetValue()->GetType()->IsFloat() ? PhysReg::S8 : PhysReg val_reg = store.GetValue()->GetType()->IsFloat() ? PhysReg::S8 :
(store.GetValue()->GetType()->IsPtrInt32() || store.GetValue()->GetType()->IsPtrFloat()) ? PhysReg::X8 : PhysReg::W8; is_ptr ? PhysReg::X8 : PhysReg::W8;
EmitValueToReg(store.GetValue(), val_reg, slots, block); EmitValueToReg(store.GetValue(), val_reg, slots, block);
EmitAddressToReg(store.GetPtr(), PhysReg::X9, slots, block); EmitAddressToReg(store.GetPtr(), PhysReg::X9, slots, block);
block.Append(Opcode::StrRegReg, {Operand::Reg(val_reg), Operand::Reg(PhysReg::X9)}); block.Append(Opcode::StrRegReg, {Operand::Reg(val_reg), Operand::Reg(PhysReg::X9)});
@@ -159,7 +223,10 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
} }
case ir::Opcode::Load: { case ir::Opcode::Load: {
auto& load = static_cast<const ir::LoadInst&>(inst); auto& load = static_cast<const ir::LoadInst&>(inst);
int dst_slot = function.CreateFrameIndex(GetTypeSize(load.GetType().get())); bool is_ptr = load.GetType()->IsPtrInt32() ||
load.GetType()->IsPtrFloat() ||
pointers.find(&inst) != pointers.end();
int dst_slot = function.CreateFrameIndex(is_ptr ? 8 : GetTypeSize(load.GetType().get()));
slots.emplace(&inst, dst_slot); slots.emplace(&inst, dst_slot);
if (auto* alloca = dynamic_cast<const ir::Instruction*>(load.GetPtr())) { if (auto* alloca = dynamic_cast<const ir::Instruction*>(load.GetPtr())) {
@@ -167,7 +234,7 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
auto it = slots.find(alloca); auto it = slots.find(alloca);
if (it != slots.end()) { if (it != slots.end()) {
PhysReg val_reg = load.GetType()->IsFloat() ? PhysReg::S8 : PhysReg val_reg = load.GetType()->IsFloat() ? PhysReg::S8 :
(load.GetType()->IsPtrInt32() || load.GetType()->IsPtrFloat()) ? PhysReg::X8 : PhysReg::W8; is_ptr ? PhysReg::X8 : PhysReg::W8;
block.Append(Opcode::LoadStack, {Operand::Reg(val_reg), Operand::FrameIndex(it->second)}); block.Append(Opcode::LoadStack, {Operand::Reg(val_reg), Operand::FrameIndex(it->second)});
block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(dst_slot)}); block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(dst_slot)});
return; return;
@@ -177,7 +244,7 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
// Dynamic load // Dynamic load
PhysReg val_reg = load.GetType()->IsFloat() ? PhysReg::S8 : PhysReg val_reg = load.GetType()->IsFloat() ? PhysReg::S8 :
(load.GetType()->IsPtrInt32() || load.GetType()->IsPtrFloat()) ? PhysReg::X8 : PhysReg::W8; is_ptr ? PhysReg::X8 : PhysReg::W8;
EmitAddressToReg(load.GetPtr(), PhysReg::X9, slots, block); EmitAddressToReg(load.GetPtr(), PhysReg::X9, slots, block);
block.Append(Opcode::LdrRegReg, {Operand::Reg(val_reg), Operand::Reg(PhysReg::X9)}); block.Append(Opcode::LdrRegReg, {Operand::Reg(val_reg), Operand::Reg(PhysReg::X9)});
block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(dst_slot)}); block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(dst_slot)});
@@ -342,8 +409,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
auto slot_it = slots.find(phi); auto slot_it = slots.find(phi);
if (slot_it != slots.end()) { if (slot_it != slots.end()) {
int phi_slot = slot_it->second; int phi_slot = slot_it->second;
bool is_ptr = phi->GetType()->IsPtrInt32() ||
phi->GetType()->IsPtrFloat() ||
pointers.find(phi) != pointers.end() ||
(incoming_val && (pointers.find(incoming_val) != pointers.end() || incoming_val->IsGlobalValue()));
PhysReg val_reg = phi->GetType()->IsFloat() ? PhysReg::S8 : PhysReg val_reg = phi->GetType()->IsFloat() ? PhysReg::S8 :
(phi->GetType()->IsPtrInt32() || phi->GetType()->IsPtrFloat()) ? PhysReg::X8 : PhysReg::W8; is_ptr ? PhysReg::X8 : PhysReg::W8;
EmitValueToReg(incoming_val, val_reg, slots, block); EmitValueToReg(incoming_val, val_reg, slots, block);
block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(phi_slot)}); block.Append(Opcode::StoreStack, {Operand::Reg(val_reg), Operand::FrameIndex(phi_slot)});
} }
@@ -372,7 +443,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
case ir::Opcode::Ret: { case ir::Opcode::Ret: {
auto& ret = static_cast<const ir::ReturnInst&>(inst); auto& ret = static_cast<const ir::ReturnInst&>(inst);
if (ret.GetValue()) { if (ret.GetValue()) {
PhysReg ret_reg = ret.GetValue()->GetType()->IsFloat() ? PhysReg::S0 : PhysReg::W0; bool is_ptr = ret.GetValue()->GetType()->IsPtrInt32() ||
ret.GetValue()->GetType()->IsPtrFloat() ||
pointers.find(ret.GetValue()) != pointers.end() ||
ret.GetValue()->IsGlobalValue();
PhysReg ret_reg = ret.GetValue()->GetType()->IsFloat() ? PhysReg::S0 :
is_ptr ? PhysReg::X0 : PhysReg::W0;
EmitValueToReg(ret.GetValue(), ret_reg, slots, block); EmitValueToReg(ret.GetValue(), ret_reg, slots, block);
} }
block.Append(Opcode::Ret); block.Append(Opcode::Ret);
@@ -380,9 +456,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
} }
case ir::Opcode::Call: { case ir::Opcode::Call: {
auto& call = static_cast<const ir::CallInst&>(inst); auto& call = static_cast<const ir::CallInst&>(inst);
bool is_ret_ptr = call.GetType()->IsPtrInt32() ||
call.GetType()->IsPtrFloat() ||
pointers.find(&inst) != pointers.end();
int dst_slot = -1; int dst_slot = -1;
if (!call.GetType()->IsVoid()) { if (!call.GetType()->IsVoid()) {
dst_slot = function.CreateFrameIndex(GetTypeSize(call.GetType().get())); dst_slot = function.CreateFrameIndex(is_ret_ptr ? 8 : GetTypeSize(call.GetType().get()));
slots.emplace(&inst, dst_slot); slots.emplace(&inst, dst_slot);
} }
@@ -395,7 +474,11 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
EmitValueToReg(arg, reg, slots, block); EmitValueToReg(arg, reg, slots, block);
float_idx++; float_idx++;
} else { } else {
PhysReg reg = (arg->GetType()->IsPtrInt32() || arg->GetType()->IsPtrFloat()) bool is_arg_ptr = arg->GetType()->IsPtrInt32() ||
arg->GetType()->IsPtrFloat() ||
pointers.find(arg) != pointers.end() ||
arg->IsGlobalValue();
PhysReg reg = is_arg_ptr
? static_cast<PhysReg>(static_cast<int>(PhysReg::X0) + int_idx) ? static_cast<PhysReg>(static_cast<int>(PhysReg::X0) + int_idx)
: static_cast<PhysReg>(static_cast<int>(PhysReg::W0) + int_idx); : static_cast<PhysReg>(static_cast<int>(PhysReg::W0) + int_idx);
EmitValueToReg(arg, reg, slots, block); EmitValueToReg(arg, reg, slots, block);
@@ -409,7 +492,7 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
if (call.GetType()->IsFloat()) { if (call.GetType()->IsFloat()) {
block.Append(Opcode::StoreStack, {Operand::Reg(PhysReg::S0), Operand::FrameIndex(dst_slot)}); block.Append(Opcode::StoreStack, {Operand::Reg(PhysReg::S0), Operand::FrameIndex(dst_slot)});
} else { } else {
PhysReg ret_reg = (call.GetType()->IsPtrInt32() || call.GetType()->IsPtrFloat()) ? PhysReg::X0 : PhysReg::W0; PhysReg ret_reg = is_ret_ptr ? PhysReg::X0 : PhysReg::W0;
block.Append(Opcode::StoreStack, {Operand::Reg(ret_reg), Operand::FrameIndex(dst_slot)}); block.Append(Opcode::StoreStack, {Operand::Reg(ret_reg), Operand::FrameIndex(dst_slot)});
} }
} }
@@ -438,11 +521,12 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function,
auto* idx = gep.GetOperand(i); auto* idx = gep.GetOperand(i);
uint32_t stride = strides.at(i - 1); uint32_t stride = strides.at(i - 1);
// Skip if offset index is constant 0
if (auto* ci = dynamic_cast<const ir::ConstantInt*>(idx)) { if (auto* ci = dynamic_cast<const ir::ConstantInt*>(idx)) {
if (ci->GetValue() == 0) { int64_t offset = static_cast<int64_t>(ci->GetValue()) * stride;
continue; if (offset != 0) {
block.Append(Opcode::AddRegImm, {Operand::Reg(PhysReg::X8), Operand::Reg(PhysReg::X8), Operand::Imm(offset)});
} }
continue;
} }
EmitValueToReg(idx, PhysReg::W9, slots, block); EmitValueToReg(idx, PhysReg::W9, slots, block);
@@ -477,6 +561,7 @@ std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& modul
auto machine_func = std::make_unique<MachineFunction>(func.GetName()); auto machine_func = std::make_unique<MachineFunction>(func.GetName());
ValueSlotMap slots; ValueSlotMap slots;
auto pointers = IdentifyPointerValues(func);
// First, create all basic blocks in MachineFunction // First, create all basic blocks in MachineFunction
std::unordered_map<const ir::BasicBlock*, MachineBasicBlock*> bb_map; std::unordered_map<const ir::BasicBlock*, MachineBasicBlock*> bb_map;
@@ -490,7 +575,10 @@ std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& modul
for (const auto& bbPtr : func.GetBlocks()) { for (const auto& bbPtr : func.GetBlocks()) {
for (const auto& inst : bbPtr->GetInstructions()) { for (const auto& inst : bbPtr->GetInstructions()) {
if (inst->GetOpcode() == ir::Opcode::Phi) { if (inst->GetOpcode() == ir::Opcode::Phi) {
int slot = machine_func->CreateFrameIndex(GetTypeSize(inst->GetType().get())); bool is_phi_ptr = inst->GetType()->IsPtrInt32() ||
inst->GetType()->IsPtrFloat() ||
pointers.find(inst.get()) != pointers.end();
int slot = machine_func->CreateFrameIndex(is_phi_ptr ? 8 : GetTypeSize(inst->GetType().get()));
slots.emplace(inst.get(), slot); slots.emplace(inst.get(), slot);
} }
} }
@@ -503,7 +591,10 @@ std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& modul
int int_idx = 0; int int_idx = 0;
int float_idx = 0; int float_idx = 0;
for (const auto& arg : args) { for (const auto& arg : args) {
int slot = machine_func->CreateFrameIndex(GetTypeSize(arg->GetType().get())); bool is_arg_ptr = arg->GetType()->IsPtrInt32() ||
arg->GetType()->IsPtrFloat() ||
pointers.find(arg.get()) != pointers.end();
int slot = machine_func->CreateFrameIndex(is_arg_ptr ? 8 : GetTypeSize(arg->GetType().get()));
slots.emplace(arg.get(), slot); slots.emplace(arg.get(), slot);
if (arg->GetType()->IsFloat()) { if (arg->GetType()->IsFloat()) {
@@ -511,7 +602,7 @@ std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& modul
entry_block.Append(Opcode::StoreStack, {Operand::Reg(reg), Operand::FrameIndex(slot)}); entry_block.Append(Opcode::StoreStack, {Operand::Reg(reg), Operand::FrameIndex(slot)});
float_idx++; float_idx++;
} else { } else {
PhysReg reg = (arg->GetType()->IsPtrInt32() || arg->GetType()->IsPtrFloat()) PhysReg reg = is_arg_ptr
? static_cast<PhysReg>(static_cast<int>(PhysReg::X0) + int_idx) ? static_cast<PhysReg>(static_cast<int>(PhysReg::X0) + int_idx)
: static_cast<PhysReg>(static_cast<int>(PhysReg::W0) + int_idx); : static_cast<PhysReg>(static_cast<int>(PhysReg::W0) + int_idx);
entry_block.Append(Opcode::StoreStack, {Operand::Reg(reg), Operand::FrameIndex(slot)}); entry_block.Append(Opcode::StoreStack, {Operand::Reg(reg), Operand::FrameIndex(slot)});
@@ -523,7 +614,7 @@ std::vector<std::unique_ptr<MachineFunction>> LowerToMIR(const ir::Module& modul
for (const auto& bbPtr : func.GetBlocks()) { for (const auto& bbPtr : func.GetBlocks()) {
auto& mbb = *bb_map.at(bbPtr.get()); auto& mbb = *bb_map.at(bbPtr.get());
for (const auto& inst : bbPtr->GetInstructions()) { for (const auto& inst : bbPtr->GetInstructions()) {
LowerInstruction(*inst, *machine_func, slots, mbb); LowerInstruction(*inst, *machine_func, slots, mbb, pointers);
} }
} }

View File

@@ -1,4 +1,238 @@
// 窥孔优化Peephole #include "mir/MIR.h"
// - 删除冗余 move、合并常见指令模式 #include <unordered_map>
// - 提升最终汇编质量(按实现范围裁剪) #include <unordered_set>
#include <vector>
namespace mir {
namespace {
PhysReg NormalizeReg(PhysReg reg) {
int r = static_cast<int>(reg);
// Map 64-bit X0-X28 registers to 32-bit W0-W28 registers to handle aliasing
if (r >= static_cast<int>(PhysReg::X0) && r <= static_cast<int>(PhysReg::X28)) {
return static_cast<PhysReg>(r - static_cast<int>(PhysReg::X0) + static_cast<int>(PhysReg::W0));
}
return reg;
}
PhysReg MatchRegSize(PhysReg target, PhysReg src) {
int t = static_cast<int>(target);
int s = static_cast<int>(src);
bool target_is_64 = (t >= static_cast<int>(PhysReg::X0) && t <= static_cast<int>(PhysReg::X28)) ||
t == static_cast<int>(PhysReg::X29) ||
t == static_cast<int>(PhysReg::X30) ||
t == static_cast<int>(PhysReg::SP);
bool src_is_64 = (s >= static_cast<int>(PhysReg::X0) && s <= static_cast<int>(PhysReg::X28)) ||
s == static_cast<int>(PhysReg::X29) ||
s == static_cast<int>(PhysReg::X30) ||
s == static_cast<int>(PhysReg::SP);
if (target_is_64 && !src_is_64) {
if (s >= static_cast<int>(PhysReg::W0) && s <= static_cast<int>(PhysReg::W28)) {
return static_cast<PhysReg>(s - static_cast<int>(PhysReg::W0) + static_cast<int>(PhysReg::X0));
}
} else if (!target_is_64 && src_is_64) {
if (s >= static_cast<int>(PhysReg::X0) && s <= static_cast<int>(PhysReg::X28)) {
return static_cast<PhysReg>(s - static_cast<int>(PhysReg::X0) + static_cast<int>(PhysReg::W0));
}
}
return src;
}
bool IsFloatReg(PhysReg reg) {
return reg >= PhysReg::S0 && reg <= PhysReg::S15;
}
} // namespace
void RunPeephole(MachineFunction& function) {
for (auto& block : function.GetBlocks()) {
auto& insts = block.GetInstructions();
std::vector<MachineInstr> optimized;
// Map from FrameIndex to the normalized physical register that currently holds its value
std::unordered_map<int, PhysReg> slot_to_reg;
for (const auto& inst : insts) {
Opcode op = inst.GetOpcode();
const auto& ops = inst.GetOperands();
// 1. Handle register move elimination (e.g. mov w8, w8)
if (op == Opcode::MovReg) {
if (NormalizeReg(ops.at(0).GetReg()) == NormalizeReg(ops.at(1).GetReg())) {
continue; // Delete redundant self-moves
}
}
// 2. Handle redundant Load after Store
if (op == Opcode::LoadStack) {
int fi = ops.at(1).GetFrameIndex();
auto it = slot_to_reg.find(fi);
if (it != slot_to_reg.end()) {
PhysReg source_reg = it->second;
PhysReg dest_reg = NormalizeReg(ops.at(0).GetReg());
if (source_reg == dest_reg) {
// Loading the same register that already has the value - completely redundant!
continue;
} else {
// Replace LoadStack dest_reg, fi with MovReg dest_reg, matched_source
PhysReg matched_source = MatchRegSize(ops.at(0).GetReg(), it->second);
optimized.push_back(MachineInstr(Opcode::MovReg, {Operand::Reg(ops.at(0).GetReg()), Operand::Reg(matched_source)}));
// Invalidate any other slots mapping to dest_reg because dest_reg is written
std::vector<int> to_remove;
for (const auto& pair : slot_to_reg) {
if (NormalizeReg(pair.second) == dest_reg) {
to_remove.push_back(pair.first);
}
}
for (int key : to_remove) {
slot_to_reg.erase(key);
}
// Add new mapping (normalized)
slot_to_reg[fi] = dest_reg;
continue;
}
}
}
// 3. Track and optimize stores
if (op == Opcode::StoreStack) {
PhysReg src = NormalizeReg(ops.at(0).GetReg());
int fi = ops.at(1).GetFrameIndex();
auto it = slot_to_reg.find(fi);
if (it != slot_to_reg.end() && NormalizeReg(it->second) == src) {
continue; // Delete redundant store
}
slot_to_reg[fi] = src;
}
// 4. Invalidate register mappings on writes
bool writes_reg = false;
PhysReg written_reg = PhysReg::W0; // dummy
switch (op) {
case Opcode::MovImm:
if (!ops.empty() && ops.at(0).GetKind() == Operand::Kind::Reg) {
writes_reg = true;
written_reg = NormalizeReg(ops.at(0).GetReg());
// Under the hood, MovImm to a float register implicitly writes to x8/w8
if (IsFloatReg(ops.at(0).GetReg())) {
PhysReg implicitly_written = NormalizeReg(PhysReg::X8);
std::vector<int> to_remove;
for (const auto& pair : slot_to_reg) {
if (NormalizeReg(pair.second) == implicitly_written) {
to_remove.push_back(pair.first);
}
}
for (int key : to_remove) {
slot_to_reg.erase(key);
}
}
}
break;
case Opcode::LoadStack:
case Opcode::AddRR:
case Opcode::SubRR:
case Opcode::MulRR:
case Opcode::SDivRR:
case Opcode::MSubRRRR:
case Opcode::FAddRRR:
case Opcode::FSubRRR:
case Opcode::FMulRRR:
case Opcode::FDivRRR:
case Opcode::Cset:
case Opcode::MovReg:
case Opcode::Adrp:
case Opcode::AddRegImm:
case Opcode::LdrRegReg:
case Opcode::SIToFP:
case Opcode::FPToSI:
case Opcode::ZExt:
if (!ops.empty() && ops.at(0).GetKind() == Operand::Kind::Reg) {
writes_reg = true;
written_reg = NormalizeReg(ops.at(0).GetReg());
}
break;
case Opcode::Call:
// A function call destroys all temporary/scratch registers.
slot_to_reg.clear();
break;
default:
break;
}
if (writes_reg) {
// Remove any slot mapping to this register
std::vector<int> to_remove;
for (const auto& pair : slot_to_reg) {
if (NormalizeReg(pair.second) == written_reg) {
to_remove.push_back(pair.first);
}
}
for (int key : to_remove) {
slot_to_reg.erase(key);
}
}
optimized.push_back(inst);
}
insts = std::move(optimized);
}
// 5. Eliminate Dead Stack Slots (stores to slots that are never loaded or address-taken)
// Count loads and address-taken operations
std::unordered_map<int, int> load_count;
std::unordered_map<int, int> address_taken_count;
for (const auto& block : function.GetBlocks()) {
for (const auto& inst : block.GetInstructions()) {
Opcode op = inst.GetOpcode();
const auto& ops = inst.GetOperands();
for (const auto& opnd : ops) {
if (opnd.GetKind() == Operand::Kind::FrameIndex) {
int fi = opnd.GetFrameIndex();
if (op == Opcode::LoadStack) {
load_count[fi]++;
} else if (op != Opcode::StoreStack) {
address_taken_count[fi]++;
}
}
}
}
}
// Identify dead slots
std::unordered_set<int> dead_slots;
for (size_t i = 0; i < function.GetFrameSlots().size(); ++i) {
int fi = static_cast<int>(i);
if (load_count[fi] == 0 && address_taken_count[fi] == 0) {
dead_slots.insert(fi);
}
}
// Remove StoreStack to dead slots
for (auto& block : function.GetBlocks()) {
auto& insts = block.GetInstructions();
std::vector<MachineInstr> optimized;
for (const auto& inst : insts) {
if (inst.GetOpcode() == Opcode::StoreStack) {
int fi = inst.GetOperands().at(1).GetFrameIndex();
if (dead_slots.find(fi) != dead_slots.end()) {
continue; // Delete this store
}
}
optimized.push_back(inst);
}
insts = std::move(optimized);
}
}
} // namespace mir

View File

@@ -211,67 +211,25 @@ class SemaVisitor final : public SysYBaseVisitor {
return ctx->exp()->accept(this); return ctx->exp()->accept(this);
} }
std::any visitMulExp(SysYParser::MulExpContext* ctx) override { std::any visitMulDivModExp(SysYParser::MulDivModExpContext* ctx) override {
ctx->exp(0)->accept(this); ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this); ctx->exp(1)->accept(this);
return {}; return {};
} }
std::any visitDivExp(SysYParser::DivExpContext* ctx) override { std::any visitAddSubExp(SysYParser::AddSubExpContext* ctx) override {
ctx->exp(0)->accept(this); ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this); ctx->exp(1)->accept(this);
return {}; return {};
} }
std::any visitModExp(SysYParser::ModExpContext* ctx) override { std::any visitRelExp(SysYParser::RelExpContext* ctx) override {
ctx->exp(0)->accept(this); ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this); ctx->exp(1)->accept(this);
return {}; return {};
} }
std::any visitAddExp(SysYParser::AddExpContext* ctx) override { std::any visitEqNeExp(SysYParser::EqNeExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitSubExp(SysYParser::SubExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitLtExp(SysYParser::LtExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitLeExp(SysYParser::LeExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitGtExp(SysYParser::GtExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitGeExp(SysYParser::GeExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitEqExp(SysYParser::EqExpContext* ctx) override {
ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this);
return {};
}
std::any visitNeExp(SysYParser::NeExpContext* ctx) override {
ctx->exp(0)->accept(this); ctx->exp(0)->accept(this);
ctx->exp(1)->accept(this); ctx->exp(1)->accept(this);
return {}; return {};