后端与系统工具链¶
后端到底在做什么¶
到了后端阶段,前面的工作基本已经把程序逻辑整理清楚了。
后端这时候不该再重新猜程序想干什么了。前面几层已经把结构和语义整理过一遍,后端要做的是把这些结果翻成真正能继续构建的代码。
当前项目里,这个目标代码不是原生汇编,而是规范化 C。
为什么先输出 C¶
这件事值得反复强调,因为它决定了整个项目第一代的形态。
输出规范化 C 的好处包括:
- 可以立刻借助系统编译器生成可执行文件
- macOS 和 Linux 上都更容易工作
- 中间产物更容易人工阅读
- 不用马上处理 ABI、汇编语法和目标文件细节
所以这个后端更像一座桥,而且是一座先求稳、先能走人的桥,不是一步到位就奔着最炫的原生汇编去。
当前后端其实也有两种模式¶
1. IR Backend¶
如果程序走基础 int 子集路径,后端读取的是 IR。
此时它会把每条 IR 指令翻译成较直接的 C 语句,例如:
const-> 常量赋值copy-> 普通赋值binary-> 二元表达式jump->gotocjump->if ... goto ... else goto ...return->return
这一条路径的特点很简单:前面的 lowering 已经把控制流拆开了,后端不用再猜 if、while、for 的执行顺序。
2. AST Backend¶
如果程序用了 char、字符串、数组、指针等扩展特性,后端读取的就是 AST。
此时它不是按 IR 指令翻译,而是直接把 AST 重新渲染成 C 结构,例如:
- 指针声明
- 数组声明
- 下标访问
- 字符串字面量
这条路径会保留更多源码里的原始长相,因为这些高级特性现在还没有统一压进那套 IR。
什么叫“规范化 C”¶
这里的“规范化”不是说它写得多优雅,而是说它写得够规矩、够统一、够稳定。
例如:
- 生成形式尽量统一
- 局部变量和临时变量显式声明
- 控制流直接展开
- 输出风格更利于检查和测试
也就是说,这份 C 代码首先是写给编译器自己和调试者看的,其次才是写给人欣赏的。
为什么系统工具链仍然不可少¶
即使后端已经生成了 C,项目本身还没有直接产出最终可执行文件。
最后这一步仍然需要系统工具链来完成,例如 clang 或 cc。
它们负责处理:
- 真正的 C 编译
- 目标文件生成
- 链接
- 平台相关细节
这也就是 toolchain.py 真正在接手的地方。到了这里,项目自己的工作差不多已经做完了,剩下的是把生成结果安全地交给外部工具链。
ToolchainDriver 在做什么¶
当前 ToolchainDriver 主要做三件事:
- 选择后端并生成后端文本
- 把生成结果写到中间
.generated.c文件 - 调用系统里的 C 编译器,产出最终可执行文件
这里把中间文件真的写到磁盘上,不是多此一举,而是很有用,因为:
- 构建失败时可以直接翻生成代码
- 你能看到编译器到底吐出了什么
- 做回归测试和手工排错都方便
为什么这条设计很适合第一代¶
如果第一代就直接做原生汇编后端,需要同时解决:
- 指令选择
- 寄存器分配
- 调用约定
- 栈帧管理
- 平台差异
那样复杂度会一下子冲上去,前面很多本来已经讲清楚的东西,也会被更底层的细节淹掉。
而当前设计的聪明地方就在于,它把难题拆开了:
- 先把前端和中间层做扎实
- 再用规范化 C 先跑通执行链
- 以后如果需要,再把后端替换成更底层实现
这不是“保守”,更像是先把能跑通、能看懂、能验证的部分做扎实,再去碰更难啃的底层骨头。