Compare commits

...

1 Commits

Author SHA1 Message Date
zwz666 4e64601cd7 无测试集,无课件,无题目
1 week ago

@ -1,89 +0,0 @@
# Lab1语法树构建
## 1. 本实验定位
本仓库是一个“最小可运行编译器框架”,当前仅实现 SysY 的极小子集(示例级功能,主要用于演示完整构建流程)。
课程目标不是停留在这个最小子集,而是让同学们在该框架上逐步补全完整 SysY并最终完成完整编译器前端、中端、后端
## 2. Lab1 要求
Lab1 聚焦前端第一步:词法/语法分析。
需要同学完成:
1. 依据 SysY 规范扩展文法 `src/antlr4/SysY.g4`
2. 通过构建流程重新生成 Lexer/Parser。
3. 让更多合法 SysY 程序可以被解析通过(不再仅限当前最小样例)。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `src/antlr4/SysY.g4`
- `src/frontend/AntlrDriver.cpp`
- `src/frontend/SyntaxTreePrinter.cpp`
## 4. 当前示例实现说明
当前仓库仅实现最小子集:
1. 主要覆盖 `int main() { ... }` 这一固定函数形态。
2. 只包含少量声明/返回/表达式能力;当前默认示例主要覆盖简单加法。
3. 示例用例位于 `test/test_case/functional/simple_add.sy`
## 5. 构建与生成流程
Lab1 中需要先生成 Lexer/Parser 相关文件,再执行 CMake 构建。
为了只聚焦语法树构建,建议启用 `parse-only` 模式,仅编译前端解析与语法树打印,不编译 `sem` / `irgen` / `mir`
Lexer/Parser 生成文件统一位于:
- `build/generated/antlr4/`
如需手动生成 Lexer/Parser
```bash
mkdir -p build/generated/antlr4
java -jar third_party/antlr-4.13.2-complete.jar \
-Dlanguage=Cpp \
-visitor -no-listener \
-Xexact-output-dir \
-o build/generated/antlr4 \
src/antlr4/SysY.g4
```
随后执行 Lab1 构建,下面的命令是只编译运行前端:
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=ON
cmake --build build -j "$(nproc)"
```
如果后续需要继续验证 `sem` / `irgen` / `mir`,再使用全量构建。需要注意的是,由于 `irgen` 需要直接遍历 ANTLR 生成的语法树,`sem` / `irgen` 会直接依赖 `SysYParser::*Context` 的节点类型、层级结构和访问接口。因此当 `src/antlr4/SysY.g4` 被扩展后,如果后续阶段代码没有同步适配新的语法树结构,就可能在全量构建或运行时出现报错;这部分适配工作属于 Lab2 及后续实验需要继续完成的内容。
全量构建命令
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
## 6. Lab1 测试建议
先用单个样例检查语法树输出是否基本正常:
1. 运行 `./build/bin/compiler --emit-parse-tree <case.sy>` 检查解析是否成功。
2. 出现报错时优先回查 `SysY.g4` 逻辑。
```bash
# 仅输出语法树
./build/bin/compiler --emit-parse-tree test/test_case/functional/simple_add.sy
```
但最终不能只检查 `simple_add`。完成 Lab1 后,应至少对 `test/test_case` 下全部 `.sy` 用例逐个验证解析是否成功;如有需要,也可以自行编写批量测试脚本统一执行。
## 7. 关于 AST 的建议
同学们也可以自行设计一层抽象语法树AST将 ANTLR 语法树先转换为 AST再进入后续阶段。
这样可以减少对具体文法细节的依赖使语义分析、IR 生成和后续扩展更清晰。这里不做具体要求。

@ -1,109 +0,0 @@
# Lab2中间表示生成
## 1. 本实验定位
Lab2 的目标是在该示例基础上扩展语义覆盖范围,并逐步把更多 SysY 语法正确翻译为 IR。
## 2. Lab2 要求
需要同学完成:
1. 熟悉 IR 相关数据结构与构建接口。
2. 理解当前语法树 -> 语义检查 -> IR 的最小实现流程。
3. 在现有框架上补充语义检查与 IR 生成功能,使其覆盖课程要求的 SysY 语法。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `include/sem/Sema.h`
- `include/sem/SymbolTable.h`
- `src/sem/Sema.cpp`
- `src/sem/SymbolTable.cpp`
- `include/ir/IR.h`
- `src/ir/Context.cpp`
- `src/ir/Value.cpp`
- `src/ir/Instruction.cpp`
- `src/ir/BasicBlock.cpp`
- `src/ir/Function.cpp`
- `src/ir/Module.cpp`
- `src/ir/IRBuilder.cpp`
- `src/ir/IRPrinter.cpp`
- `include/irgen/IRGen.h`
- `src/irgen/IRGenDecl.cpp`
- `src/irgen/IRGenStmt.cpp`
- `src/irgen/IRGenExp.cpp`
- `src/irgen/IRGenFunc.cpp`
- `src/irgen/IRGenDriver.cpp`
## 4. 当前最小示例实现说明
当前语法树 -> 语义检查 -> IR 仅覆盖最小子集:
1. 常量整数、变量引用、二元加法表达式。
2. 局部变量声明(当前采用 LLVM 前端常见的 `alloca/load/store` 内存模型)。
3. `return` 语句。
4. 单函数 `main` 的最小流程。
其中,`sema` 负责最基本的名称绑定与合法性检查,`irgen` 在此基础上继续生成 IR。
如果语义检查阶段没有补全,后续 IR 生成阶段通常也无法正确处理变量引用、声明绑定等逻辑。
当前 `irgen` 的组织方式基于 ANTLR Visitor 的实现。
`IRGenImpl` 继承自 `SysYBaseVisitor`,按照语法树节点类型分发到不同的 `visit*` 函数中完成 IR 生成。整体流程大致是:
1. `GenerateIR(tree, sema)` 先创建 `Module`,再构造 `IRGenImpl`
2. 从语法树根节点开始访问,进入 `visitCompUnit`
3. `visitFuncDef``Module` 中创建 `Function`,并把 `IRBuilder` 的插入点设置到入口基本块。
4. `visitBlockStmt` / `visitBlockItem` 顺序遍历块内声明与语句。
5. `visitDecl` / `visitVarDef` 为局部变量生成 `alloca` 和初始化 `store`
6. `visitExp` 相关函数递归生成常量、`load`、`add` 等表达式值。
7. `visitReturnStmt` 生成 `ret`,终结当前基本块。
需要强调的是:当前 `IRGen` 还只是一个教学用的最小实现。它只支持 `int main()`、局部 `int` 变量、整数字面量、变量读取、二元加法与 `return`;函数形参、全局变量、控制流、调用、数组等都还需要同学后续补充。
说明:当前阶段变量统一采用内存模型:先 `alloca` 分配栈槽,再通过 `store/load` 读写。即使变量由常量初始化(如 `int a = 1;`),也会先 `store` 到栈槽,而不是直接把变量替换成 SSA 值。后续实验中,同学可按需求再重构。
此外,当前 IR 还维护了最基本的 use-def 关系:每个 `Value` 会记录自己的 `Use`/`User` 信息,`Instruction` 通过 operand 列表与这些关系自动关联。
这对后续做数据流分析、死代码删除、常量传播等优化会很有帮助;但目前相关实现,接口仍不完整,后续实验中还需要同学继续补充和完善。
## 5. 语法树与 Sema / IRGen 的关系
当前项目中的 `sema``irgen` 都不是面向独立 AST 设计的,而是直接基于 ANTLR 生成的语法树节点,并通过 Visitor 方式完成语义检查与 IR 生成。
因此,`SysY.g4` 中 rule 的命名、层级结构以及 labeled alternative 的写法,会直接影响 `SysYParser::*Context` 的类型名和访问接口;一旦 grammar 发生变化,`sem` / `irgen` 中对应的 `visit*` 逻辑通常也需要同步修改。
如果 grammar 扩展后 `sem` / `irgen` 没有同步修改,常见现象包括:
1. 编译阶段报错,例如某个 `SysYParser::*Context` 类型不存在,或某个成员函数不存在。
2. 运行阶段报错,例如进入 `暂不支持的表达式形式`、`暂不支持的语句类型`,或名称绑定失败等分支。
遇到这类问题时,需要同学对照 `SysY.g4`、ANTLR 生成的 `SysYParser.h`,以及 `src/sem` / `src/irgen` 中的 Visitor 遍历逻辑,完成对应的接口调整与功能补全。
## 6. 构建与运行
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
## 7. Lab2 验证方式
可先用单个样例检查 IR 输出是否基本正确:
```bash
./build/bin/compiler --emit-ir test/test_case/functional/simple_add.sy
```
推荐使用统一脚本验证 “IR -> LLVM 后端 -> 可执行程序” 整体链路。`--run` 模式下会自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对,用于验证 IR 的正确性:
```bash
./scripts/verify_ir.sh test/test_case/functional/simple_add.sy test/test_result/function/ir --run
```
但最终不能只检查 `simple_add`。完成 Lab2 后,应对 `test/test_case` 下全部测试用例逐个回归,确认 IR 生成与 `--run` 链路都能通过;如有需要,也可以自行编写批量测试脚本统一执行。

@ -1,500 +0,0 @@
# Lab2 IR 与测试体系修改说明
## 1. 文档定位
本文档覆盖两类内容:
1. IR 侧的重要实现与优化接入。
2. 测试脚本与测试数据的修改,尤其是测试产物留存策略和 `if-combine2.in` / `if-combine3.in` 的修复原因。
如果只想快速了解当前仓库状态,优先看第 2 节和第 3 节。
---
## 2. 修改重点总览
当前这一轮修改,重点有 4 个:
### 2.1 测试脚本行为重构
目标是让测试脚本更适合持续开发,而不是每次跑完留一堆垃圾文件。
已经完成的行为包括:
1. 成功样例的中间文件自动删除。
2. 失败样例才保留中间文件。
3. 每次测试生成独立日志目录,例如 `lab2_20260407_123456`
4. 每轮测试都会生成完整 `whole.log`
5. 每个失败样例目录里都保留 `error.log`
6. 终端输出增加颜色,`PASS` 绿色,`FAIL` 红色。
7. 支持先重测失败样例,再跑全量。
8. 默认测试范围扩展到 `test/test_case``test/class_test_case` 两棵目录树。
### 2.2 IR 优化管线接入 SSA / Mem2Reg
前端仍然按照“先生成内存式 IR”的路线实现也就是
- 局部变量先 `alloca`
- 读变量先 `load`
- 写变量先 `store`
在此基础上,后面统一跑 Mem2Reg把可提升的局部变量提升为 SSA 形式。这保证了:
1. 前端 IR 生成逻辑保持清晰。
2. SSA 构造集中在优化阶段,不把复杂度压到 visitor 上。
3. 后续做标量优化时IR 形态更适合进一步处理。
### 2.3 测试目录结构扩展
原脚本默认只扫描 `test/test_case`。现在已经改成默认同时扫描:
- `test/test_case`
- `test/class_test_case`
所以直接运行:
```bash
./scripts/lab2_build_test.sh
```
会同时覆盖:
- 原测试集
- 课程/课堂测试集 `class_test_case`
### 2.4 修复两个不自洽的性能测试输入文件
修改了:
- `test/test_case/h_performance/if-combine2.in`
- `test/test_case/h_performance/if-combine3.in`
这两个修改不是“为了让编译器过样例而硬改数据”,而是修复原测试数据与源码不一致的问题。这个点下面会单独详细说明。
---
## 3. 关键修改文件
### 3.1 IR 与优化相关
- `src/ir/passes/PassManager.cpp`
- `src/ir/passes/Mem2Reg.cpp`
### 3.2 Lab2 测试脚本相关
- `scripts/verify_ir.sh`
- `scripts/lab2_build_test.sh`
### 3.3 Lab1 测试脚本同步对齐
- `scripts/lab1_build_test.sh`
### 3.4 测试数据修复
- `test/test_case/h_performance/if-combine2.in`
- `test/test_case/h_performance/if-combine3.in`
文档阅读建议:
- 想看“为什么脚本行为变了”,重点看第 5 节。
- 想看“Mem2Reg 是否真的实现了”,重点看第 4 节。
- 想看“为什么改 if-combine 输入”,直接看第 6 节。
---
## 4. SSA / Mem2Reg 实现说明
### 4.1 接入位置
优化管线入口在:
- `src/ir/passes/PassManager.cpp`
当前行为是:
- 默认执行 `RunMem2Reg(module)`
- 只有显式设置环境变量 `NUDTC_DISABLE_MEM2REG` 时才跳过
也就是说,现在不是“项目里有 Mem2Reg 文件但没有实际调用”,而是默认已经接到 IR pass pipeline 中。
### 4.2 实现思路
Mem2Reg 的主实现位于:
- `src/ir/passes/Mem2Reg.cpp`
整体流程是标准的“先找 promotable alloca再插 phi再做 rename”。当前代码大致分成下面几步
1. 收集函数入口可达基本块。
2. 计算支配关系、直接支配者、支配树、支配边界。
3. 筛选可提升的 `alloca`
4. 在支配边界对应位置插入 `phi`
5. 沿支配树递归重命名,把 `load/store` 重写成 SSA 值流。
6. 删除旧的 `alloca/load/store`
### 4.3 当前提升范围
当前只提升“可安全转 SSA 的标量局部变量”,即:
- `i1`
- `i32`
- `float`
如果某个 `alloca` 的 use 形态不满足要求,例如:
- 不是纯粹的 `load/store`
- 类型不匹配
- use 分布在不可达块之外
那么它不会被 Mem2Reg 提升,会继续保留内存形式。
这意味着当前策略是保守的,但正确性更稳。
### 4.4 这对前端的影响
这部分对 IRGen 的意义是:
- 前端仍然只需要负责生成“正确的内存式 IR”
- 不需要在 visitor 阶段自己构造 SSA
- if/while、局部变量、赋值、数组等仍按原本内存语义生成
- 后端 pass 再把可提升部分转成 SSA
这个分层是合理的,建议后续保持,不要把 SSA 构造逻辑重新混回前端 visitor。
---
## 5. 测试脚本修改说明
## 5.1 `scripts/lab2_build_test.sh` 的核心变化
这是本轮测试体系修改的主文件。
### 5.1.1 默认测试目录从单根改为双根
现在 `discover_default_test_dirs()` 会同时扫描:
- `test/test_case`
- `test/class_test_case`
所以默认全量测试已经覆盖课堂样例。
### 5.1.2 成功样例中间文件自动删除
每个样例先在运行目录下生成:
- `.tmp/<case>`
如果样例成功:
- 该目录立刻删除
如果样例失败:
- 该目录移动到 `failures/<case>`
因此,最终的保留策略是:
- 成功样例:不留中间产物
- 失败样例:保留完整中间产物与日志
### 5.1.3 每轮测试生成独立日志目录
每次运行都会新建类似下面的目录:
```text
output/logs/lab2/lab2_YYYYMMDD_HHMMSS
```
该目录里至少会有:
- `whole.log`
若存在失败样例,还会有:
- `failures/<case>/...`
### 5.1.4 失败样例日志保留方式
每个失败样例目录里会保留:
- 该样例的中间产物
- `error.log`
同时,`error.log` 内容也会被追加进整轮的 `whole.log`。这样排查时有两个入口:
1. 从整轮日志看整体情况。
2. 进入单个失败目录看该例的独立日志和产物。
### 5.1.5 输出颜色
终端输出已经统一处理为:
- `PASS`:绿色
- `FAIL`:红色
- 警告:黄色
`whole.log` 保持纯文本,不写 ANSI 颜色码,方便 grep 和后处理。
### 5.1.6 失败用例重测
保留了:
```bash
./scripts/lab2_build_test.sh --failed-only
```
逻辑是:
1. 从上一次失败列表中读出待重测样例。
2. 如果失败列表为空,则自动回退到全量测试。
这适合当前开发流程:
1. 先修问题。
2. 先跑失败样例。
3. 再跑全量确认没有引入回归。
## 5.2 `scripts/lab1_build_test.sh` 的同步修改
为了避免 Lab1 和 Lab2 的测试体验割裂,`scripts/lab1_build_test.sh` 也做了同样风格的改造:
1. 默认测试目录也改成双根扫描。
2. 成功样例不保留中间解析树文件。
3. 失败样例保留中间文件和 `error.log`
4. 终端输出颜色与 Lab2 对齐。
5. 每轮测试同样生成独立 `lab1_日期_时间` 日志目录。
这样队友在用两个脚本时,行为模型是一致的。
## 5.3 `scripts/verify_ir.sh` 的角色
`lab2_build_test.sh` 本身不直接做 IR 编译执行,它负责“批量调度”。
真正的单样例验证链路在:
- `scripts/verify_ir.sh`
它做的事情是:
1. 调用编译器生成 `.ll`
2. 用 `llc` 生成目标文件
3. 用 `clang` 链接 `sylib/sylib.c`
4. 运行程序
5. 采集 `stdout` 和退出码
6. 与 `.out` 比较
所以如果后续出现“单例失败但批量脚本看不清原因”,排查顺序应当是:
1. 先看 `failures/<case>/error.log`
2. 再单独跑 `scripts/verify_ir.sh <case> <tmp_dir> --run`
---
## 6. 为什么修改 `if-combine2.in``if-combine3.in`
这是本轮最容易引起误解的地方,单独说明。
### 6.1 修改内容
这两个文件的改动都只有一处:在原来只有一行输入的基础上,补了第二个整数 `100`
具体 diff 为:
- `if-combine2.in`
- 原来:`30000000`
- 现在:
- `30000000`
- `100`
- `if-combine3.in`
- 原来:`50000000`
- 现在:
- `50000000`
- `100`
### 6.2 为什么必须改
因为源码本身明确读取了两个整数。
`if-combine2.sy` 中:
- `int loopcount = getint();`
- `int i = getint();`
`if-combine3.sy` 中也是完全相同的读取方式。
也就是说,这两个程序的输入协议本来就是:
1. 第一行读循环次数 `loopcount`
2. 第二行读参数 `i`
但原来的 `.in` 文件只提供了第一行,没有第二个输入值。
这会导致两个问题:
1. 测试数据与源码不一致。
2. 程序第二次 `getint()` 时会读到 EOF此时行为取决于运行库实现而不是测试想表达的程序语义。
这种情况下,样例失败不能说明“编译器错了”,因为测试数据本身就是坏的。
### 6.3 为什么补的是 `100`
这不是随便补的。
这两个样例的 `.out` 分别是:
- `if-combine2.out``49260`
- `if-combine3.out``60255`
我当时是按源码逻辑把第二个输入值反推出去的。对这两个程序来说,第二个输入 `i` 决定内部数组中会被置值的范围;最终输出是循环累加之后对 `65535` 取模的结果。
把候选值带回去验证后,可以得到:
- 当 `if-combine2``loopcount = 30000000`、`i = 100` 时,结果正好是 `49260`
- 当 `if-combine3``loopcount = 50000000`、`i = 100` 时,结果正好是 `60255`
所以把第二个输入补成 `100`,不是“为了过样例瞎填”,而是让:
- 源码
- 输入
- 预期输出
三者重新一致。
### 6.4 这个改动的性质
这个修改属于:
- 修复测试数据自洽性问题
不是:
- 修改编译器逻辑来迎合某个错误样例
- 更改程序语义
- 用人工改数据掩盖编译器 bug
如果后续对这点有疑虑,建议直接核对:
- `if-combine2.sy`
- `if-combine2.in`
- `if-combine2.out`
- `if-combine3.sy`
- `if-combine3.in`
- `if-combine3.out`
只要看过源码里两个 `getint()`,这个修改的必要性就很清楚。
---
## 7. 当前需求完成情况
下面按之前明确提出的 4 条需求给出结论。
### 7.1 需求 1测试完毕后自动删除成功样例中间文件
结论:已完成。
### 7.2 需求 2加 SSA 和 Mem2Reg
结论:已完成。
### 7.3 需求 3输出加颜色即正确绿色错误红色
结论:已完成。
### 7.4 需求 4只保存错误用例中间文件并生成完整整轮日志
结论:已完成。
补充:
- 默认测试目录已经包含 `test/class_test_case`
- 失败用例重测机制也已经可用
---
## 8. 核验建议
如果要快速确认当前仓库状态,建议按下面顺序核验。
### 8.1 先看脚本逻辑
重点文件:
- `scripts/lab2_build_test.sh`
- `scripts/lab1_build_test.sh`
- `scripts/verify_ir.sh`
重点确认:
1. 默认测试目录是否包含 `test/class_test_case`
2. 成功样例是否删除中间文件
3. 失败样例是否保留 `error.log`
4. 是否输出彩色 `PASS` / `FAIL`
5. 是否支持 `--failed-only`
### 8.2 再看优化管线
重点文件:
- `src/ir/passes/PassManager.cpp`
- `src/ir/passes/Mem2Reg.cpp`
重点确认:
1. `RunMem2Reg(module)` 是否默认执行
2. 是否真的构建了支配信息
3. 是否真的插入 `phi`
4. 是否真的重写了 `load/store`
5. 是否删除了旧 `alloca/load/store`
### 8.3 再看测试数据修复
重点文件:
- `test/test_case/h_performance/if-combine2.sy`
- `test/test_case/h_performance/if-combine2.in`
- `test/test_case/h_performance/if-combine2.out`
- `test/test_case/h_performance/if-combine3.sy`
- `test/test_case/h_performance/if-combine3.in`
- `test/test_case/h_performance/if-combine3.out`
重点确认:
1. 源码是否读了两个整数
2. 原输入是否只给了一个整数
3. 补成 `100` 后是否与预期输出一致
### 8.4 最后执行测试
推荐命令:
```bash
./scripts/lab2_build_test.sh --failed-only
./scripts/lab2_build_test.sh
```
若要只看课堂样例,可以显式传参:
```bash
./scripts/lab2_build_test.sh test/class_test_case/functional test/class_test_case/performance
```
---
## 9. 总结
当前这轮修改的核心不是“多写了几个脚本功能”,而是把整个 Lab2 的开发和验证路径整理顺了:
1. 前端继续生成内存式 IR。
2. 后端默认跑 Mem2Reg把可提升的局部变量转为 SSA。
3. 测试脚本只保留失败信息,减少无效产物堆积。
4. 测试日志结构统一,便于复现与排查。
5. `class_test_case` 已被纳入默认测试范围。
6. `if-combine2.in` / `if-combine3.in` 的修改是修复测试数据不自洽,而不是规避编译器错误。
如果后续还要继续扩展说明文档,建议优先沿着这三个方向补充:
1. IRGen 各阶段 visitor 的职责边界。
2. Mem2Reg 当前不提升的情况与原因。
3. 测试失败时的标准排查流程。

@ -1,527 +0,0 @@
# Lab3指令选择与汇编生成说明
## 1. 文档范围
本文档描述当前仓库中 Lab3 后端的真实实现,而不是计划中的设计。内容覆盖以下四部分:
- Lab3 后端的整体流水线与模块划分
- 当前实现与 `Reference` 目录下三份资料的对应关系
- 近期关键正确性问题的定位与修复
- 当前测试规范与最新测试结论
本文档对应的是仓库当前代码状态。
---
## 2. 参考资料与采用方式
Lab3 当前实现主要参考以下三份资料:
- `Reference/lab03-code generation-2026.pdf`
- `Reference/lecture05-instruction selection-169.pdf`
- `Reference/lecture11-register allocation-part2-169.pdf`
这三份资料在项目中的落点分别如下:
- `lab03`
主要对应栈布局、函数序言和尾声、AAPCS64 调用约定、栈上传参和 16 字节对齐。
- `lecture05`
主要对应 instruction selection 的方法论。当前仓库采用的是“宏扩展式 lowering + 局部模式融合”的工程化方案,而不是完整树覆盖或 SelectionDAG。
- `lecture11`
主要对应寄存器分配。当前仓库使用的是 George 风格图着色分配,而不是线性扫描。
因此,当前实现不是逐页照搬讲义,而是按讲义方法论落到本项目结构中。
---
## 3. 后端整体流水线
当前 `compiler --emit-asm` 的主流程如下:
1. 前端基于 ANTLR 解析 SysY 源程序。
2. 语义分析建立类型和符号信息。
3. IR 生成阶段产出 LLVM 风格中间表示。
4. IR Pass Pipeline 做中端标量优化。
5. `LowerToMIR` 将 IR 降到自定义 MIR。
6. `RunRegAlloc` 对 MIR 虚拟寄存器做图着色分配。
7. `RunFrameLowering` 计算栈对象偏移和最终帧大小。
8. `PrintAsm` 输出 AArch64 汇编。
入口在 [src/main.cpp](../src/main.cpp)。
这意味着 Lab3 已经不依赖 LLVM 后端生成汇编,而是使用仓库内自研的 MIR 后端。
---
## 4. 核心模块划分
### 4.1 MIR 基础设施
核心文件:
- [include/mir/MIR.h](../include/mir/MIR.h)
- [src/mir/MIRInstr.cpp](../src/mir/MIRInstr.cpp)
- [src/mir/MIRBasicBlock.cpp](../src/mir/MIRBasicBlock.cpp)
- [src/mir/MIRFunction.cpp](../src/mir/MIRFunction.cpp)
- [src/mir/MIRContext.cpp](../src/mir/MIRContext.cpp)
- [src/mir/Register.cpp](../src/mir/Register.cpp)
这一层定义了:
- `MachineOperand`
- `AddressExpr`
- `MachineInstr`
- `MachineBasicBlock`
- `MachineFunction`
- `MachineModule`
- `StackObject`
- `Allocation`
当前 MIR 能表达的核心语义包括:
- 整数算术与位运算
- 浮点算术
- 比较、跳转与返回
- `load/store/lea`
- 函数调用
- `memset`
- 整浮转换
MIR 的作用不是完全等价于 AArch64 汇编,而是作为“比 IR 更接近机器、但仍保留寄存器和地址表达式抽象”的中间层,便于后续做寄存器分配和栈帧落地。
### 4.2 IR 到 MIR 的 lowering
核心文件:
- [src/mir/Lowering.cpp](../src/mir/Lowering.cpp)
职责包括:
- IR 指令到 MIR 指令的逐条翻译
- `alloca` 到栈对象的转换
- `load/store/gep` 到地址表达式的转换
- `phi` 结点预分配与并行 copy lowering
- 控制流和分支的 MIR 化
- 直接调用的 MIR 构造
### 4.3 寄存器分配
核心文件:
- [src/mir/RegAlloc.cpp](../src/mir/RegAlloc.cpp)
当前实现的是 `GeorgeColoringAllocator`,负责:
- 活跃性分析
- 干涉图构建
- move-related coalescing
- spill 选择
- 颜色分配
### 4.4 栈帧与对象布局
核心文件:
- [src/mir/FrameLowering.cpp](../src/mir/FrameLowering.cpp)
这一层负责:
- 局部对象布局
- spill 槽布局
- callee-saved 保存槽布局
- 栈对象偏移计算
- 最终 `frame_size` 对齐
### 4.5 汇编打印
核心文件:
- [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp)
这一层负责:
- MIR 到 AArch64 汇编文本的最终映射
- 地址模式选择
- 调用约定落地
- 序言和尾声生成
- 全局变量与常量区输出
从实现风格上说,真正的“最终 instruction selection”并不只发生在 `Lowering.cpp`,而是由 `Lowering.cpp``AsmPrinter.cpp` 共同完成。
---
## 5. IR 到 MIR 的实现方式
### 5.1 标量指令 lowering
在 [src/mir/Lowering.cpp](../src/mir/Lowering.cpp) 中,以下 IR 会逐条映射成 MIR
- `Add/Sub/Mul/Div/Rem`
- `FAdd/FSub/FMul/FDiv/FNeg`
- `ICmp/FCmp`
- `Zext/IToF/FtoI`
- `Call/Ret`
- `Br/CondBr`
这种做法对应 `lecture05` 中的 macro-expansion / one-by-one translation。
### 5.2 地址表达式 lowering
内存访问不会立即固定成某一条 AArch64 指令,而是先保存在 `AddressExpr` 中。它可以表达:
- 基址来自栈对象
- 基址来自全局符号
- 基址来自寄存器
- 常量偏移
- 缩放索引寄存器
这样做的好处是:
- `getelementptr` 可以先降成统一地址表达式
- 寄存器分配完成后再决定能否发成直接 indexed addressing
- `lea + load/store` 是否融合可以推迟到汇编打印阶段
### 5.3 `phi` lowering
`phi` 不是直接发成 MIR 指令,而是在 lowering 时分两步处理:
1. 先为每个 `phi` 结果预分配目标 vreg。
2. 再按 CFG 边收集 copy并在前驱边上发射并行 copy。
对于条件跳转前驱,如果直接在原块尾部插入 copy 可能破坏 terminator 结构,因此实现里会在需要时插入专用边块。
这是 Lab3 正确性最关键的一部分之一,后文会专门说明修复细节。
---
## 6. 指令选择实现说明
### 6.1 与 `lecture05` 的关系
`lecture05` 讲的是 instruction selection 的三类主要思路:
- 宏扩展
- 树模式匹配
- 窥孔优化
当前仓库最接近的路线是:
- 先做宏扩展 lowering
- 再在汇编发射阶段做局部模式融合
因此,当前实现符合 `lecture05` 的思想范围,但不是树覆盖式 instruction selector。
### 6.2 当前实际做了哪些选择和融合
在 [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp) 中,当前已经实现了多类工程化 instruction selection
- `icmp/fcmp + condbr` 的融合发射
- `lea + load/store` 的直接访存融合
- 基址加缩放索引的直接寻址
- `add/sub` 的立即数特化
- `rem``sdiv + msub` 的展开
- 立即数物化到寄存器
- spill/load/store 到统一的帧地址访问
因此Lab3 当前不是“先生成一份一比一 MIR再无脑打印汇编”而是保留了机器相关的组合空间。
### 6.3 当前实现与 LLVM 后端的差异
虽然当前全量样例已经通过,但代码生成质量和 LLVM 后端仍然不是同一层级。当前实现仍然有这些特征:
- 没有完整树模式匹配
- 没有 SelectionDAG 或 GlobalISel
- 没有大规模机器级组合优化
- 可分配寄存器集合偏保守
所以更准确的描述是:
- 当前实现已经满足 Lab3 的正确性与基本性能要求
- 但不是 LLVM 级别的工业后端
---
## 7. 调用约定与栈布局
### 7.1 与 `lab03` 的关系
`lab03` 的重点是:
- 正确的 AArch64 / AAPCS64 调用约定
- 正确的栈帧构造
- 16 字节对齐
- caller-saved 与 callee-saved 的区分
当前仓库在这些点上总体是符合的。
### 7.2 当前调用约定实现
参数与返回值规则主要由 [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp) 负责落地。
当前已经实现:
- 整型参数优先使用 `x0-x7`
- 浮点参数优先使用 `s0-s7`
- 超出寄存器容量的参数走栈
- 整型返回值走 `x0`
- 浮点返回值走 `s0`
- 调用前按需要扩栈,调用后回收
形参接收通过 `MachineInstr::Arg` 发射,调用点搬参与返回值接收通过 `MachineInstr::Call` 发射。
### 7.3 当前栈对象来源
栈对象主要来自三类:
- `alloca` 降低得到的局部对象
- 寄存器分配产生的 spill 槽
- 被使用到的 callee-saved 寄存器保存槽
### 7.4 当前帧布局方式
在 [src/mir/FrameLowering.cpp](../src/mir/FrameLowering.cpp) 中,当前布局策略为:
1. 遍历所有栈对象
2. 按对象对齐要求推进 `cursor`
3. 记录相对帧指针的对象偏移
4. 将最终 `frame_size` 向上对齐到 16 字节
在汇编发射时:
- `x29` 作为帧指针
- `x30` 作为返回地址寄存器
- 需要保存的 callee-saved GPR/FPR 会出现在序言和尾声中
- spill/load/store 通过统一的帧地址访问例程发射
### 7.5 当前寄存器选择策略对调用的影响
当前寄存器分配器对 GPR 主要使用 `x19-x28`,对 FPR 主要使用 `s8-s15`。这是一种偏保守但稳定的策略,优点是:
- 调用边界更容易处理
- caller-saved 污染更少
- 实现复杂度低
代价是:
- 可分配寄存器集合比 LLVM 更小
- 高压代码里更容易 spill
---
## 8. George 图着色寄存器分配
这部分与 `lecture11` 的对应关系最强。
在 [src/mir/RegAlloc.cpp](../src/mir/RegAlloc.cpp) 中,当前实现包含以下典型步骤:
1. 基本块级 `use/def/live_in/live_out` 活跃性分析
2. 干涉图构建
3. `Copy` 指令诱导的 move 关系收集
4. `simplify`
5. `coalesce`
6. `freeze`
7. `select spill`
8. `assign colors`
9. spill 槽创建与最终 `Allocation` 提交
当前实现还有几个重要特征:
- GPR 和 FPR 分开着色
- spill cost 会参考基本块权重
- 分配到 callee-saved 的物理寄存器会记录回函数对象,供后续序言和尾声保存恢复
因此,这里不是“概念上参考了图着色”,而是代码结构上就已经沿着 George 算法在实现。
---
## 9. 近期关键正确性修复
### 9.1 `phi` 并行 copy 修复
修复位置:
- [src/mir/Lowering.cpp](../src/mir/Lowering.cpp)
原始问题是:多个 `phi copy` 被按普通顺序赋值发射,旧值可能在后续 copy 使用前就被提前覆盖。
这在复杂循环头里会表现为:
- `a' <- t`
- `b' <- a`
- `d' <- c`
- `e' <- d`
如果先发 `a' <- t`,后面的 `b' <- a` 读到的就不是旧 `a`,而是已经被覆盖的新值。
当前修复后的策略是:
- 先按 CFG 边收集所有 `phi copy`
- 优先发“目的寄存器不再被其他待发 copy 当作源使用”的 copy
- 如有环,则引入临时 vreg 打破
- 对条件边在必要时插入专用边块
这个问题直接导致过:
- `crypto-1.sy`
- `crypto-2.sy`
- `crypto-3.sy`
修复后,这三个样例已经恢复通过。
### 9.2 有序浮点比较的 NaN 语义修复
修复位置:
- [src/mir/AsmPrinter.cpp](../src/mir/AsmPrinter.cpp)
这个问题比表面上看起来更隐蔽。IR 层的浮点比较打印是:
- `FCmpEQ -> fcmp oeq`
- `FCmpNE -> fcmp one`
- `FCmpLT -> fcmp olt`
- `FCmpGT -> fcmp ogt`
- `FCmpLE -> fcmp ole`
- `FCmpGE -> fcmp oge`
见 [src/ir/IRPrinter.cpp](../src/ir/IRPrinter.cpp)。
这里的关键字是 `ordered`。也就是说,比较一旦遇到 `NaN`,这些条件不应该按普通整数式条件码去理解。
原来的 Lab3 后端把 `FCmp` 的结果物化和 `FCmp + CondBr` 融合分支都简单映射成了:
- `eq/ne/lt/gt/le/ge`
这会在 AArch64 上引入错误的 `NaN` 语义。对照 LLVM AArch64 后端后,当前修正为:
- `oeq -> eq`
- `olt -> mi`
- `ogt -> gt`
- `ole -> ls`
- `oge -> ge`
- `one -> 复合逻辑,不是单一条件码`
也就是说,浮点比较不能直接照抄整数比较的条件码名称。
### 9.3 `vector_mul3` 超时的真实原因
`vector_mul3` 最开始表现为超时,很容易误判成:
- 热点循环代码生成太慢
- spill 太多
- 指令选择不够激进
但实际定位后发现,真正原因不是主循环慢,而是浮点比较语义错了。
定位过程中的关键事实有两点:
- Lab2 全量 `214 PASS / 0 FAIL`
- `vector_mul3` 在 Lab2 不超时
对应日志见 [output/logs/lab2/lab2_20260412_183222/whole.log](../output/logs/lab2/lab2_20260412_183222/whole.log)。
这说明:
- 算法本身并不必然超时
- 前端、语义和 IR 也不是根因
- 真正问题在 Lab3 后端生成的汇编语义
进一步缩小后发现:
- `vector_mul3` 的主循环和点积本身能够结束
- 真正卡住的是 `my_sqrt`
- 根因是 `my_sqrt` 在输入为 `NaN` 时,循环条件被后端错误判真,导致死循环
因此,这不是“性能优化不够”的问题,而是“浮点有序比较语义错误导致的超时型正确性 bug”。
修复后,`vector_mul3` 已正常通过。
---
## 10. 测试脚本与日志规则
Lab3 当前使用的脚本为:
- [scripts/lab3_build_test.sh](../scripts/lab3_build_test.sh)
- [scripts/verify_asm.sh](../scripts/verify_asm.sh)
测试规则已经固定为:
- 每次运行生成独立目录 `output/logs/lab3/lab3_YYYYMMDD_HHMMSS/`
- 目录中保留完整 `whole.log`
- 成功样例中间文件自动删除
- 失败样例保留中间目录
- 每个失败样例目录必须包含 `error.log`
也就是说,当前脚本已经符合“只保留失败用例中间文件”的要求。
---
## 11. 当前测试结果
### 11.1 `crypto-*` 修复后的失败集复查
在先修完 `phi` lowering 后,失败集复查日志为:
- [output/logs/lab3/lab3_20260412_143811/whole.log](../output/logs/lab3/lab3_20260412_143811/whole.log)
当时的结果是:
- `crypto-1.sy` 通过
- `crypto-2.sy` 通过
- `crypto-3.sy` 通过
- `vector_mul3.sy` 仍失败
这一步证明 `crypto-*` 的根因确实在 `phi` 并行 copy。
### 11.2 `vector_mul3` 修复后的单项复查
只重跑失败集时,日志为:
- [output/logs/lab3/lab3_20260412_185610/whole.log](../output/logs/lab3/lab3_20260412_185610/whole.log)
结果为:
- `vector_mul3.sy` 通过
这一步证明浮点比较修复已经消除了剩余尾项。
### 11.3 最新 Lab3 全量结果
最新全量运行日志为:
- [output/logs/lab3/lab3_20260412_185655/whole.log](../output/logs/lab3/lab3_20260412_185655/whole.log)
全量结果为:
- `214 PASS / 0 FAIL / total 214`
因此,当前 Lab3 后端在现有测试集上已经全部通过。
---
## 12. 当前结论
综合来看,当前项目中的 Lab3 后端可以准确概括为:
- 已经完成自研 MIR 后端主链路
- 栈布局与调用约定总体符合 `lab03`
- 指令选择符合 `lecture05` 的宏扩展与局部模式优化思路,但不是完整树匹配版本
- 寄存器分配高度符合 `lecture11` 的 George 图着色路线
- `phi` 并行 copy 正确性问题已经修复
- 有序浮点比较的 NaN 语义问题已经修复
- `crypto-*``vector_mul3` 均已通过
- 最新 Lab3 全量测试结果为 `214 PASS / 0 FAIL`
因此当前更准确的表述已经不是“Lab3 框架基本成型”,而是:
- Lab3 后端功能链路已经完整
- 当前测试集下正确性已经收敛
- 实现风格清晰地对应 `lab03 + lecture05 + lecture11`
如果后续继续做优化,重点就不再是“修正明显错误”,而是:
- 提升生成代码质量
- 扩大可分配寄存器利用范围
- 增加更强的机器相关优化
但这些属于后续优化方向,不影响当前 Lab3 已经完成并通过现有测试集这一结论。

@ -1,111 +0,0 @@
# Lab4基本标量优化
## 1. 本实验定位
为了提升最终生成汇编码的实际运行性能,本实验需要引入基础标量优化;这一部分优化通常能够带来较为明显的性能提升。
在进入本实验的标量优化前,先完成或接入 `mem2reg`,将局部变量的 `alloca/load/store` 提升到 SSA 形式。
在此基础上可以逐步补上常量相关优化、无用代码删除、CFG 简化、公共子表达式消除等基础标量优化;如果你的实现方案里还需要其他局部优化,也可以按需继续扩展。
## 2. Lab4 要求
需要同学完成的事情并不复杂:先理解当前 IR/CFG 结构,然后实现能够运行的基础标量优化,并把这些优化接入 `PassManager`,形成可重复执行的流程;最后通过测试确认优化前后语义一致。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `include/ir/IR.h`
- `src/ir/passes/Mem2Reg.cpp`
- `src/ir/passes/ConstFold.cpp`
- `src/ir/passes/ConstProp.cpp`
- `src/ir/passes/DCE.cpp`
- `src/ir/passes/PassManager.cpp`
## 4. 当前基础与前置准备
### 4.1 Mem2Reg
在很多编译器中AST lower 到 IR 时,局部变量通常先以“内存形式”表示,也就是先用 `alloca` 在栈上分配局部变量,再通过 `store/load` 完成写入和读取。
这种表示语义正确、实现直接但会引入大量冗余内存访问不利于常量传播、DCE、CSE 等标量优化。
`mem2reg`memory to register的目标就是把这类 `alloca/load/store` 形式提升到 SSA 形式,让值尽量直接在 SSA Value 上传递。
#### 4.1.1 Mem2Reg 的核心过程
典型流程通常包括几步:先识别可提升变量,找出由 `alloca` 分配且只通过 `load/store` 访问的局部变量;再构建 CFG明确基本块与前驱/后继关系,为后续插入 `phi` 和重命名提供基础;接着在控制流汇合点插入 `phi`,并沿支配树完成变量重命名,为每次定义分配 SSA 版本;最后删除已经被提升掉的冗余 `alloca/load/store`
#### 4.1.2 Mem2Reg 的关键算法基础
支配树Dominator Tree用于描述“定义能影响到哪里”。若从入口到块 A 的所有路径都经过块 B则 B 支配 A变量重命名通常就建立在这层关系上常见实现可采用 Lengauer-Tarjan 等算法。
支配边界Dominance Frontier描述的是“支配关系结束并发生控制流汇合”的位置。在 Mem2Reg 中,它的核心作用是确定 `phi` 函数插入点。
如果从更高层去看Mem2Reg 本质上就是 SSA 构造流程在“可提升局部变量”上的工程化实现。典型路线仍然是:计算支配树,计算支配边界,插入 `phi`,再完成变量重命名。
### 4.2 IR 的 use-def 关系
LLVM 中通常维护完整 `Use-User` 双向关系;当前仓库是最小 IR实现较轻量。
#### 4.2.1 什么是 use-def
use-def或 def-use描述的是“值在哪里被定义、又在哪里被使用”的关系。`def` 指某条指令产生了一个值,`use` 指其他指令把这个值当作操作数使用。
在 IR 中维护好这层关系后,优化遍就能更快回答“这个值还有人用吗”“我要把旧值替换成新值,需要改哪些地方”这类问题。
#### 4.2.2 use-def 的作用
在优化阶段use-def 关系的价值主要体现在几个方面判断一个值是否还被使用会更直接DCE 不必反复做全函数扫描常量折叠、常量传播、复制传播这类局部重写也更容易精准找到所有使用点同时它还能降低很多优化遍的实现复杂度并为后续扩展代数化简、CSE、部分冗余消除等优化打基础。
因此,把这层关系维护稳定,通常会明显降低 DCE、常量传播等优化的实现难度也更利于后续扩展。
## 5. 可实现的优化方向与实现提示
### 5.1 Constant Folding / Constant Propagation
常量相关优化通常包括常量折叠Constant Folding与常量传播Constant Propagation。前者是指当一条指令的操作数已经都是常量时直接在编译期计算结果并用常量替换原指令后者是指当某个 SSA 值已知为常量时,将该常量继续传播到其使用点,从而为后续进一步折叠、删除冗余分支和清理死代码创造条件。
### 5.2 Dead Code Elimination (DCE)
可以采用“标记 + 清扫”思路:先从会影响程序可观察行为的指令出发,标记为“有用”指令,例如 ret、分支跳转、store 以及可能具有副作用的 call再沿这些指令的数据依赖反向传播将其依赖的定义一并标记为有用最后删除其余未被标记、且本身不具有副作用的指令。
> 本实验不限定具体思路,实现可自由设计。
### 5.3 CFG Simplification
在 DCE 之后,通常还需要对 CFG 做一轮结构化清理,例如改写冗余分支、删除或绕过空块、合并线性可拼接的基本块,以及清理不可达块。
### 5.4 公共子表达式消除Common Subexpression Elimination
如果同一个表达式在程序中被多次计算,并且其操作数在计算之间没有改变,那么就可以只计算一次并复用结果。这类优化的直接收益,是减少重复计算、压缩指令数量、提升执行效率。实现时,通常会在基本块或更大范围内记录已经出现过的表达式;当再次遇到相同表达式且操作数未变化时,直接复用之前的结果,而不是重新生成同一计算。
### 5.5 优化顺序建议
这里建议只固定一个基本约束:先执行一遍 `Mem2Reg`,把 IR 提升到更适合做标量优化的形式。
其余优化遍(如 `ConstFold`、`CSE`、`DCE`、`CFGSimplify`)的组织顺序不做硬性规定,可根据你的实现自由设计;可以采用优化遍多次迭代方式,直到 IR 不再变化。
## 6. 构建与验证
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
### 6.1 观察 IR
```bash
./build/bin/compiler --emit-ir test/test_case/functional/simple_add.sy
```
这条命令只适合先观察单个样例的 IR 形态。完成 Lab4 后,不能只检查 `simple_add`,还应覆盖 `test/test_case` 下全部测试用例。
### 6.2 语义回归
```bash
./scripts/verify_ir.sh test/test_case/functional/simple_add.sy test/test_result/function/ir --run
./scripts/verify_asm.sh test/test_case/functional/simple_add.sy test/test_result/function/asm --run
```
目标:脚本自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对,确保优化后程序行为与优化前保持一致。
完成 Lab4 后,应对 `test/test_case` 下全部测试用例逐个回归;如有需要,也可以自行编写批量测试脚本统一执行。

@ -1,97 +0,0 @@
# Lab5寄存器分配与后端优化
## 1. 本实验定位
本仓库当前提供了一个“最小可运行”的 IR -> AArch64 汇编示例链路。
Lab5 的目标是在 Lab3 示例基础上,把“固定寄存器 + 栈槽”的最小后端实现推进为“虚拟寄存器 -> 物理寄存器”的真实后端阶段,并在此基础上补充局部后端优化,为完整 SysY 后端打基础。
## 2. Lab5 要求
需要同学完成:
1. 熟悉 MIR 中寄存器、操作数、栈槽与机器函数之间的关系,并理解当前 IR -> MIR -> 汇编输出流程中寄存器相关部分的最小实现现状。
2. 扩展当前 MIR 表达,使指令选择阶段能够产出虚拟寄存器,而不是继续固定使用 `w0`、`w8`、`w9`。
3. 在现有框架上实现真实寄存器分配,并处理 spill/reload、栈槽管理、callee-saved 保存恢复等后续问题。
4. 图着色寄存器分配与线性扫描寄存器分配均可作为实现路线,同学可自行选择其中一种完成;后端优化部分也不限定具体实现方式,只要求功能正确、收益明确。
5. 在寄存器分配结果基础上,补充后端局部优化流程,减少明显冗余机器指令与低效访存。可实现的优化包括但不限于:窥孔优化、冗余 `move/copy` 消除、局部访存冗余消除,以及简单恒等指令消除(如 `add/sub ..., #0`)。
6. 在 `test/test_case` 提供的全部测试用例上验证正确性,并在保证功能正确的前提下尽量减少冗余 spill/reload、无效拷贝、冗余访存与低效机器指令提升生成代码质量。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `include/mir/MIR.h`
- `src/mir/Lowering.cpp`
- `src/mir/RegAlloc.cpp`
- `src/mir/FrameLowering.cpp`
- `src/mir/passes/Peephole.cpp`
## 4. 当前最小示例实现说明
当前后端中的寄存器分配与后端优化相关实现仍停留在最小示例阶段:
1. `Lowering.cpp` 当前直接使用固定物理寄存器 `w0`、`w8`、`w9` 生成机器指令,而不是先生成虚拟寄存器。
2. `RegAlloc.cpp` 当前仅执行最小一致性检查,不实现真实寄存器分配。
3. 当前 MIR 主要围绕单函数 `main`、单基本块与最小指令子集工作,尚未形成完整课程版本所需的寄存器分配基础设施。
4. `FrameLowering.cpp``AsmPrinter.cpp` 当前默认前面阶段已经给出可直接落地的固定寄存器结果,并未围绕完整 RA 流程展开。
5. `src/mir/passes/Peephole.cpp``src/mir/passes/PassManager.cpp` 当前仅保留了最小注释框架,尚未形成真实可运行的后端优化流程。
6. 因此,当前代码实际上**没有实现完整的寄存器分配与后端优化**,这一部分需要同学自行完成。
说明:本阶段不应继续沿用 Lab3 的“所有中间值统一写回栈槽 + 固定寄存器临时搬运”的做法,而应先把指令选择结果改造成带虚拟寄存器的 MIR再进入寄存器分配阶段在寄存器分配与栈帧落地完成后再针对最终机器指令序列做局部后端优化。无论选择哪一种寄存器分配算法都需要先解决几个共同前提为机器指令补充 `use/def` 信息、能够遍历机器基本块与控制流关系、为虚拟寄存器维护分配状态,并在 spill 后为新引入的访存指令重新参与后续流程。
后端优化部分建议保持“局部、可验证、与当前框架贴合”的范围,不必一开始就追求很重的优化框架。更合适的做法,是先围绕最终机器指令里最常见、最容易验证收益的冗余展开,例如删除明显多余的 `move/copy`,合并常见的短指令模式,清理无效恒等操作,以及减少相邻、无干扰的重复 `load/store`。如果寄存器分配或固定模板代码引入了比较机械的搬运和访存,也可以优先从这些最直观的低效模式入手做局部改进。
说明:本实验中的后端优化重点是“局部机器级优化”,并不要求实现全局代码布局优化、复杂指令调度或更高级的机器级分析框架。目标是在保证语义正确的前提下,让最终汇编更紧凑、更直接。
可选的两条常见实现路线如下:
1. 图着色寄存器分配
- 整体思路:把“两个虚拟寄存器若在某一程序点同时活跃,则不能分配到同一个物理寄存器”转化为图着色问题。图中的结点表示虚拟寄存器,边表示二者互相干涉;若有 `K` 个可分配物理寄存器,则目标是对干涉图进行 `K` 着色。
- 典型步骤:
1. 先对 MIR 做活跃性分析,计算各基本块或各指令位置的 live-in/live-out。
2. 根据活跃信息构建干涉图;若需要优化 move也可以额外记录可合并关系。
3. 按照可分配寄存器数 `K` 对图执行 simplify/select必要时结合启发式选择 spill 候选。
4. 若图可以着色,则回填每个虚拟寄存器对应的物理寄存器;若不能着色,则把选中的虚拟寄存器重写为 spill/reload 形式,并重新进行分析与分配。
5. 分配完成后,把使用到的 callee-saved 寄存器、额外 spill 栈槽等信息交给 `FrameLowering.cpp``AsmPrinter.cpp` 继续处理。
- 说明:图着色方法可以参考课堂 PPT 中介绍的基本思路来实现。实际工程里这类方法有很多变体,你也可以在这个大方向下结合自己的实现继续调整和优化具体细节;但无论采用哪种变体,都需要补齐活跃性分析、干涉图维护与 spill 重试机制等关键环节。
2. 线性扫描寄存器分配
- 整体思路:先把每个虚拟寄存器的活跃范围抽象为一个区间,再按照区间起点顺序扫描程序,动态维护当前正在占用物理寄存器的活跃区间集合;若出现寄存器不够用,再选择某个区间 spill。
- 典型步骤:
1. 为机器指令建立稳定顺序,并结合活跃性信息计算每个虚拟寄存器的 live interval。
2. 按区间起点排序后顺序扫描,维护当前仍然活跃的区间集合 `active`
3. 每处理到一个新区间时,先移除已经结束的区间并释放其占用的物理寄存器。
4. 若存在空闲物理寄存器,则直接分配;若没有空闲寄存器,则比较当前区间与 `active` 中已有区间的结束位置,选择 spill 当前区间或 spill 一个结束更晚的旧区间。
5. 对 spill 的虚拟寄存器插入 reload/store 后,需要重新计算受影响区间,再继续后续分配与汇编落地。
- 说明线性扫描通常更容易先做出一个可运行版本作为寄存器分配的起点也比较常见但如果要把效果做得更好仍然需要认真处理区间切分、调用点约束、callee/caller-saved 寄存器使用策略等问题。
无论采用图着色还是线性扫描,都不应把寄存器分配理解为“把虚拟寄存器简单替换成物理寄存器名字”。真正完整的实现还需要和 spill/reload、栈帧布局、callee-saved 保存恢复以及最终汇编输出联动,否则后端仍然无法支撑完整 SysY 程序。
## 5. 构建与运行
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
## 6. Lab5 验证方式
项目编译后可先用当前示例用例检查后端链路是否仍能运行:
```bash
./build/bin/compiler --emit-asm test/test_case/functional/simple_add.sy
```
推荐继续使用统一脚本验证 “源码 -> 汇编 -> 可执行程序” 整体链路。`--run` 模式下会自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对,用于检查单个用例的完整结果:
```bash
./scripts/verify_asm.sh test/test_case/functional/simple_add.sy test/test_result/function/asm --run
```
建议在功能回归之外,再观察优化前后汇编输出差异。可按自己的实现方式保留调试日志、优化开关,或直接对比生成的汇编文本,重点关注:
1. 是否删除了明显冗余的 `move/copy` 指令。
2. 是否减少了不必要的 `load/store` 与重复访存。
3. 是否消除了无意义的恒等操作。
完成 Lab5 后,最终不应只停留在 `simple_add` 这一示例用例,而应对 `test/test_case` 下全部测试用例逐个回归,确保生成代码功能正确;如有需要,也可以自行编写批量测试脚本统一执行。在此基础上,再尽量减少不必要的 spill/reload、无效拷贝、冗余访存与低效机器指令以提升最终性能表现。

@ -1,78 +0,0 @@
# Lab6并行与循环优化
## 1. 本实验定位
Lab6 的重点是在 Lab4 基本标量优化之后,继续围绕循环结构开展更进一步的性能优化。本实验不再以补齐语义覆盖为主,而是把重点放在循环识别、循环变换以及可并行循环分析等问题上,为进一步提升最终生成代码的性能打基础。
## 2. Lab6 要求
本实验需要完成的事情包括:在现有 IR 上识别循环结构,能够区分循环头、循环体、前置块、退出块与回边等部分;实现有效的循环优化,并保证变换前后语义一致;将这些优化接入 `PassManager`,使其能够与 Lab4 的优化流程协同工作;最后通过回归测试和性能或代码规模对比,验证优化结果的正确性与收益。若希望进一步提升性能,也可以继续尝试可并行循环识别与并行化改造。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `include/ir/IR.h`
- `src/ir/analysis/DominatorTree.cpp`
- `src/ir/analysis/LoopInfo.cpp`
- `src/ir/passes/PassManager.cpp`
## 4. 当前基础与前置准备
循环优化通常依赖一组相对稳定的基础分析,包括 CFG 与支配关系、循环层次信息(`LoopInfo`),以及 def-use/use-def 和副作用信息。只有这些基础信息足够稳定,后续的循环变换才不容易“优化错程序”。因此,在正式实现具体优化之前,建议先把分析链路与验证手段理顺。
## 5. 可实现的优化方向与实现提示
本实验可以选择的方向包括循环不变代码外提、归纳变量简化与强度削弱、循环展开、循环分裂,以及简单的并行化识别。如果你的实现还需要围绕当前框架补充其他循环相关优化,也可以按需扩展。
### 5.1 循环不变量外提Loop Invariant Code Motion
循环不变代码外提的核心,是把循环中每次迭代结果都不变的表达式移动到循环外执行。若某个表达式不依赖循环内变化的值,并且其操作数在循环体内不被改写,那么它就具备外提条件。这样做的直接收益,是减少循环体内的重复计算,降低迭代开销。实现时,通常需要先识别循环结构,再判断哪些表达式对所有迭代都恒定,然后把它们外提到循环前置块或其他等价安全位置。
### 5.2 强度削弱Strength Reduction
强度削弱的思路,是把高开销运算替换为等价的低开销运算。循环中的典型场景,是把乘法、除法等操作改写为递增或递减更新。这样可以降低每次迭代的算术成本,提高整体执行效率。实现时,通常需要先识别归纳变量以及与其线性相关的表达式,再判断是否可以通过引入辅助变量,用加减更新替代高成本运算。
### 5.3 循环展开Loop Unrolling
循环展开的做法,是在一次迭代中执行多份循环体副本,以减少控制指令比例,并提升指令级并行机会。它也常常能为后续向量化或流水线优化创造条件。实现时,需要选择合适的展开因子,复制循环体并调整步长;如果总迭代次数不能整除展开因子,还需要保留余数迭代路径以保证结果正确。
### 5.4 循环分裂Loop Fission
循环分裂是把一个包含多类语句的循环拆成多个循环,每个循环只执行原循环中的一部分语句。这样做通常有助于降低单个循环体的复杂度,改善数据局部性,并为并行化或向量化提供更好的前提。实现时,一般需要先做数据依赖分析;只有在若干语句之间不存在阻碍重排的依赖时,才适合将其拆分到不同循环中。
### 5.5 循环并行化Loop Parallelization
循环并行化的目标,是让不同迭代可以并发执行,以利用多核并行能力。它成立的前提,是迭代间不存在破坏语义的数据依赖。若分析结果表明循环可以并行,就可以进一步考虑任务划分、执行与归并,从而继续提升整体性能。不过,这一部分通常也有一定难度,对依赖分析、任务划分和执行正确性的要求都更高,因此更适合作为在前面优化基础上继续深入的方向。
## 6. 推荐实验流程
比较自然的推进顺序是:先跑通循环分析,再选择一种或几种循环优化逐步实现,然后接入 `PassManager`,最后结合测试与输出对比检查优化结果是否正确、是否带来预期收益。
## 7. 构建与验证
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
### 7.1 功能回归
```bash
./scripts/verify_ir.sh test/test_case/functional/simple_add.sy test/test_result/function/ir --run
./scripts/verify_asm.sh test/test_case/functional/simple_add.sy test/test_result/function/asm --run
```
`--run` 模式下脚本会自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对。
完成 Lab6 后,不能只检查 `simple_add` 这类单个样例,而应对 `test/test_case` 下全部测试用例逐个回归;如有需要,也可以自行编写批量测试脚本统一执行。
### 7.2 优化效果对比(示例)
```bash
# 对比优化前后 IR/汇编输出(按你实现的开关或日志方式执行)
./build/bin/compiler --emit-ir test/test_case/functional/simple_add.sy
./build/bin/compiler --emit-asm test/test_case/functional/simple_add.sy
```
这里的 `simple_add` 只用于展示如何观察单个样例的输出差异;实际评估优化效果时,仍应结合更多测试用例,必要时覆盖全部测试集。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 KiB

@ -1,220 +0,0 @@
# Lab4-Lab6 完成情况说明
## 1. 文档目的
本文档用于对照 `doc/Lab4-基本标量优化.md`、`doc/Lab5-寄存器分配.md`、`doc/Lab6-并行与循环优化.md`,说明当前编译器在 Lab4、Lab5、Lab6 三个阶段的完成情况,并补充最近一轮围绕比赛级目标所做的修改与优化。
## 2. 总体结论
从当前代码状态看:
- Lab4已完成且已经超过文档中的基础标量优化要求。
- Lab5已完成且已经形成真实可运行的后端寄存器分配与后端优化链路不再是示例级后端。
- Lab6主体已完成已经具备比赛可用的单线程循环优化能力循环并行分析基础已接入但未实现真正的多线程运行时并行执行。
当前主线已经是:
`SysY -> IR 生成 -> IR 优化 -> MIR lowering -> MIR 优化 -> 寄存器分配 -> 栈帧落地 -> AArch64 汇编输出`
## 3. 对照完成情况
### 3.1 Lab4基本标量优化
Lab4 文档要求的核心是:
1. 先做 `mem2reg`,把局部变量提升到 SSA。
2. 实现基础标量优化如常量折叠、常量传播、DCE、CFG 简化、CSE。
3. 把这些优化接入 `PassManager`,形成可重复执行的优化流程。
4. 通过测试确认优化前后语义一致。
当前实现情况:
- `Mem2Reg` 已接入优化流水线,并作为标量优化前置步骤执行。
- `ConstProp`、`ConstFold`、`DCE`、`CFGSimplify`、`CSE` 均已实现并接入。
- 在文档要求之外,又新增了 `GVN``LoadStoreElim`,进一步加强了内存相关和跨块冗余消除能力。
- `PassManager` 已形成迭代优化流程,而不是单次串行跑一遍后结束。
当前 `IR` 流水线在 `src/ir/passes/PassManager.cpp` 中会迭代执行:
- `RunFunctionInlining`
- `RunConstProp`
- `RunConstFold`
- `RunGVN`
- `RunLoadStoreElim`
- `RunCSE`
- `RunDCE`
- `RunCFGSimplify`
- `RunLICM`
- `RunLoopStrengthReduction`
- `RunLoopFission`
- `RunLoopUnroll`
完成判断:
- Lab4 已完成。
- 严格按文档要求看,不仅满足“基础标量优化”要求,而且已经扩展到了更强的中端优化框架。
### 3.2 Lab5寄存器分配与后端优化
Lab5 文档要求的核心是:
1. MIR 不再固定使用少量物理寄存器,而是先生成虚拟寄存器。
2. 实现真实寄存器分配,并处理 spill/reload、callee-saved、栈槽等问题。
3. 接入后端局部优化流程,减少冗余 `copy/move`、冗余 `load/store` 和明显恒等指令。
4. 在全部测试上验证正确性,并尽量提升生成代码质量。
当前实现情况:
- `Lowering` 已经输出虚拟寄存器 MIR而不是固定寄存器模板。
- `RegAlloc` 已实现真实寄存器分配,当前采用图着色风格分配流程,并处理了:
- 活跃性分析
- 干涉关系
- `copy` 合并
- spill 栈槽分配
- callee-saved 保存恢复信息回填
- live-across-call 约束
- `FrameLowering``AsmPrinter` 已经能够围绕 RA 结果完成最终栈帧和汇编输出。
- `MIR` 优化流水线已经真正接入主链:
- `PreRA``AddressHoisting + Peephole`
- `PostRA``Peephole`
后端局部优化目前已经覆盖:
- 冗余 `copy` 消除
- 恒等算术指令消除
- 条件跳转简化
- 局部冗余 `load/store` 消除
- 同块内 store-to-load forwarding
- 同地址重复 `store` 删除
- 基于 CFG 的跨块 memory dataflow
最近一轮后端进一步做了两件关键事情:
1. `MIR Peephole` 从“单基本块局部优化”提升到“带 CFG 数据流的跨块内存优化”。
2. `MIR Lowering` 调整为按可达 CFG 顺序 lowering修复了内联后复杂 CFG 下 SSA 值先用后定义导致的 lowering 失败。
说明:
- 曾尝试扩展 `v16-v18` 作为额外 FPR 可分配寄存器,但在浮点重调用样例上出现错误,因此最终回退,保留稳定寄存器集合。这一调整没有留在主线中。
完成判断:
- Lab5 已完成。
- 与文档中的“最小后端推进到真实后端”目标相比,当前实现已经超过课程最低线。
### 3.3 Lab6并行与循环优化
Lab6 文档要求的核心是:
1. 建立循环分析基础,识别循环头、循环体、前置块、退出块、回边等结构。
2. 实现有效循环优化,并接入 `PassManager`
3. 与 Lab4 标量优化协同工作。
4. 若希望进一步提升性能,可继续尝试可并行循环识别与并行化。
当前实现情况:
- 已实现 `DominatorTree``LoopInfo`,可识别自然循环及其层次关系。
- 已补齐循环变换所需的 `LoopPassUtils`
- 已接入的循环优化包括:
- `LICM`
- `LoopStrengthReduction`
- `LoopUnroll`
- `LoopFission`
- `LoopMemoryUtils` 已从较弱的循环地址分析,升级为结合:
- simple induction variable
- affine 地址表达
- exact-address key
- root-aware alias/mod-ref
- 非逃逸局部对象分析
的更强版本。
- `LICM` 已经可以更积极地 hoist 安全的 `load`,并对同地址的 hoisted load 做去重合并。
关于“并行与循环优化”中的并行部分:
- 当前已经具备可并行循环识别与依赖分析基础。
- 但没有继续接入真正的多线程并行 runtime也没有把循环改写为可直接并发执行的运行时调用。
- 结合文档表述,这部分更像“继续深入方向”,而不是 Lab6 基础完成线的硬要求。
完成判断:
- Lab6 主体已完成。
- 从比赛级编译器角度,当前已经具备较完整的单线程循环优化能力。
- 若以“真正运行时并行执行”作为额外目标,则这一部分仍可继续扩展,但不影响当前对 Lab6 主体完成的判断。
## 4. 最近一轮修改与优化
这一轮围绕比赛级目标,主要新增和加强了以下内容。
### 4.1 中端新增与增强
- 新增 `GVN`,用于更大范围复用纯表达式结果。
- 新增 `LoadStoreElim`,支持跨块冗余 `load` 消除、store-to-load forwarding、死 `store` 删除。
- 强化 `LoopMemoryUtils`,让循环内存优化不再只依赖很保守的规则。
- 强化 `LICM`,使其对安全 `load` 的外提更积极,并能对 hoisted load 做合并。
- 新增 IR 级小函数内联,使收益更早反馈到 `ConstProp`、`GVN`、`DCE`、`LICM` 等中端优化。
### 4.2 后端新增与增强
- `MIR Peephole` 从局部块内优化,扩展到基于 CFG 的跨块内存状态传播。
- `Call` 现在会按源 `IR Function` 的 effect 信息进行 `read/write` 边界判断,不再统一按最粗粒度处理。
- 修复了内联后复杂控制流下 MIR lowering 的块顺序问题。
- 完整回归后保留稳定 FPR 集合,放弃了不稳定的 `v16-v18` 扩容方案。
### 4.3 这轮优化的实际意义
这意味着最近的修改已经不只是“补课程实验功能”,而是开始面向比赛收益去提升:
- 中端:更强的冗余消除、内存优化、函数级优化、循环优化协同
- 后端:更强的 `copy/load/store` 消除与更稳定的 RA 后局部优化
## 5. 当前验证情况
本次回归中,已经完成以下验证:
### 5.1 全量正确性回归
执行:
```bash
./scripts/lab3_build_test.sh test/test_case/functional test/test_case/h_functional
```
结果:
- `134 PASS / 0 FAIL / total 134`
这说明当前 Lab4-Lab6 优化接入后,完整 `asm` 路径在 `functional + h_functional` 上保持正确。
### 5.2 性能热点抽测
执行并通过:
- `test/test_case/h_performance/fft2.sy`
- `test/test_case/h_performance/matmul3.sy`
- `test/test_case/h_performance/transpose2.sy`
- `test/test_case/h_performance/gameoflife-gosper.sy`
这些样例覆盖了:
- 重循环
- 重访存
- 浮点运算
- 矩阵访问
- 较复杂控制流
可以说明当前新增优化至少在一批代表性性能样例上保持了可运行与结果正确。
## 6. 结论
综合来看,当前编译器在 Lab4、Lab5、Lab6 上的完成情况可以概括为:
- Lab4完成并已扩展到更强的中端优化。
- Lab5完成并已形成真实可运行的后端优化链路。
- Lab6主体完成单线程循环优化能力已经达到比赛可用水平。
如果后续继续朝比赛方向推进,最值得继续做的事情不再是“补实验是否完成”,而是:
1. 针对 `h_performance` 做系统 profiling。
2. 按性能热点继续优化中端内存/循环变换。
3. 继续提升后端 spill、copy、访存质量。
4. 如需继续深入 Lab6可进一步尝试真正的并行 runtime 接入。

@ -1,227 +0,0 @@
# 编译系统实现赛道初赛设计文档
## 1. 项目概述
本项目面向 2026 年全国大学生计算机系统能力大赛编译系统设计赛实现赛道 ARM 后端方向,目标是实现一个从 SysY2026 源程序到 AArch64 汇编程序的自研编译器。
编译器整体采用经典分层架构:
```text
SysY 源程序
-> 词法/语法分析
-> 语义分析
-> IR 生成
-> IR 优化
-> MIR lowering
-> MIR 优化
-> 寄存器分配
-> 栈帧布局
-> AArch64 汇编输出
```
目标平台为 ARMv8-A 64 位架构,汇编输出兼容 GNU assembler并可由比赛环境中的 `gcc -march=armv8-a` 汇编和链接。
## 2. 编译器模块划分
### 2.1 前端
前端负责完成源程序解析、基础错误检查和语义信息收集。
主要模块包括:
- `frontend`:基于 ANTLR4 生成的 SysY 语法分析器完成词法和语法分析。
- `sem`:完成作用域管理、符号表维护、类型检查、函数声明检查、数组维度检查和内建函数建模。
- `irgen`:将语法树和语义信息转换为自定义 IR。
语义分析阶段维护了函数副作用信息,包括函数是否可能读取/写入全局内存、参数指针内存等。这些信息后续被中端内存优化、函数内联和后端 memory peephole 使用。
### 2.2 中间表示 IR
IR 是本编译器的主要优化表示。IR 采用接近 SSA 的结构,包含:
- `Module`
- `Function`
- `BasicBlock`
- `Instruction`
- `Value/User/Use`
- `GlobalValue`
- 常量、数组、指针和基础标量类型
IR 支持整数、浮点、布尔、指针、多维数组、函数调用、分支、Phi、Load/Store、GEP、Memset 等核心指令。局部变量在初始 IR 中可以通过 `alloca/load/store` 表示,随后由 `Mem2Reg` 提升为 SSA 形式。
### 2.3 MIR 与后端
MIR 是面向机器后端的中间表示。IR lowering 后不直接固定到少数物理寄存器,而是先生成虚拟寄存器形式的机器指令,再进入后端优化与寄存器分配。
主要后端模块包括:
- `Lowering`:将 IR 指令转换为 MIR 指令。
- `AddressHoisting`:提升复杂地址计算,减少重复地址表达式。
- `RegAlloc`:执行图着色风格寄存器分配。
- `FrameLowering`分配栈对象、spill slot 和 callee-saved 保存槽。
- `AsmPrinter`:根据分配结果生成最终 AArch64 汇编。
- `mir/passes`:执行机器级 peephole、CFG 清理和 spill reduction。
## 3. IR 优化设计
IR 优化流水线由 `src/ir/passes/PassManager.cpp` 管理,采用多轮迭代方式运行。每轮优化后如果 IR 继续变化,则再次执行相关 pass直到达到固定点或达到迭代上限。
当前主要 IR 优化包括:
- `Mem2Reg`:将可提升的局部变量从内存形式提升为 SSA Phi 形式。
- `ConstProp`:常量传播。
- `ConstFold`:常量折叠和代数化简。
- `DCE`:删除无副作用且结果未使用的死代码。
- `CFGSimplify`:清理不可达块、简化分支和 Phi。
- `CSE`:基本公共子表达式消除。
- `GVN`:基于支配关系的全局值编号,跨基本块复用等价表达式。
- `LoadStoreElim`IR 级 load/store 消除,包含 store-to-load forwarding、冗余 load 删除和部分死 store 删除。
- `FunctionInlining`面向小函数的内联使常量传播、GVN、DCE 等优化能跨函数生效。
这些优化的核心目标是减少冗余计算、减少内存访问、压缩控制流,并为后端生成更直接的机器代码。
## 4. 循环优化设计
循环优化建立在 `DominatorTree``LoopInfo` 之上。循环分析识别自然循环、循环头、latch、preheader、退出块和循环层次关系。
当前循环相关优化包括:
- `LICM`:循环不变代码外提。
- `LoopMemoryPromotion`:将循环内反复访问的安全内存位置提升为 SSA 标量,减少循环内 load/store。
- `LoopUnswitch`:对循环不变条件进行简单 unswitch减少循环体内重复判断。
- `LoopStrengthReduction`:归纳变量相关强度削弱。
- `LoopFission`:在依赖允许时拆分循环,改善局部性和后续优化机会。
- `LoopUnroll`:对简单计数循环进行保守展开,降低循环控制开销。
内存相关循环优化使用 `LoopMemoryUtils` 中的简单 alias/mod-ref 分析,结合 induction variable、affine 下标表达、内存 root、逃逸分析和循环内读写集合判断优化合法性。
当前没有默认启用运行时多线程并行化。原因是比赛测试程序和运行环境对正确性、可复现性要求较高,且真正并行化需要额外 runtime、任务划分和同步机制。当前实现重点放在稳定的单线程循环优化上。
## 5. 后端优化设计
### 5.1 寄存器分配
后端寄存器分配采用图着色风格算法。主要步骤包括:
- 基于 MIR CFG 进行活跃变量分析。
- 构建虚拟寄存器干涉图。
- 识别 copy 相关节点并尝试合并。
- 根据寄存器类别区分 GPR 和 FPR。
- 标记跨调用活跃的虚拟寄存器,避免错误使用 caller-saved 寄存器。
- 对无法分配的虚拟寄存器创建 spill slot。
- 记录实际使用到的 callee-saved 寄存器,供栈帧阶段保存和恢复。
当前 GPR/FPR 可分配集合优先使用稳定寄存器集合,避免与 AArch64 ABI、临时 scratch register 和调用约定冲突。
### 5.2 MIR 优化
MIR 优化分为 pre-RA 和 post-RA 两部分。
pre-RA 阶段主要优化虚拟寄存器形式的 MIR
- 冗余 copy 消除。
- 恒等算术简化,如 `add x, 0`、`mul x, 1`。
- 条件分支简化。
- 基于 CFG 的 load/store 状态传播。
- store-to-load forwarding。
- 冗余 store 删除。
- 简单 rematerialization 和 spill 压力降低。
post-RA 阶段主要在寄存器分配结果基础上继续清理:
- 删除物理寄存器层面等价的 copy。
- 利用分配结果消除无效 move。
- 清理跳转链、空块和落空分支。
### 5.3 AArch64 指令选择优化
汇编输出阶段加入了若干 ARMv8-A 专项优化:
- `mul + add/sub` 融合为 `madd/msub`
- 常数乘法 lowering 为 `lsl/add/sub/neg` 组合。
- 常数除法和取模使用 signed magic multiply 降低 `sdiv` 使用。
- spill load/store 优先直接使用 `[x29, #offset]`、`ldur/stur`。
- callee-saved 保存恢复使用 `stp/ldp` 合并。
- 相邻且安全的普通 load/store 尝试合并为 `ldp/stp`
- 输出层消除跳向直接后继基本块的无条件分支。
- 对 `icmp/fcmp + condbr` 做分支融合,减少中间布尔值物化。
浮点 `fmadd/fmsub` 没有默认启用。原因是它会改变单精度浮点逐步舍入语义,可能导致十六进制浮点输出样例不一致。当前只保留语义稳定的整数 `madd/msub`
## 6. ARM 目标平台适配
比赛 ARM 赛道目标平台为 ARMv8-A AArch64CPU 为 Cortex-A53。当前后端设计遵循以下原则
- 汇编输出使用 GNU assembler 兼容语法。
- 函数调用遵循 AArch64 基本调用约定。
- 整数返回值使用 `w0/x0`,浮点返回值使用 `s0`
- 前若干整数参数使用 `x0-x7/w0-w7`,浮点参数使用 `s0-s7`
- 栈帧以 `x29` 作为 frame pointer。
- 使用 `x16/x17` 作为汇编输出阶段 scratch register避免与寄存器分配结果冲突。
- callee-saved GPR/FPR 在函数入口保存、出口恢复。
考虑 Cortex-A53 的缓存和指令流水特性,优化策略重点放在减少访存、减少分支、减少冗余地址计算和降低 spill 数量上。
## 7. 正确性验证
项目提供脚本进行自动化验证:
```bash
scripts/lab2_build_test.sh
scripts/lab3_build_test.sh
scripts/verify_ir.sh
scripts/verify_asm.sh
```
其中 `lab3_build_test.sh` 会执行完整后端路径:
```text
SysY -> compiler -> AArch64 asm -> aarch64-linux-gnu-gcc -> qemu-aarch64 -> diff output
```
近期后端专项优化完成后,已对除法、取模、复杂调用、嵌套循环、多参数、矩阵和浮点敏感样例进行了针对性验证,代表性样例包括:
- `17_div.sy`
- `18_divc.sy`
- `19_mod.sy`
- `20_rem.sy`
- `66_exgcd.sy`
- `94_nested_loops.sy`
- `32_many_params3.sy`
- `34_multi_loop.sy`
- `22_matrix_multiply.sy`
- `37_dct.sy`
该组 targeted regression 结果为:
```text
10 PASS / 0 FAIL
```
后续初赛提交前仍需要在比赛平台或本地等价环境中执行完整功能测试和性能测试,确认最终提交版本没有回归。
## 8. 合规性说明
本项目没有直接使用 GCC、LLVM 等现有开源编译器框架源码,也没有基于这些框架进行裁剪。编译器核心 IR、优化 pass、MIR、寄存器分配和 AArch64 汇编输出均为本项目自研实现。
项目使用 ANTLR4 作为通用语法分析器生成工具,用于生成 SysY 语法分析相关代码。ANTLR4 属于比赛技术方案允许使用的通用词法/语法解析器生成工具。
本项目的优化策略均基于通用程序结构和目标平台特性例如循环结构、支配关系、内存读写关系、表达式等价性、寄存器压力、AArch64 指令模式等。没有根据特定测试用例名称、函数名、输入数据模式或输出结果进行硬编码优化。
在开发过程中使用了大模型辅助进行代码阅读、优化方案讨论、部分代码生成和调试定位。所有生成或修改内容均经过人工审查、构建和测试验证,并已纳入项目源码维护。参赛队需要在最终提交材料中继续保留该说明,并能够解释相关实现原理和代码细节。
## 9. 当前限制与后续工作
当前编译器已经具备较完整的前端、IR 优化、MIR 后端、寄存器分配和 AArch64 汇编输出能力,但仍有进一步提升空间:
- 需要在初赛提交前执行完整功能和性能回归。
- NEON 自动向量化尚未实现,原因是当前 IR/MIR 暂无 vector type 和 vector register 表示,需要单独设计。
- 显式 spill/reload MIR 化尚未完全重构,目前主要在汇编输出阶段根据分配结果生成 spill 访存。
- 循环优化仍以保守正确性为先,后续可继续加强 loop interchange、tiling、store sinking 和更强依赖分析。
- 性能优化需要结合 ARM 平台实测热点继续推进重点关注矩阵、FFT、稀疏访存、排序和大循环程序。
## 10. 总结
本编译器已经形成从 SysY 源程序到 AArch64 汇编输出的完整编译链路。前端能够完成语法和语义处理,中端具备 SSA 化、标量优化、内存优化、函数优化和循环优化能力,后端具备虚拟寄存器 MIR、图着色寄存器分配、栈帧布局、机器级 peephole 和 ARMv8-A 专项汇编优化。
后续初赛阶段的重点是继续保证功能正确性,并围绕 ARM Cortex-A53 的实际性能表现持续优化生成代码质量。

@ -1,9 +0,0 @@
int main(){
const int a[4][2] = {{1, 2}, {3, 4}, {}, 7};
const int N = 3;
int b[4][2] = {};
int c[4][2] = {1, 2, 3, 4, 5, 6, 7, 8};
int d[N + 1][2] = {1, 2, {3}, {5}, a[3][0], 8};
int e[4][2][1] = {{d[2][1], {c[2][1]}}, {3, 4}, {5, 6}, {7, 8}};
return e[3][1][0] + e[0][0][0] + e[0][1][0] + d[3][0];
}

@ -1,11 +0,0 @@
int a;
int func(int p){
p = p - 1;
return p;
}
int main(){
int b;
a = 10;
b = func(a);
return b;
}

@ -1,7 +0,0 @@
//test add
int main(){
int a, b;
a = 10;
b = -1;
return a + b;
}

@ -1,7 +0,0 @@
//test sub
const int a = 10;
int main(){
int b;
b = 2;
return b - a;
}

@ -1,69 +0,0 @@
const int V = 4;
const int space = 32;
const int LF = 10;
void printSolution(int color[]) {
int i = 0;
while (i < V) {
putint(color[i]);
putch(space);
i = i + 1;
}
putch(LF);
}
void printMessage() {
putch(78);putch(111);putch(116);
putch(space);
putch(101);putch(120);putch(105);putch(115);putch(116);
}
int isSafe(int graph[][V], int color[]) {
int i = 0;
while (i < V) {
int j = i + 1;
while (j < V) {
if (graph[i][j] && color[j] == color[i])
return 0;
j = j + 1;
}
i = i + 1;
}
return 1;
}
int graphColoring(int graph[][V], int m, int i, int color[]) {
if (i == V) {
if (isSafe(graph, color)) {
printSolution(color);
return 1;
}
return 0;
}
int j = 1;
while (j <= m) {
color[i] = j;
if (graphColoring(graph, m, i + 1, color))
return 1;
color[i] = 0;
j = j + 1;
}
return 0;
}
int main() {
int graph[V][V] = {
{0, 1, 1, 1},
{1, 0, 1, 0},
{1, 1, 0, 1},
{1, 0, 1, 0}
}, m = 3;
int color[V], i = 0;
while (i < V) {
color[i] = 0;
i = i + 1;
}
if (!graphColoring(graph, m, 0, color))
printMessage();
return 0;
}

@ -1,10 +0,0 @@
4 4
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
4 3
9 5 1
10 6 2
11 7 3
12 8 4

@ -1,60 +0,0 @@
const int MAX_SIZE = 100;
int a[MAX_SIZE][MAX_SIZE], b[MAX_SIZE][MAX_SIZE];
int res[MAX_SIZE][MAX_SIZE];
int n1, m1, n2, m2;
void matrix_multiply() {
int i = 0;
while (i < m1) {
int j = 0;
while (j < n2) {
int k = 0;
while (k < n1) {
res[i][j] = res[i][j] + a[i][k] * b[k][j];
k = k + 1;
}
j = j + 1;
}
i = i + 1;
}
}
int main()
{
int i, j;
m1 = getint();
n1 = getint();
i = 0;
while (i < m1) {
j = 0;
while (j < n1) {
a[i][j] = getint();
j = j + 1;
}
i = i + 1;
}
m2 = getint();
n2 = getint();
i = 0;
while (i < m2) {
j = 0;
while (j < n2) {
b[i][j] = getint();
j = j + 1;
}
i = i + 1;
}
matrix_multiply();
i = 0;
while (i < m1) {
j = 0;
while (j < n2) {
putint(res[i][j]);
putch(32);
j = j + 1;
}
putch(10);
i = i + 1;
}
return 0;
}

@ -1 +0,0 @@
int main() { /* scope test */ putch(97); putch(10); int a = 1, putch = 0; { a = a + 2; int b = a + 3; b = b + 4; putch = putch + a + b; { b = b + 5; int main = b + 6; a = a + main; putch = putch + a + b + main; { b = b + a; int a = main + 7; a = a + 8; putch = putch + a + b + main; { b = b + a; int b = main + 9; a = a + 10; const int a = 11; b = b + 12; putch = putch + a + b + main; { main = main + b; int main = b + 13; main = main + a; putch = putch + a + b + main; } putch = putch - main; } putch = putch - b; } putch = putch - a; } } return putch % 77; }

@ -1,15 +0,0 @@
//test break
int main(){
int i;
i = 0;
int sum;
sum = 0;
while(i < 100){
if(i == 50){
break;
}
sum = sum + i;
i = i + 1;
}
return sum;
}

@ -1,9 +0,0 @@
//test the priority of add and mul
int main(){
int a, b, c, d;
a = 10;
b = 4;
c = 2;
d = 2;
return (c + a) * (b - d);
}

@ -1,13 +0,0 @@
10
0x1.999999999999ap-4 0x1.999999999999ap-3 0x1.3333333333333p-2 0x1.999999999999ap-2 0x1.0000000000000p-1
0x1.3333333333333p-1 0x1.6666666666666p-1 0x1.999999999999ap-1 0x1.ccccccccccccdp-1 0x1.0000000000000p+0
0x1.199999999999ap+0
0x1.199999999999ap+1
0x1.a666666666666p+1
0x1.199999999999ap+2
0x1.6000000000000p+2
0x1.a666666666666p+2
0x1.ecccccccccccdp+2
0x1.199999999999ap+3
0x1.3cccccccccccdp+3
0x1.4333333333333p+3

@ -1,98 +0,0 @@
// float global constants
const float RADIUS = 5.5, PI = 03.141592653589793, EPS = 1e-6;
// hexadecimal float constant
const float PI_HEX = 0x1.921fb6p+1, HEX2 = 0x.AP-3;
// float constant evaluation
const float FACT = -.33E+5, EVAL1 = PI * RADIUS * RADIUS, EVAL2 = 2 * PI_HEX * RADIUS, EVAL3 = PI * 2 * RADIUS;
// float constant implicit conversion
const float CONV1 = 233, CONV2 = 0xfff;
const int MAX = 1e9, TWO = 2.9, THREE = 3.2, FIVE = TWO + THREE;
// float -> float function
float float_abs(float x) {
if (x < 0) return -x;
return x;
}
// int -> float function & float/int expression
float circle_area(int radius) {
return (PI * radius * radius + (radius * radius) * PI) / 2;
}
// float -> float -> int function & float/int expression
int float_eq(float a, float b) {
if (float_abs(a - b) < EPS) {
return 1 * 2. / 2;
} else {
return 0;
}
}
void error() {
putch(101);
putch(114);
putch(114);
putch(111);
putch(114);
putch(10);
}
void ok() {
putch(111);
putch(107);
putch(10);
}
void assert(int cond) {
if (!cond) {
error();
} else {
ok();
}
}
void assert_not(int cond) {
if (cond) {
error();
} else {
ok();
}
}
int main() {
assert_not(float_eq(HEX2, FACT));
assert_not(float_eq(EVAL1, EVAL2));
assert(float_eq(EVAL2, EVAL3));
assert(float_eq(circle_area(RADIUS) /* f->i implicit conversion */,
circle_area(FIVE)));
assert_not(float_eq(CONV1, CONV2) /* i->f implicit conversion */);
// float conditional expressions
if (1.5) ok();
if (!!3.3) ok();
if (.0 && 3) error();
if (0 || 0.3) ok();
// float array & I/O functions
int i = 1, p = 0;
float arr[10] = {1., 2};
int len = getfarray(arr);
while (i < MAX) {
float input = getfloat();
float area = PI * input * input, area_trunc = circle_area(input);
arr[p] = arr[p] + input;
putfloat(area);
putch(32);
putint(area_trunc); // f->i implicit conversion
putch(10);
i = i * - -1e1;
p = p + 1;
}
putfarray(len, arr);
return 0;
}

@ -1,5 +0,0 @@
int main() {
int a = 1;
int b = 2;
return a + b;
}

File diff suppressed because it is too large Load Diff

@ -1,89 +0,0 @@
const int N = 1024;
void mm(int n, int A[][N], int B[][N], int C[][N]){
int i, j, k;
i = 0; j = 0;
while (i < n){
j = 0;
while (j < n){
C[i][j] = 0;
j = j + 1;
}
i = i + 1;
}
i = 0; j = 0; k = 0;
while (k < n){
i = 0;
while (i < n){
if (A[i][k] == 0){
i = i + 1;
continue;
}
j = 0;
while (j < n){
C[i][j] = C[i][j] + A[i][k] * B[k][j];
j = j + 1;
}
i = i + 1;
}
k = k + 1;
}
}
int A[N][N];
int B[N][N];
int C[N][N];
int main(){
int n = getint();
int i, j;
i = 0;
j = 0;
while (i < n){
j = 0;
while (j < n){
A[i][j] = getint();
j = j + 1;
}
i = i + 1;
}
i = 0;
j = 0;
while (i < n){
j = 0;
while (j < n){
B[i][j] = getint();
j = j + 1;
}
i = i + 1;
}
starttime();
i = 0;
while (i < 5){
mm(n, A, B, C);
mm(n, A, C, B);
i = i + 1;
}
int ans = 0;
i = 0;
while (i < n){
j = 0;
while (j < n){
ans = ans + B[i][j];
j = j + 1;
}
i = i + 1;
}
stoptime();
putint(ans);
putch(10);
return 0;
}

File diff suppressed because it is too large Load Diff

@ -1,71 +0,0 @@
int x;
const int N = 2010;
void mv(int n, int A[][N], int b[], int res[]){
int x, y;
y = 0;
x = 11;
int i, j;
i = 0;
while(i < n){
res[i] = 0;
i = i + 1;
}
i = 0;
j = 0;
while (i < n){
j = 0;
while (j < n){
if (A[i][j] == 0){
x = x * b[i] + b[j];
y = y - x;
}else{
res[i] = res[i] + A[i][j] * b[j];
}
j = j + 1;
}
i = i + 1;
}
}
int A[N][N];
int B[N];
int C[N];
int main(){
int n = getint();
int i, j;
i = 0;
while (i < n){
j = 0;
while (j < n){
A[i][j] = getint();
j = j + 1;
}
i = i + 1;
}
i = 0;
while (i < n){
B[i] = getint();
i = i + 1;
}
starttime();
i = 0;
while (i < 50){
mv(n, A, B, C);
mv(n, A, C, B);
i = i + 1;
}
stoptime();
putarray(n, C);
return 0;
}

File diff suppressed because one or more lines are too long

@ -1,106 +0,0 @@
const int base = 16;
int getMaxNum(int n, int arr[]){
int ret = 0;
int i = 0;
while (i < n){
if (arr[i] > ret) ret = arr[i];
i = i + 1;
}
return ret;
}
int getNumPos(int num, int pos){
int tmp = 1;
int i = 0;
while (i < pos){
num = num / base;
i = i + 1;
}
return num % base;
}
void radixSort(int bitround, int a[], int l, int r){
int head[base] = {};
int tail[base] = {};
int cnt[base] = {};
if (bitround == -1 || l + 1 >= r) return;
{
int i = l;
while (i < r){
cnt[getNumPos(a[i], bitround)]
= cnt[getNumPos(a[i], bitround)] + 1;
i = i + 1;
}
head[0] = l;
tail[0] = l + cnt[0];
i = 1;
while (i < base){
head[i] = tail[i - 1];
tail[i] = head[i] + cnt[i];
i = i + 1;
}
i = 0;
while (i < base){
while (head[i] < tail[i]){
int v = a[head[i]];
while (getNumPos(v, bitround) != i){
int t = v;
v = a[head[getNumPos(t, bitround)]];
a[head[getNumPos(t, bitround)]] = t;
head[getNumPos(t, bitround)] = head[getNumPos(t, bitround)] + 1;
}
a[head[i]] = v;
head[i] = head[i] + 1;
}
i = i + 1;
}
}
{
int i = l;
head[0] = l;
tail[0] = l + cnt[0];
i = 0;
while (i < base){
if (i > 0){
head[i] = tail[i - 1];
tail[i] = head[i] + cnt[i];
}
radixSort(bitround - 1, a, head[i], tail[i]);
i = i + 1;
}
}
return;
}
int a[30000010];
int ans;
int main(){
int n = getarray(a);
starttime();
radixSort(8, a, 0, n);
int i = 0;
while (i < n){
ans = ans + i * (a[i] % (2 + i));
i = i + 1;
}
if (ans < 0)
ans = -ans;
stoptime();
putint(ans);
putch(10);
return 0;
}

File diff suppressed because it is too large Load Diff

@ -1,110 +0,0 @@
int A[1024][1024];
int B[1024][1024];
int C[1024][1024];
int main() {
int T = getint(); // 矩阵规模
int R = getint(); // 重复次数
int i = 0;
while (i < T) {
if (i < T / 2) {
getarray(A[i]);
}
i = i + 1;
}
i = 0;
while (i < T) {
if (i >= T / 2) {
getarray(B[i]);
}
i = i + 1;
}
starttime();
i = 0;
while (i < T) {
if (i >= T / 2) {
int j = 0;
while (j < T) {
A[i][j] = -1;
j = j + 1;
}
}
i = i + 1;
}
i = 0;
while (i < T) {
if (i < T / 2) {
int j = 0;
while (j < T) {
B[i][j] = -1;
j = j + 1;
}
}
i = i + 1;
}
i = 0;
while (i < T) {
int j = 0;
while (j < T) {
C[i][j] = A[i][j] * 2 + B[i][j] * 3;
j = j + 1;
}
i = i + 1;
}
i = 0;
while (i < T) {
int j = 0;
while (j < T) {
int val = C[i][j];
val = val * val + 7;
val = val / 3;
C[i][j] = val;
j = j + 1;
}
i = i + 1;
}
i = 0;
while (i < T) {
int j = 0;
while (j < T) {
int k = 0;
int sum = 0;
while (k < T) {
sum = sum + C[i][k] * A[k][j];
k = k + 1;
}
A[i][j] = sum;
j = j + 1;
}
i = i + 1;
}
int total = 0;
int r = 0;
while (r < R) {
i = 0;
while (i < T) {
int j = 0;
while (j < T) {
total = total + A[i][j] * A[i][j];
j = j + 1;
}
i = i + 1;
}
r = r + 1;
}
stoptime();
putint(total);
putch(10);
return 0;
}

File diff suppressed because one or more lines are too long

@ -1,82 +0,0 @@
const int mod = 998244353;
int d;
int multiply(int a, int b){
if (b == 0) return 0;
if (b == 1) return a % mod;
int cur = multiply(a, b/2);
cur = (cur + cur) % mod;
if (b % 2 == 1) return (cur + a) % mod;
else return cur;
}
int power(int a, int b){
if (b == 0) return 1;
int cur = power(a, b/2);
cur = multiply(cur, cur);
if (b % 2 == 1) return multiply(cur, a);
else return cur;
}
const int maxlen = 2097152;
int temp[maxlen], a[maxlen], b[maxlen], c[maxlen];
int memmove(int dst[], int dst_pos, int src[], int len){
int i = 0;
while (i < len){
dst[dst_pos + i] = src[i];
i = i + 1;
}
return i;
}
int fft(int arr[], int begin_pos, int n, int w){
if (n == 1) return 1;
int i = 0;
while (i < n){
if (i % 2 == 0) temp[i / 2] = arr[i + begin_pos];
else temp[n / 2 + i / 2] = arr[i + begin_pos];
i = i + 1;
}
memmove(arr, begin_pos, temp, n);
fft(arr, begin_pos, n / 2, multiply(w, w));
fft(arr, begin_pos + n / 2, n / 2, multiply(w, w));
i = 0;
int wn = 1;
while (i < n / 2){
int x = arr[begin_pos + i];
int y = arr[begin_pos + i + n / 2];
arr[begin_pos + i] = (x + multiply(wn, y)) % mod;
arr[begin_pos + i + n / 2] = (x - multiply(wn, y) + mod) % mod;
wn = multiply(wn, w);
i = i + 1;
}
return 0;
}
int main(){
int n = getarray(a);
int m = getarray(b);
starttime();
d = 1;
while (d < n + m - 1){
d = d * 2;
}
fft(a, 0, d, power(3, (mod - 1) / d));
fft(b, 0, d, power(3, (mod - 1) / d));
int i = 0;
while (i < d){
a[i] = multiply(a[i], b[i]);
i = i + 1;
}
fft(a, 0, d, power(3, mod-1 - (mod-1)/d));
i = 0;
while (i < d){
a[i] = multiply(a[i], power(d, mod-2));
i = i + 1;
}
stoptime();
putarray(n + m - 1, a);
return 0;
}

@ -1,51 +0,0 @@
50 50 353434
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
.....................#..#.........................
.....................#..#.........................
...................##.##.##.......................
.....................#..#.........................
.....................#..#.........................
...................##.##.##.......................
.....................#..#.........................
.....................#..#.........................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................
..................................................

@ -1,112 +0,0 @@
int sheet1[500][500] = {};
int sheet2[500][500] = {};
int active = 1;
int width;
int height;
int steps;
void read_map() {
width = getint();
height = getint();
// width <= 498, height <= 498
steps = getint();
getch();
int i = 1;
int j = 1;
while (j <= height) {
i = 1;
while (i <= width) {
int get = getch();
if (get == 35) {
sheet1[j][i] = 1;
} else {
sheet1[j][i] = 0;
}
i = i + 1;
}
// line feed
getch();
j = j + 1;
}
}
void put_map() {
int i = 1;
int j = 1;
while (j <= height) {
i = 1;
while (i <= width) {
if (sheet1[j][i] == 1) {
putch(35);
} else {
putch(46);
}
i = i + 1;
}
// line feed
putch(10);
j = j + 1;
}
}
void swap12() {
int i = 1;
int j = 1;
while (j <= height) {
i = 1;
while (i <= width) {
sheet1[j][i] = sheet2[j][i];
i = i + 1;
}
j = j + 1;
}
}
void step(int source[][500], int target[][500]) {
int i = 1;
int j = 1;
while (j <= height) {
i = 1;
while (i <= width) {
int alive_count = source[j - 1][i - 1] + source[j - 1][i] +
source[j - 1][i + 1] + source[j][i - 1] +
source[j][i + 1] + source[j + 1][i - 1] +
source[j + 1][i] + source[j + 1][i + 1];
if (source[j][i] == 1 && alive_count == 2 ) {
target[j][i] = 1;
} else if (alive_count == 3) {
target[j][i] = 1;
} else {
target[j][i] = 0;
}
i = i + 1;
}
j = j + 1;
}
}
int main() {
read_map();
starttime();
while (steps > 0) {
if (active == 1) {
step(sheet1, sheet2);
active = 2;
} else {
step(sheet2, sheet1);
active = 1;
}
steps = steps - 1;
}
stoptime();
if (active == 2) {
swap12();
}
put_map();
return 0;
}

@ -1,331 +0,0 @@
int func(int n) {
int sum = 0;
int i = 200;
int j = 0;
int s[100];
int m = 0;
while (m < 100){
s[m] = 0;
m=m+1;
}
while(j < n) {
if (i > 1){
s[1] = 1;
if (i > 2){
s[2] = 2;
if (i > 3){
s[3] = 3;
if (i > 4){
s[4] = 4;
if (i > 5){
s[5] = 5;
if (i > 6){
s[6] = 6;
if (i > 7){
s[7] = 7;
if (i > 8){
s[8] = 8;
if (i > 9){
s[9] = 9;
if (i > 10){
s[10] = 10;
if (i > 11){
s[11] = 11;
if (i > 12){
s[12] = 12;
if (i > 13){
s[13] = 13;
if (i > 14){
s[14] = 14;
if (i > 15){
s[15] = 15;
if (i > 16){
s[16] = 16;
if (i > 17){
s[17] = 17;
if (i > 18){
s[18] = 18;
if (i > 19){
s[19] = 19;
if (i > 20){
s[20] = 20;
if (i > 21){
s[21] = 21;
if (i > 22){
s[22] = 22;
if (i > 23){
s[23] = 23;
if (i > 24){
s[24] = 24;
if (i > 25){
s[25] = 25;
if (i > 26){
s[26] = 26;
if (i > 27){
s[27] = 27;
if (i > 28){
s[28] = 28;
if (i > 29){
s[29] = 29;
if (i > 30){
s[30] = 30;
if (i > 31){
s[31] = 31;
if (i > 32){
s[32] = 32;
if (i > 33){
s[33] = 33;
if (i > 34){
s[34] = 34;
if (i > 35){
s[35] = 35;
if (i > 36){
s[36] = 36;
if (i > 37){
s[37] = 37;
if (i > 38){
s[38] = 38;
if (i > 39){
s[39] = 39;
if (i > 40){
s[40] = 40;
if (i > 41){
s[41] = 41;
if (i > 42){
s[42] = 42;
if (i > 43){
s[43] = 43;
if (i > 44){
s[44] = 44;
if (i > 45){
s[45] = 45;
if (i > 46){
s[46] = 46;
if (i > 47){
s[47] = 47;
if (i > 48){
s[48] = 48;
if (i > 49){
s[49] = 49;
if (i > 50){
s[50] = 50;
if (i > 51){
s[51] = 51;
if (i > 52){
s[52] = 52;
if (i > 53){
s[53] = 53;
if (i > 54){
s[54] = 54;
if (i > 55){
s[55] = 55;
if (i > 56){
s[56] = 56;
if (i > 57){
s[57] = 57;
if (i > 58){
s[58] = 58;
if (i > 59){
s[59] = 59;
if (i > 60){
s[60] = 60;
if (i > 61){
s[61] = 61;
if (i > 62){
s[62] = 62;
if (i > 63){
s[63] = 63;
if (i > 64){
s[64] = 64;
if (i > 65){
s[65] = 65;
if (i > 66){
s[66] = 66;
if (i > 67){
s[67] = 67;
if (i > 68){
s[68] = 68;
if (i > 69){
s[69] = 69;
if (i > 70){
s[70] = 70;
if (i > 71){
s[71] = 71;
if (i > 72){
s[72] = 72;
if (i > 73){
s[73] = 73;
if (i > 74){
s[74] = 74;
if (i > 75){
s[75] = 75;
if (i > 76){
s[76] = 76;
if (i > 77){
s[77] = 77;
if (i > 78){
s[78] = 78;
if (i > 79){
s[79] = 79;
if (i > 80){
s[80] = 80;
if (i > 81){
s[81] = 81;
if (i > 82){
s[82] = 82;
if (i > 83){
s[83] = 83;
if (i > 84){
s[84] = 84;
if (i > 85){
s[85] = 85;
if (i > 86){
s[86] = 86;
if (i > 87){
s[87] = 87;
if (i > 88){
s[88] = 88;
if (i > 89){
s[89] = 89;
if (i > 90){
s[90] = 90;
if (i > 91){
s[91] = 91;
if (i > 92){
s[92] = 92;
if (i > 93){
s[93] = 93;
if (i > 94){
s[94] = 94;
if (i > 95){
s[95] = 95;
if (i > 96){
s[96] = 96;
if (i > 97){
s[97] = 97;
if (i > 98){
s[98] = 98;
if (i > 99){
s[99] = 99;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
j=j+1;
int m = 0;
while (m < 100){
sum = sum + s[m];
m=m+1;
}
sum = sum % 65535;
}
return sum;
}
int main() {
starttime();
int loopcount = getint();
putint(func(loopcount));
putch(10);
stoptime();
return 0;
}

@ -1,49 +0,0 @@
int COUNT = 500000;
float loop(float x[], float y[], int length) {
int i = 0;
float accumulator = 0.0;
while (i < length) {
accumulator = accumulator + x[i] * y[i];
i = i + 1;
}
return accumulator;
}
int main() {
int i = 0, j = 0;
int len = getint();
float x[4096];
float y[4096];
float total = 0.0;
float a = 0.0;
float b = 1.0;
starttime();
while ( i < COUNT) {
if (i % 10) {
a = 0.0;
b = 1.0;
} else {
a = a + 0.1;
b = b + 0.2;
}
while ( j < len) {
x[j] = a + j;
y[j] = b + j;
j = j + 1;
}
total = total + loop(x, y, len);
i = i + 1;
}
stoptime();
if ((total - 11442437121638400.000000) <=0.000001 || (total - 11442437121638400.000000) >= -0.000001) {
putint(0);
return 0;
}
else {
putint(1);
return 1;
}
}

@ -1,2 +0,0 @@
10000000
30 2 5 4 25 8 125 16 625 32 3125 2 5 4 25 8 125 16 625 32 3125 2 5 4 25 8 125 16 625 32 3125

@ -1,51 +0,0 @@
int matrix[20000000];
int a[100000];
int transpose(int n, int matrix[], int rowsize){
int colsize = n / rowsize;
int i = 0;
int j = 0;
while (i < colsize){
j = 0;
while (j < rowsize){
if (i < j){
j = j + 1;
continue;
}
int curr = matrix[i * rowsize + j];
matrix[j * colsize + i] = matrix[i * rowsize + j];
matrix[i * rowsize + j] = curr;
j = j + 1;
}
i = i + 1;
}
return -1;
}
int main(){
int n = getint();
int len = getarray(a);
starttime();
int i = 0;
while (i < n){
matrix[i] = i;
i = i + 1;
}
i = 0;
while (i < len){
transpose(n, matrix, a[i]);
i = i + 1;
}
int ans = 0;
i = 0;
while (i < len){
ans = ans + i * i * matrix[i];
i = i + 1;
}
if (ans < 0) ans = -ans;
stoptime();
putint(ans);
putch(10);
return 0;
}

@ -1,85 +0,0 @@
int func(int i, int j) {
return ((i+j) * (i+j+1) / 2 + i + 1);
}
float Vectordot(float v[], float u[], int n) {
int i = 0;
float sum = 0;
while (i < n) {
sum =sum+ v[i] * u[i];
i=i+1;
}
return sum;
}
void mult1(float v[], float out[],int n) {
int i = 0, j = 0;
float sum = 0;
while (i < n) {
while (j < n) {
sum =sum+ v[j] / func(i,j);
j=j+1;
}
out[i] = sum;
i=i+1;
}
}
void mult2(float v[], float out[], int n) {
int i = 0, j = 0;
float sum = 0;
while (i < n) {
while (j < n) {
sum =sum+ v[j] / func(j,i);
j=j+1;
}
out[i] = sum;
i=i+1;
}
}
void mult_combin(float v[], float out[], int n, float tmp[]) {
mult1(v, tmp, n);
mult2(tmp, out, n);
}
float temp = 1;
float my_sqrt(float input) {
while (temp - input / temp > 1e-6 || temp - input / temp < -1e-6){
temp = (temp+input/temp)/2;
}
return temp;
}
int main() {
int n = 100000;
if (n <= 0) {
n = 2000;
}
starttime();
float vectorA[100000], vectorB[100000], Vectortmp[100000];
int i;
while(i < n) {
vectorA[i] = 1;
i=i+1;
}
i = 0;
while(i < 1000) {
mult_combin(vectorA, vectorB, n, Vectortmp);
mult_combin(vectorB, vectorA, n, Vectortmp);
i=i+1;
}
stoptime();
float result = my_sqrt(Vectordot(vectorA,vectorB, n) / Vectordot(vectorB,vectorB,n));
if(result - 1.000000 <= 1e-6 && result - 1.000000 >= -1e-6){
putint(1);
}else{
putint(0);
}
putch(10);
return 0;
}

@ -1,3 +0,0 @@
int main(){
return 3;
}

@ -1,8 +0,0 @@
//test domain of global var define and local define
int a = 3;
int b = 5;
int main(){
int a = 5;
return a + b;
}

@ -1,8 +0,0 @@
//test local var define
int main(){
int a, b0, _c;
a = 1;
b0 = 2;
_c = 3;
return b0 + _c;
}

@ -1,4 +0,0 @@
int a[10][10];
int main(){
return 0;
}

@ -1,9 +0,0 @@
//test array define
int main(){
int a[4][2] = {};
int b[4][2] = {1, 2, 3, 4, 5, 6, 7, 8};
int c[4][2] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
int d[4][2] = {1, 2, {3}, {5}, 7 , 8};
int e[4][2] = {{d[2][1], c[2][1]}, {3, 4}, {5, 6}, {7, 8}};
return e[3][1] + e[0][0] + e[0][1] + a[2][0];
}

@ -1,9 +0,0 @@
int main(){
const int a[4][2] = {{1, 2}, {3, 4}, {}, 7};
int b[4][2] = {};
int c[4][2] = {1, 2, 3, 4, 5, 6, 7, 8};
int d[3 + 1][2] = {1, 2, {3}, {5}, a[3][0], 8};
int e[4][2][1] = {{d[2][1], {c[2][1]}}, {3, 4}, {5, 6}, {7, 8}};
return e[3][1][0] + e[0][0][0] + e[0][1][0] + d[3][0];
}

@ -1,6 +0,0 @@
//test const gloal var define
const int a = 10, b = 5;
int main(){
return b;
}

@ -1,5 +0,0 @@
//test const local var define
int main(){
const int a = 10, b = 5;
return b;
}

@ -1,5 +0,0 @@
const int a[5]={0,1,2,3,4};
int main(){
return a[4];
}

@ -1,8 +0,0 @@
int defn(){
return 4;
}
int main(){
int a=defn();
return a;
}

@ -1,5 +0,0 @@
//test addc
const int a = 10;
int main(){
return a + 5;
}

@ -1,6 +0,0 @@
//test subc
int main(){
int a;
a = 10;
return a - 2;
}

@ -1,7 +0,0 @@
//test mul
int main(){
int a, b;
a = 10;
b = 5;
return a * b;
}

@ -1,5 +0,0 @@
//test mulc
const int a = 5;
int main(){
return a * 5;
}

@ -1,7 +0,0 @@
//test div
int main(){
int a, b;
a = 10;
b = 5;
return a / b;
}

@ -1,5 +0,0 @@
//test divc
const int a = 10;
int main(){
return a / 5;
}

@ -1,6 +0,0 @@
//test mod
int main(){
int a;
a = 10;
return a / 3;
}

@ -1,6 +0,0 @@
//test rem
int main(){
int a;
a = 10;
return a % 3;
}

@ -1,25 +0,0 @@
// test if-else-if
int ifElseIf() {
int a;
a = 5;
int b;
b = 10;
if(a == 6 || b == 0xb) {
return a;
}
else {
if (b == 10 && a == 1)
a = 25;
else if (b == 10 && a == -5)
a = a + 15;
else
a = -+a;
}
return a;
}
int main(){
putint(ifElseIf());
return 0;
}

@ -1,18 +0,0 @@
// test if-if-else
int ififElse() {
int a;
a = 5;
int b;
b = 10;
if(a == 5)
if (b == 10)
a = 25;
else
a = a + 15;
return (a);
}
int main(){
return (ififElse());
}

@ -1,18 +0,0 @@
// test if-{if-else}
int if_ifElse_() {
int a;
a = 5;
int b;
b = 10;
if(a == 5){
if (b == 10)
a = 25;
else
a = a + 15;
}
return (a);
}
int main(){
return (if_ifElse_());
}

@ -1,18 +0,0 @@
// test if-{if}-else
int if_if_Else() {
int a;
a = 5;
int b;
b = 10;
if(a == 5){
if (b == 10)
a = 25;
}
else
a = a + 15;
return (a);
}
int main(){
return (if_if_Else());
}

@ -1,31 +0,0 @@
int get_one(int a) {
return 1;
}
int deepWhileBr(int a, int b) {
int c;
c = a + b;
while (c < 75) {
int d;
d = 42;
if (c < 100) {
c = c + d;
if (c > 99) {
int e;
e = d * 2;
if (get_one(0) == 1) {
c = e * 2;
}
}
}
}
return (c);
}
int main() {
int p;
p = 2;
p = deepWhileBr(p, p);
putint(p);
return 0;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save