Gleam 不再编译出 Erlang 源码:直接输出语法树

Gleam 不再编译出 Erlang 源码:直接输出语法树

GleamErlangCompilerBEAM

数据源:gleam.run

跳过源码,直接生成抽象语法树

2026 年 10 月 5 日,Gleam v1.19.0 正式发布。开发者 Giacomo Cavalieri 耗费数月时间,将 Erlang 代码生成器进行了重写。从这个版本开始,Gleam 不再输出人类可读的 Erlang 源码文本,而是将目标产物直接锁定为 Erlang abstract forms。

Abstract forms 并非新生事物,它是 Erlang 编译器内部使用的一种中间表示形态。在常规的编译流水线中,Erlang 源码文件需要经过词法分析器(tokeniser)的拆解,再交由语法分析器(parser)进行重组,最终才能生成这样一棵带有丰富元数据注解的抽象语法树。Gleam 团队利用了基于 Erlang external term format 的二进制编码机制,让生成器直接吐出这种二进制格式的代码。编译器加载这些产物后,可以直接跳过前半程的文本处理阶段。

这条技术路径在 BEAM 生态中早有先例,Elixir 正是通过 abstract forms 编译到 Erlang。正如 Gleam 创始人 Louis Pilfold 在公告中所言,既然这种机制足以支撑 Elixir 的庞大生态,它也一定能承载 Gleam。当一门新兴语言面对足够成熟的底层环境时,把文本解析的重复工作交还给虚拟机基础设施,是获取架构轻量化的最佳捷径。

全量构建碾压,行号精准对应

跨过文本解析阶段带来的第一项显性收益,是构建速度的突飞猛进。根据 José Valim 设计的 langcompilebench 基准测试平台数据,在编译 100 个模块、且每个模块包含 100 个返回字符串的函数的场景下,全量且无缓存的构建耗时被压缩。多语言横向对比显示,Gleam 在 Erlang 后端的编译速度仅次于 Go 和编译到 JavaScript 的同款 Gleam,甚至超越了 Erlang 原生编译器本身。

尽管官方声明这是一个刻意构造的基准测试,不足以据此对各语言性能下最终定论,但速度量级差距足以说明架构缩减的威力。Gleam 自身的编译机制原本就是增量的,日常开发中的体感速度会比基准测试的数据表现更加平滑。

Gleam 构建时间对比 图:Gleam v1.17 与 v1.19 编译同一基准项目(100 模块 × 100 函数,全量构建)的耗时对比,横轴为毫秒。来源:gleam.run 发布公告

比速度更深远的工程红利,体现在运行时元数据的质量上。过去由于生成的是中间形态的 Erlang 代码文件,一旦应用程序发生崩溃,BEAM 抛出的崩溃报告和堆栈跟踪中的行号信息,只能模糊地定位到最接近的函数入口。如今,所有的位置元数据都能精确映射到开发者编写的原始 Gleam 源码文件。

构建产物越过源码层并保留完整元数据,开发者在排查故障时所面临的上下文断层被抹平。这些精确的元数据体系,也为后续接入 edb 等专业级调试器铺垫了基础设施。即便核心团队当前并未在外部调试器上投入精力,这种标准化的数据格式输出,已经为第三方工具链的跟进扫清了底层障碍。

多语言耗时对照 图:扩展后的多语言编译耗时对照,含 Gleam(JavaScript)、Go、Gleam(Erlang)、Erlang、Java、Elixir、Elm、Rust、C#、TypeScript 7。来源:gleam.run 发布公告

为什么不直出虚拟机字节码

既然目标是绕开前端工具链,为何不一步到位,干脆让编译器直接生成 BEAM 字节码?这个技术选项在设计之初就被明确否决。核心原因在于,BEAM 字节码并不像 Erlang 源码或 abstract forms 那样具备长期的静态稳定性。

Erlang 虚拟机的每一次版本迭代,都可能伴随着字节码格式的演进。新的指令会被加入,陈旧冗余的功能会被剔除。直接生成字节码,意味着 Gleam 的核心维护团队必须永远追踪这套底层演化,并与新版虚拟机的发布节奏强制绑定。更致命的是,Erlang 编译器在过去几十年中积累了庞大且深度的优化策略,即便 Gleam 拥有更强大的静态分析能力作为支撑,想要凭一己之力复刻这些优化,工作量也庞大得惊人。

资源禀赋决定了技术选型的边界。Gleam 作为一个纯粹由社区赞助的开源项目,预算和人力规模远不及由企业或学术机构背书的主流语言。他们必须挑选最可持续的技术路径,避免陷入无休止的维护泥潭。在预算受限的技术演进中,认清自身边界并果断放弃最佳性能理论解,远比盲目承接技术债务更能决定项目的长期存续。

撕掉转译器刻板标签

被替换掉的老版本 Erlang 代码生成器,曾经是整个 Gleam 代码库中最古老、也最稳定的模块之一。它在服役期间从未引发过严重故障,甚至不曾违背过原有的设计初衷,但却未能跟上项目如今的规范要求。它不仅阻碍了新特性的引入,更让这门语言长久背负着某种原罪。在发布公告中,官方用一句轻松的调侃道出了这次重写的深层动机:我们再也不用忍受别人把 transpiler 当成贬义词来使用了。

这项后端重写的红利边界十分清晰,一切收益均局限于编译到 Erlang 的主链路。JavaScript 侧的编译路径并未受惠于此。与此平行的是,JavaScript 后端在同一版本也获得了独立的优化,John Downey 改进了模式匹配的决策树生成算法,将嵌套的条件分支折叠成单一条件,减少了生成的中间变量。两条路径在架构上相互解耦,演进过程互不干扰。

把代码生成器的输出形态从源码切换到 abstract forms,Gleam 用一次务实的底层重写,换来了更快的构建速度、精确的调试行号以及更加规范的编译器代码。真正的门槛是一个独立运作的语言项目,是否愿意在合适的时机把沉重的编译器前端控制权交还给成熟的生态系统。社区语言的预算养不起一套要永远跟进 VM 演化的 BEAM 字节码生成器,把前端交还给上游、把力气留在类型系统上,是它能长期活下去的算法。

参考链接:

  • gleam.run 发布公告