现代编译器写作计划:从 Rust 到 LLVM、Wasm 和 MLIR
这不是一篇“把一个表达式计算器写出来”的短教程,而是一套可以拆成连载的编译器写作计划。目标有三个:读者能跟着写,例子能跟着跑,技术选型不会很快过时。技术锚点按 2026-09-05 的官方文档来定:Rust 1.98.1 / Rust 2024 edition,LLVM 23.1.0,Wasmtime 45.0.0,WASI 0.2/0.3 preview,以及仍在活跃演进中的 MLIR。
这本书要写什么
这套教程的主线不是“如何写一个最小可运行编译器”,而是“如何写一条今天仍然成立的编译器路线”。前半部分讲前端:词法、语法、名字绑定、类型检查、错误恢复。中间部分讲 IR:AST、typed HIR、SSA、控制流图、数据流分析。后半部分讲后端:LLVM IR、Cranelift、WebAssembly component model、WASI,再加一章 MLIR 作为多层 IR 的扩展方向。
这条路线的好处很直接。读者先学到经典编译器骨架,再看到现代工具链怎么把这条骨架接到真实系统上。写完以后,代码可以落到原生二进制,也可以落到 Wasm component;同一套中端还能顺着 LLVM 和 Cranelift 两条路跑出去。
技术选型
| 层次 | 选型 | 作用 |
|---|---|---|
| 语言实现 | Rust 2024 | 语义清晰,内存安全,适合写可读的编译器基础设施 |
| 前端 | 手写 lexer / parser | 便于讲清 token、AST、错误恢复和语法优先级 |
| 中端 | typed HIR + SSA | 对齐现代编译器的真实中间形态 |
| 主后端 | LLVM 23.1.0 | 适合讲经典 lowering、优化和 AOT 代码生成 |
| 扩展后端 | Cranelift / Wasmtime 45.0.0 | 适合讲 JIT、嵌入式运行时和低延迟代码生成 |
| 运行时接口 | Wasm component model + WASI 0.3 preview | 适合讲宿主能力、接口组合和跨语言边界 |
| 进阶 IR | MLIR | 适合讲多层 lowering、DSL 和异构后端 |
主后端选 LLVM,原因不是它“最潮”,而是它最适合教学。LLVM 23.1.0 的发布版文档已经足够稳定,Kaleidoscope 教程也把从 AST 到 LLVM IR、JIT、对象文件、调试信息这一整条链路讲得很完整。用它做主线,读者能把“编译器教科书里的抽象阶段”直接对上“现实工具链里的 IR 和代码生成”。
扩展后端选 Cranelift,原因是它更像一个“第二视角”。Wasmtime 的架构文档明确写着 wasmtime-cranelift 是函数级编译实现,而且 Cranelift 在 Wasmtime 里是当前必需依赖。把它放进教程里,能顺手讲清楚 JIT、嵌入式运行时和生产级 WebAssembly 执行器是怎么分工的。
章节结构
第一部分写前端。先定义语言,再写词法器和递归下降解析器。这里不追求炫技,重点是把 token、AST、错误信息和优先级表讲清楚。每一章都要有一小段可运行代码和一组失败样例,读者才能看见编译器在哪一步拒绝了输入。
第二部分写语义分析。把名字解析、作用域、类型系统和错误恢复拆开讲。不要把“类型检查”混成一个黑箱。最好先写出 typed AST,再引入 HIR,让读者看清楚“语法正确”和“语义正确”不是同一件事。
第三部分写中端。这里才进入 SSA、控制流图、支配关系、常量传播、死代码删除和内联。编译器书最容易写散的地方就是优化;解决办法不是堆算法名词,而是围绕同一个小语言反复加特性。每加一个特性,就补一组 IR dump 和一组优化前后对照。
第四部分写主后端 LLVM。写法应当是“先输出文本 LLVM IR,再下到对象文件或 JIT”。这样读者能直接看见 opt、llc、clang 这一套工具是怎么串起来的。这里建议固定到 LLVM 23.1.0 的 release 文档,不要引用 git 主干文档,因为教学内容需要版本锚点。
第五部分写 Cranelift 和 Wasmtime。这个部分不替代 LLVM,而是补一个“另一种工程视角”:更小的 IR、更快的编译、更直接的嵌入式运行时。把同一门语言再编一次,读者会很快看懂两种后端在设计取舍上的差异。
第六部分写 Wasm component model 和 WASI。这里不只是在“把程序编到 wasm”,而是在讲“编译器如何面对宿主能力”。WASI 0.2 是 component model 基础上的模块化 API 集合,WASI 0.3 是当前 preview,加入了 async、stream 和 future。这部分最适合用 WIT 讲接口,而不是回到裸 syscalls。
第七部分写 MLIR。MLIR 不适合放在开篇,它更像进阶章或附录。它的价值在于把“语言前端 + 多层 lowering + 异构目标”统一到一套可扩展的 IR 框架里。对于只教单一语言的教程,MLIR 可以晚一点出现;对于想扩展到 DSL、GPU、向量化或张量编译的教程,MLIR 才是正解。
每章该交付什么
每一章都要留下一个可验证的结果,而不是只留概念。
- 词法 / 语法章:一个可打印 AST 的命令行工具
- 语义分析章:带准确报错信息的类型检查器
- IR 章:可 dump 的 typed HIR 和 SSA
- 优化章:能看到前后差异的 IR 变换
- LLVM 章:能生成对象文件或可执行文件
- Cranelift 章:能在 Wasmtime 里跑起来
- Wasm 章:能通过 component model 暴露宿主接口
- MLIR 章:能把一个小 DSL lower 到 LLVM dialect 或 wasm 相关路径
写书时最怕的是每章都“讲完了”,但没有一个能跑的落点。每章最后都应该有一个小实验,一次命令,一份输出,一段能复现的结果。
结尾该怎么收
这类书最容易写成两种样子:一种是老式教科书,只讲原理不讲当代工具;另一种是工具清单,只讲 LLVM、Wasm、MLIR 的名字,不讲编译器本身。比较稳妥的写法是把二者拆开:前半本讲编译器骨架,后半本讲现代后端和运行时。这样既保留了“从零构建”的教学节奏,也不会把内容锁死在过时的技术栈上。
