如何调试整条编译流水线¶
为什么这个项目适合逐层调试¶
很多编译器项目的痛点是:你只能看到“最后成功还是失败”,中间发生了什么不透明。
这个项目有个挺大的优点:它把好几层中间结果都亮出来了。这样你排错时就不用一上来只会问“为什么最后错了”,而是能问得更准:
- 是 Lexer 切错了 Token 吗
- 是 Parser 把结构解析错了吗
- 是 IR 展开有问题吗
- 是后端生成结果不对吗
这对学习和排错都很值钱,因为编译器最怕的就是所有问题最后都挤成一个“运行结果不对”。
最常用的几个观察入口¶
--emit-tokens¶
适合先看:
- 字面量是否被正确识别
- 注释是否被正确跳过
- 双字符运算符是否被误拆
--emit-ast¶
适合先看:
- 表达式优先级是否正确
if / while / for结构是否正确挂接- 顶层声明是否被正确区分
--emit-ir¶
适合先看:
- 控制流是否被正确展开
- 临时变量是否合理
- 赋值、跳转、返回是否符合预期
--emit-ir-dot¶
适合先看:
- 条件分支走向
- 循环结构
- 控制流图形态
--emit-c¶
适合先看:
- 后端最终生成了什么
- 程序是走 IR Backend 还是 AST Backend
- 生成代码是否容易被系统编译器接受
一条很实用的排错顺序¶
如果你发现“最终程序不对”,建议按这个顺序查:
- 先看
--emit-tokens - 再看
--emit-ast - 如果是基础
int子集,再看--emit-ir - 最后看
--emit-c
这样查的好处是,不会一上来就把所有锅都甩给“编译器出问题了”,而是能很快把问题圈到某一层。
怎么判断自己走的是哪条编译路径¶
很简单:
- 如果输出了真实 IR 指令,说明你在走 IR 路径
- 如果 IR 显示这是高级特性程序,或者最终 C 明显保留了数组、指针、字符串等高层结构,那通常是在走 AST Backend 路径
这点很要命,因为两条路径看的东西不一样。你如果连自己走哪条路都没搞清楚,后面很容易越查越偏。
为什么中间文件也值得看¶
在真正构建可执行文件时,项目会写出 .generated.c 中间文件。
这个文件对排错特别有用,因为它能帮你分清一个很关键的问题:
- 是编译器前面就生成错了,还是系统工具链在后面编译时失败了
如果 .generated.c 本身就不合理,问题通常在项目自己的前后端逻辑。
如果 .generated.c 合理但系统编译失败,问题可能更靠近工具链或平台环境。
调试时要避免什么误区¶
- 不要一上来只盯最终二进制行为
- 不要忽略自己到底走的是 IR 路径还是 AST Backend 路径
- 不要把词法、语法、语义错误都混在一起看
编译器调试最有效的方法,基本永远都是分层定位。别上来就扑最终结果,那样最容易把自己绕进去。