QBE - 编译器后端:版本 1.3
摘要
QBE 1.3 是一个重要的编译器后端版本,新增了 7000 行代码,引入了一种新的 IL 匹配算法,针对 coremark 基准测试进行了优化(性能从 gcc -O2 的 40% 提升到超过 63%),支持 Windows ABI 和位置无关代码生成。
<p><a href="https://lobste.rs/s/aahxxs/qbe_compiler_backend_version_1_3">评论</a></p>
查看缓存全文
缓存时间: 2026/06/01 12:31
# QBE - 编译器后端
来源:https://c9x.me/compile/release/qbe-1.3.html
- 简介 (https://c9x.me/compile/)
- 文档 (https://c9x.me/compile/docs.html)
- 用户 (https://c9x.me/compile/users.html)
- 发布 (https://c9x.me/compile/releases.html)
- 代码 (https://c9x.me/compile/code.html)
### QBE 1.3 - 发布说明
qbe-1.3 (https://c9x.me/compile/releases.html)
QBE 1.3 历经较长时间才成型,但却是自 1.0 以来最重要的版本,新增约 7k 行代码,删除 1.5k 行。除了常规的 bug 修复,QBE 还获得了一种全新的、独创的 IL 匹配算法,Roland Paterson-Jones 贡献了新的优化,Scott Graham 添加了对 Windows ABI 的支持,而我实现了 Michael Forney 建议的一项计划,让 QBE 能够生成位置无关代码(如共享对象)。QBE 是团队协作的成果,我很高兴感谢所有为此次发布做出贡献的人。在接下来的说明中,我们将详细探讨本次发布的几个重点。
#### 更快
每隔一段时间,QBE 这只蚊子就会蜇一下某位杰出的程序员。这次,Roland 成为了目标!Roland 建议我们研究 coremark (https://www.eembc.org/coremark/) 基准测试,以便拥有一个具体但简单的优化沙盒。使用 qbe-1.2 的初步测量显示,我们远远落后于“达到 `gcc -O2` 性能的 70%”的目标,实际只有 40% 左右。我们决定在 1.3 版本中解决这个问题。
对性能分析数据的早期检查发现,性能差距很大程度上源于两个函数的处理方式:`ee_isdigit` (https://github.com/eembc/coremark/blob/1f483d5b8316753a742cbf5590caf5bd0a4e4777/core_state.c#L197-L203) 和 `crcu8` (https://github.com/eembc/coremark/blob/1f483d5b8316753a742cbf5590caf5bd0a4e4777/core_util.c#L164-L188)。有趣的是,这些函数并非典型的 C 语言写法;例如,`ee_isdigit` 通常是文本内联的,使用 `&&` 而不是 `&`,并且跳过了多余的三目运算符;至于 CRC,最好的实现方式是使用预计算表。这个观察结果有点令人失望,因为除了 QBE 缺乏内联功能外,它并没有指出一个适用于其他编译任务的通用性能瓶颈。另一方面,CPU 密集型代码确实会将大部分时间花在紧凑的代码段中。
尽管如此,我们实现了大量优化(GVN/GCM、循环优化、if消除、CFG简化等),并在 coremark 以及更实际的使用场景(如 Hare (https://harelang.org/) 测试套件)中进行了尝试。最终,我们决定只保留一套经过验证的优化 pass,现在在原始 coremark 上,我们的性能达到了商业编译器的 63% 以上。值得注意的是,我们将内联排除在优化集合之外,以推迟解决其与 QBE 流式逐函数编译模型不兼容的问题。通过修改 coremark 基准测试,内联 `ee_isdigit` 函数并使用更简单的无分支 `crcu8` 实现,QBE 可以达到其 70% 的目标。新的优化也应该惠及 Hare 用户:我测量到,与 qbe-1.2 相比,Hare 测试套件的性能提升了 33%(1.7 秒对比 2.6 秒)。
#### 更智能
从早期开始,QBE 就使用了一种受 Ken Thompson 的 Plan9 C 编译器 (https://c9x.me/compile/bib/new-c.pdf)(见 5.5. 可寻址性)启发的自底向上树编号算法。该算法相当通用,但在优雅处理算术运算符的结合性和交换性方面有些微妙之处。实现一个元编程解决方案来解决这个问题一直是我的长期目标。QBE 1.3 实现了这个目标。
一个新的 OCaml 工具 `mgen` 被用来将 Lispy 的 IL 模式编译成惯用的 C 代码以匹配它们。`mgen` 工具会查找包含 IL 模式的特殊注释块,并在这些块的正下方内联匹配的 C 代码。生成的 C 代码设计为在 qbe 中看起来惯用,并且工作方式类似于 1.3 之前的手写逻辑。请参阅 `isel.c` (https://c9x.me/git/qbe.git/tree/amd64/isel.c?id=c0818978acec60ebb6167fade60fb7012cbf20ca#n667) 文件查看当前 `mgen` 的使用情况。
更详细地说,指令 DAG 的匹配方式类似于 Ken Thompson 编译器中的编号方法。然后,`mgen` 将每个编号关联到一个位集,该位集指示当前 IL 节点(临时变量)匹配哪些顶层用户模式;然后可以通过手写逻辑选择最合适的模式。模式可以包含变量,这些变量可以通过运行匹配器程序来收集。这些程序也是由 `mgen` 以一种简单的字节码语言生成的,`runmatch()` (https://c9x.me/git/qbe.git/tree/util.c?id=c0818978acec60ebb6167fade60fb7012cbf20ca#n723) 函数可以解释执行。
我期望未来 `mgen` 能用于简化更多后端的指令选择,甚至可能在优化 pass 中识别 IL 模式,例如位旋转。
#### 更好用
在 1.3 版本中,QBE 也蜇了 Scott Graham (https://scot.tg/)。Scott 慷慨地将他对 Windows ABI 的实现进行了上游化,该实现最初出现在一个衍生作品 (https://github.com/sgraham/sqbe) 中。QBE 生成的汇编仍然是 AT&T 语法,最好由 mingw 汇编器编译,尽管我本人没有在 Windows 上尝试过。现在为 Windows 编译只需要向 QBE 传递 `-t amd64_win` 参数。
最后同样重要的是,QBE 改进了对位置无关代码的支持,现在能够在大多数目标上顺利链接,甚至生成共享对象。到目前为止的主要障碍是缺乏对全局变量的间接访问支持(例如,ELF 上的全局偏移表)。现在通过 IL 层面支持一个新的 `extern` “动态常量”标志(`DYNCONST` in the IL spec)可以实现这一点。例如,要访问动态链接库中的变量 `dlvar`,可以使用:
``
function w $load() {
@start
%v =w load extern $dlvar
ret %v
}
``
如果你好奇,我们使用矛盾修辞“动态常量”来指代只能在运行时(动态)确定的地址符号(在整个执行过程中恒定),因为它们是由运行时或动态链接器分配的,而不是由编译的常规链接阶段分配。
感谢阅读至此,祝你编程愉快。
相似文章
QBE – 编译器后端
QBE 是一个紧凑的、爱好级别的编译器后端,仅用 10% 的代码即可实现工业级优化编译器 70% 的性能,支持 amd64、arm64 和 riscv64,并采用简单的基于 SSA 的中间语言。
Blaise – 一款面向 QBE 的现代、自举、无历史包袱的 Object Pascal 编译器
Blaise 是一款现代且自举的 Object Pascal 编译器,旨在通过提供单一语言模式、统一的内存模型以及基于 QBE 的原生代码生成,来消除遗留系统的负担。
Qwen3.6-27B Uncensored Aggressive 发布,附带 K_P 量化!
社区释出去除安全拒答的 Qwen3.6-27B,并以专为 llama.cpp 与 LM Studio 优化的 K_P GGUF 量化格式打包。
Zig ELF 链接器改进开发日志
新的 Zig ELF 链接器现在支持外部库和 C 源码的快速增量编译,在 x86_64 Linux 上能够实现毫秒级重建。
BeeLlama.cpp:支持推理和视觉的先进 DFlash 与 TurboQuant。在 RTX 3090 上以 200k 上下文运行 Qwen 3.6 27B Q5,速度比基线快 2-3 倍(峰值 135 tps!)
BeeLlama.cpp 是一个专注于性能的 llama.cpp 分支,引入了 DFlash 投机解码和 TurboQuant KV 缓存压缩技术,使得在消费级硬件上也能高速本地运行像 Qwen 3.6 27B 这样的大型模型。