QBE 1.3 发布:轻量级编译器后端的新突破
QBE(Quick Backend Engine)是一个轻量级的编译器后端,定位介于 LLVM 的复杂和手写代码生成之间的中间地带。近日发布的 1.3 版本是其自 1.0 以来最重要的版本,新增约 7k 行代码,删除 1.5k 行。
更快:从 GCC 40% 到 63% 的性能飞跃
QBE 团队以 coremark 基准测试为目标进行优化。初始测量显示 qbe-1.2 仅达到 GCC -O2 约 40% 的性能,远低于项目"70% 目标"。
分析性能数据后发现,瓶颈主要集中在对两个函数的处理上:ee_isdigit 和 crcu8。这两个函数本身并不符合典型的 C 惯用法——ee_isdigit 通常被内联展开,使用 && 而不是 &,并跳过了多余的三元运算符;CRC 最好用预计算表实现。
团队实现了大量优化:全局值编号(GVN)、全局代码移动(GCM)、循环优化、if-elimination、控制流图简化等。最终筛选出了经过充分验证的 passes 子集,在标准 coremark 上达到了 63% 的性能。值得注意的是,内联优化被排除在外,原因是它与 QBE 的流式按函数编译模型存在兼容性问题。
对 Hare 语言测试套件的测量显示,相比 qbe-1.2 有 33% 的提升(1.7s vs 2.6s)。
更智能:全新的 IL 匹配算法
QBE 长期以来一直使用受 Ken Thompson 的 Plan 9 C 编译器启发的自底向上树编号算法。该算法足够通用,但在优雅处理算术运算符的结合性和交换性方面存在微妙问题。
1.3 版本实现了一个全新的元编程解决方案:一个名为 mgen 的 OCaml 工具,它将 Lisp 风格的 IL 模式编译为能匹配这些模式的惯用 C 代码。
具体来说,mgen 会查找包含 IL 模式的特殊注释块,并在这块注释下方内联生成匹配的 C 代码。指令 DAG 通过类似于 Ken Thompson 编译器的编号方法进行匹配,然后 mgen 将每个编号与一个位集关联起来,指示当前 IL 节点匹配了哪些顶层用户模式。
模式可以包含变量,这些变量通过一个由 mgen 生成的简单字节码语言解释器 runmatch() 来收集。未来,mgen 有望被用来简化更多后端中的指令选择,甚至可能用于在优化 passes 中识别 IL 模式(如位旋转)。
更好:Windows ABI 支持与位置无关代码
Scott Graham 贡献了 Windows ABI 的实现。QBE 生成的汇编保持 AT&T 语法,推荐使用 mingw 汇编器编译。现在编译 Windows 目标只需传递 -t amd64_win 参数。
QBE 还改进了对位置无关代码(PIC)的支持,现在可以在大多数目标上顺利链接甚至生成共享对象。之前的障碍是缺乏对全局变量的间接访问支持(如 ELF 的 GOT)。现在通过 IL 层支持一个新的 extern "动态常量"标记(DYNCONST)实现了这一功能。
结语
QBE 1.3 是一次重大的功能升级。它在性能、编译质量和平台支持上都取得了实质性进步,同时保持了轻量级和简洁的核心设计哲学。对于需要嵌入式编译器的项目,或那些觉得 LLVM 过于重量级的开发者来说,QBE 正变得越来越有吸引力。
项目地址:c9x.me/compile/