ZIG与LLVM的新关系

2020-09-28 23:24:37

虽然还没有发布1.0版,但Zig即将达到成熟和稳定的新水平。

在早期,Zig只是LLVM面前的一个薄薄的前端。这对于快速入门和填补Andrew作为编译器开发人员的知识空白很有帮助。现在,自行车的训练轮正在脱落,LLVM正在转变为一个可选部件。

用新的纯Zig版本替换当前C++编译器实现的工作已经开始。转向自托管实现通常被认为是走向成熟的一步,语言本身的开发人员感受到了最大的好处。例如,Go通过切换到自托管编译器而损失了一些编译速度,但作为交换,它简化了工具链,消除了依赖,并改善了整个开发体验。

转移到Zig的自托管编译器对核心贡献者有类似的优势,但它也使LLVM成为可选的依赖项,提高编译速度(而不是失去它),并为代码的调试版本添加了一个令人惊叹的特性:使用就地二进制修补的增量编译,这是另一个独特的Zig特性。

大多数语言都提供某种形式的缓存来加快编译速度,从C的编译单元开始,一直到更现代语言中的模块、包和其他类似的边界。

Zig还实现了一个缓存系统,在构建混合了C和Zig源代码的项目时,或者将Zig用作带有zig cc命令的C编译器时,该系统特别方便。ZIG跟踪编译过程中涉及的所有文件,因此可以非常容易地知道目标文件何时可以重用,而这只是使用ZIG编译C代码的优势之一。

Zig源代码总是捆绑到单个编译单元中,因此当前形式的缓存系统在编辑和重新编译纯Zig项目时不会提供任何加速。好处是,不仅编译Zig代码非常快,而且增量编译将为Zig代码提供智能缓存,而不仅仅是弥补了我们无法从简单缓存中获得的东西。

增量编译是一种缓存形式,它在比正常的“编译单元”级缓存更高的粒度级别进行操作。Rust博客有一篇很棒的帖子,解释了它是如何工作的。

对于Rust,编译器在AST级别构建依赖图,然后将其与缓存的中间结果(目标文件)一起保存到磁盘。当请求新的编译时,编译器将能够很容易地注意到AST的哪些部分已经更改,从而使依赖于它的所有中间结果无效。

关于此图的一个重要细节是,最右边的框始终是无效的。换句话说,最终的可执行文件总是从头开始重新链接,从旧的和新生成的目标文件混合开始。很明显,情况肯定是这样的,因为最终的可执行文件依赖于其他所有东西,所以对代码的任何有意义的更改都会使其无效,但这正是Zig自托管编译器为表带来新的巧妙想法的地方。

从Zig版本0.6.0开始,无论发布的类型是什么(调试、安全发布、快速发布),总会有最后一步委托给LLVM,这至少会占用总编译时间的70%,包括编译调试版本时,其中甚至没有启用优化。

自托管编译器将不依赖于LLVM进行调试构建,并且将能够显著缩短编译时间,基本上将这70%减少到几乎为零,因为与LLVM相比,它是一款更简单的软件。

最重要的是,由于编译器将完全控制整个过程,因此它将使用针对增量编译进行优化的特别策略生成机器码,从而允许编译器使用新的更改就地修补最终的可执行文件。

就地二进制打补丁基于顶级声明的粒度。每个全局变量和函数都可以独立打补丁,因为最终的二进制文件被构造为一系列松散耦合的块。另一个重要特征是所有这些信息都保存在内存中,因此编译器将在两次编译之间保持打开状态。

如果您想看看自托管编译器的运行情况,下面是Andrew的5分钟演示:

高效的就地二进制修补只能通过紧密耦合编译器前端和后端来实现。这个特性在野外非常少见的部分原因是它违背了我们更好的抽象感和干净的代码组织。但我们永远不能忘记:抽象只是达到实际结果的一种工具,并不总是最合适的工具。

为了执行就地二进制修补,我们需要代码与位置无关。这允许我们在函数在其分配的边界之外增长时,在虚拟内存中移动它。我们还需要能够间接引用虚拟地址,以便在将函数移动到新的虚拟地址时不需要更新N个调用点。

然而,这只解决了函数问题。这里需要考虑更多组件,例如调试信息。当我们向函数添加新行时,它会修改调试信息,该信息用于打印堆栈跟踪!解决这一问题需要创造性地组织调试行信息的分配方案,并找出如何进行NOP。安德鲁在这里的旅程包括创建一个新的矮人线路号码操作码的提案。

对于每种链接后端-ELF、DWARF、PE、PDB、MACO和WebAssembly,这个问题都必须反复解决。特别感谢挺身而出并接受支持就地二进制修补的额外挑战的贡献者:Alexandros Naskos、Jakub Konka和Isaac Freund。

请关注Andrew的博客上更具技术性的帖子,在那里他将深入研究这些引人入胜的细节-包括这种设计如何让我们达到90%的热代码交换!

自托管后端仍在进行中,但本文中提供的所有功能都经过了设计和原型设计,现在只需要完成工作的有条不紊的部分。

自托管后端将在一个FLAG后面发布Zig 0.7.0,只支持Zig语言的一个子集。与此同时,核心开发团队和其他一些贡献者正在冲刺,提供更多的语言支持和更多的目标。目前的目标是用Zig 0.8.0的自托管后端完全取代C++实现,大约7个月后。

如果你喜欢Zig要去的地方,现在是加入Zig社区的最佳时机,如果你想帮助加快开发速度,请考虑捐赠给Zig软件基金会,让核心开发者花更多的时间在Zig上。