Files
nudt-compiler-cpp/doc/Lab2-中间表示生成.md
2026-03-18 02:07:34 +08:00

5.1 KiB
Raw Permalink Blame History

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. visitFuncDefModule 中创建 Function,并把 IRBuilder 的插入点设置到入口基本块。
  4. visitBlockStmt / visitBlockItem 顺序遍历块内声明与语句。
  5. visitDecl / visitVarDef 为局部变量生成 alloca 和初始化 store
  6. visitExp 相关函数递归生成常量、loadadd 等表达式值。
  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 的关系

当前项目中的 semairgen 都不是面向独立 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. 构建与运行

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"

7. Lab2 验证方式

可先用单个样例检查 IR 输出是否基本正确:

./build/bin/compiler --emit-ir test/test_case/functional/simple_add.sy

推荐使用统一脚本验证 “IR -> LLVM 后端 -> 可执行程序” 整体链路。--run 模式下会自动读取同名 .in,并将程序输出与退出码和同名 .out 比对,用于验证 IR 的正确性:

./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 链路都能通过;如有需要,也可以自行编写批量测试脚本统一执行。