2024 年 Google 编程之夏成果

2024 年 11 月 7 日 · Jakub Beránek, Jack Huey 和 Paul Lenz

正如我们之前宣布的,Rust 项目今年首次参与了Google 编程之夏 (GSoC)。九位贡献者几个月来一直在不懈地努力完成他们激动人心的项目。项目持续时间各不相同;有些在八月结束,而最后一个在十月中旬结束。现在所有项目的最终报告都已提交,我们可以高兴地宣布,所有九位贡献者都通过了最终评审!这意味着我们认为他们的所有项目都取得了成功,尽管它们可能没有实现所有最初设定的目标(但这也在预期之内)。

我们与 GSoC 贡献者进行了许多愉快的互动,根据他们的反馈,他们似乎对 GSoC 项目也相当满意,并且学到了很多。我们当然也非常感谢他们的所有贡献——他们中的一些甚至在项目结束后仍在继续贡献,这真的很棒。总的来说,我们认为 2024 年 Google 编程之夏对于 Rust 项目来说是成功的,我们期待在不久的将来再次参与 GSoC(或类似项目)。如果你有兴趣成为一名 (GSoC) 贡献者,请查看我们的项目想法列表

下面你可以找到每个 GSoC 2024 项目的简要总结,包括贡献者和导师的反馈。你可以在这里找到更多关于项目的信息。

为 cargo-semver-checks 添加 Lint 级别配置

cargo-semver-checks 是一个旨在自动检测语义版本冲突的工具,计划将来成为 Cargo 本身的一部分。本项目旨在通过允许用户配置哪些 lint 在哪些情况下运行,以及其发现是否报告为错误或警告,从而使 cargo-semver-checks 能够提供额外的*可选* lint。Max 通过在 Cargo.toml 清单文件中直接实现一个全面的配置系统来配置 cargo-semver-checks lint,从而实现了这一目标。他还与 Cargo 团队广泛讨论了设计,以确保它与 Cargo 中其他 lint 的配置方式兼容,并且不会给 cargo-semver-checks 合并到 Cargo 带来未来的兼容性问题。

Predrag 是 cargo-semver-checks 的作者,也是 Max 在此项目中的导师,他对 Max 的贡献感到非常满意,这些贡献甚至超出了他最初的项目范围。

他设计并构建了我们最受欢迎的功能之一,并制作了用户会喜欢的另外几个功能的设计原型。他还注意到编写高质量的 CLI 和功能测试很困难,因此他彻底改进了我们的测试系统,使更好的测试更容易制作。未来在 cargo-semver-checks 上的工作将因 Max 今年夏天的努力而变得更加容易。

干得漂亮,Max!

为 Cranelift 实现更快的寄存器分配器

Rust 编译器可以使用各种*后端*来生成可执行代码。主要的当然是 LLVM 后端,但也有其他后端,例如 GCC.NETCranelift。Cranelift 是一个针对各种硬件目标的代码生成器,本质上类似于 LLVM。Cranelift 后端使用 Cranelift 将 Rust 代码编译成可执行代码,旨在提高编译性能,尤其是对于调试(未优化)构建。尽管此后端可能已经比 LLVM 后端更快,但我们发现它受到 Cranelift 使用的寄存器分配器的拖累。

寄存器分配是一个众所周知的编译器任务,编译器决定哪些寄存器应该保存程序的变量和临时表达式。通常,寄存器分配的目标是以最大化编译程序运行时性能的方式执行寄存器分配。然而,对于未优化构建,我们通常更关心编译速度。

因此,Demilade 提议实现一个新的 Cranelift 寄存器分配器,称为 fastalloc,目标是使其尽可能快,但可能会牺牲生成的代码质量。他准备得非常充分,事实上,在他的 GSoC 项目开始之前,他就已经准备好了一个原型实现!然而,寄存器分配是一个复杂的问题,因此花费了几个月的时间来完成实现并尽可能地优化它。Demilade 还广泛使用了模糊测试,以确保他的分配器在各种边缘情况下都具有鲁棒性。

分配器准备就绪后,Demilade 使用我们的编译器基准测试套件对 Cranelift 后端分别使用原始分配器和他的新分配器进行了基准测试。结果令人惊叹!使用他更快的寄存器分配器,Rust 编译器在多个基准测试中执行的指令减少了多达 18%,包括像执行 Cargo 本身的调试构建这样复杂的测试。请注意,这是编译整个 crate 所需时间的*端到端*性能提升,这确实令人印象深刻。如果你想更详细地查看结果,甚至自己运行基准测试,请查看 Demilade 的最终报告,其中包含如何重现基准测试的详细说明。

除了有可能加速 Rust 代码的编译外,新的寄存器分配器也可以用于其他用例,因为它可以在 Cranelift 中独立使用(在 Cranelift 代码生成后端之外)。除了我们对 Demilade 的工作非常满意之外,我们还能说什么!请注意,新的寄存器分配器尚未在 Cranelift 代码生成后端中直接可用,但我们预计它最终将成为调试构建的默认选择,从而在未来使使用 Cranelift 后端编译 Rust crates 更快。

改进 Rust 基准测试套件

这个项目相对宽松地定义,总体目标是改进 Rust 编译器基准测试套件的用户界面。Eitaro 同时从各个角度应对了这一挑战。他改进了运行时基准测试的可视化,这些基准测试之前在基准测试套件中处于次要地位,通过将它们添加到我们的仪表板中,并实现了运行时基准测试结果的历史图表,这有助于我们了解给定的基准测试在较长时间内的表现。

他工作的另一个改进是直接在 rustc-perf 网站中嵌入分析器跟踪可视化工具。这是一项具有挑战性的任务,需要他评估多个可视化工具,并找出一种在不破坏性地将其包含在基准测试套件源代码中的方法。最终,他成功地将 Perfetto 集成到套件网站中,并还进行了各种优化以提高加载编译配置文件的性能。

最后但同样重要的是,Eitaro 还为基准测试套件创建了一个全新的用户界面,该界面完全在终端中运行。通过此界面,Rust 编译器贡献者可以检查编译器的性能,而无需启动 rustc-perf 网站,这在本地部署时可能具有挑战性。

除了上述贡献外,Eitaro 还对基准测试套件的各个部分进行了许多其他较小的改进。感谢你的所有工作!

将 Cargo shell 补全功能迁移到 Rust

Cargo 的补全脚本一直是手动维护的,并且在更改时经常出错。这项工作的目标是让补全功能从 Cargo 命令行定义中自动生成,并具有动态生成结果的扩展点。

shanmu 拿起了 clap(Cargo 使用的命令行解析器)中动态补全的原型,使其在常用 shell 中正常工作并进行了测试,并扩展了解析器以涵盖更多情况。然后他们为 CLI 添加了扩展点,以便提供可以动态生成的自定义补全结果。

在下一阶段,shanmu 将此功能添加到 nightly Cargo 中,并添加了不同的自定义补全器以匹配手写补全的功能。例如,启用此功能后,当你键入 cargo test --test= 并按 Tab 键时,你的 shell 将自动补全当前 Rust crate 中的所有测试目标!如果你有兴趣,请参阅说明进行尝试。该链接也列出了你可以提供反馈的地方。

你还可以查看以下 issue,了解在稳定此功能之前还剩下哪些工作:

使用强大的 Rust 特性重写晦涩、易出错的 Makefile 测试

Rust 编译器有几个测试套件,用于确保它在各种条件下正常工作。其中一个套件是 run-make 测试套件,其测试之前是使用 Makefile 编写的。然而,这种设置带来了一些问题。无法在 Tier 1 Windows MSVC 目标 (x86_64-pc-windows-msvc) 上运行该套件,并且让它在 Windows 上运行本身就相当具有挑战性。此外,Makefile 的语法非常晦涩,这使得即使经过多人审查也经常忽略错误。

Julien 帮助将基于 Makefilerun-make 测试转换成了纯粹的基于 Rust 的测试,并由一个名为 run_make_support 的测试支持库提供支持。然而,这并非一个简单的“用 Rust 重写”的任务。在此项目中,Julien

  • 显著改进了测试文档;
  • 修复了 Makefile 版本中存在多年未被发现的多个 bug——有些测试从未测试过任何东西或静默忽略了失败,因此即使被测试的对象退步,这些测试也不会发现问题。
  • 补充并改进了测试支持库的 API 和实现;以及
  • 改进了测试中的代码组织,使其更容易理解和维护。

为了让你了解他工作范围的大小,他在 GSoC 项目期间移植了近 250 个 Makefile 测试!如果你喜欢双关语,请查看 Julien 的 PR 分支名称,它们简直*fantestic*(精彩极了)。

结果,Julien 大大提高了 run-make 测试套件的健壮性,并提高了修改现有 run-make 测试和编写新 run-make 测试的人体工程学。多位贡献者表示,他们更愿意使用基于 Rust 的 run-make 测试,而不是之前的 Makefile 版本。

绝大多数 run-make 测试现在使用基于 Rust 的测试基础设施,由于各种奇特的因素,只剩下少数遗留项。解决这些问题后,我们最终可以移除遗留的 Makefile 测试基础设施。

重写 Rewrite trait

rustfmt 是一个 Rust 代码格式化工具,由于它直接集成在 Cargo 中,因此在 Rust 生态系统中被广泛使用。通常,你只需运行 cargo fmt 就可以立即享受格式正确的 Rust 项目。然而,在某些边缘情况下,rustfmt 可能无法格式化你的代码。这本身不是什么大问题,但当它*静默*失败而没有给出用户任何上下文说明问题出在哪里时,就变得更加麻烦了。这就是 rustfmt 中发生的情况,许多函数只返回一个 Option 而不是 Result,这使得添加适当的错误报告变得困难。

SeoYoung 项目的目标是对 rustfmt 进行大规模内部重构,以便能够跟踪重格式化过程中出现问题的上下文。反过来,这将使静默失败转变为适当的错误消息,从而帮助用户检查和调试出了什么问题,甚至可以允许 rustfmt 在更多情况下重试格式化。

起初,这听起来像是一个简单的任务,但在像 rustfmt 这样复杂的项目中进行如此大规模的重构并非易事。SeoYoung 需要想出一种方法来逐步应用这些重构,以便它们易于审查,并且不会同时影响整个代码库。她引入了一个新的 trait,它增强了原始的 Rewrite trait,并修改了现有的实现以与之对齐。她还必须处理我们在项目开始前没有预料到的各种边缘情况。SeoYoung 的方法一丝不苟且系统化,并确保没有遗漏任何格式化函数或方法。

最终,重构取得了成功!现在,rustfmt 内部跟踪更多与格式化失败相关的信息,包括它以前无法报告的错误,例如宏格式化的问题。它还能够提供关于源代码 span 的信息,这有助于识别超出最大行宽时需要调整间距的代码部分。我们尚未将这些额外的失败上下文作为用户可见的错误消息传播出去,因为这是一个我们没有时间完成的更高目标,但 SeoYoung 表示有兴趣继续以此为未来改进点!

除了研究错误上下文传播外,SeoYoung 还进行了各种其他改进,提高了代码库的整体质量,并且她还帮助其他贡献者理解 rustfmt。感谢你为每个人改进格式化的基础!

Rust 到 .NET 编译器 - 添加对编译和运行 Cargo 测试的支持

如上所述,Rust 编译器可以与各种代码生成后端一起使用。其中之一是 .NET 后端,它将 Rust 代码编译成通用中间语言 (CIL),然后可以由 .NET 公共语言运行时 (CLR) 执行。此后端允许 Rust 和 .NET(例如 C#)代码的互操作性,旨在拉近这两个生态系统的距离。

今年年初,.NET 后端已经能够编译复杂的 Rust 程序,但仍缺少某些关键功能。此 GSoC 项目由 Michał 实现,他实际上是此后端的唯一作者,项目目标是扩展此后端在各个领域的功能。他设定了一个目标,即将后端扩展到可以使用 cargo test 命令运行测试。尽管听起来微不足道,但正确编译和运行 Rust 测试套件并非易事,因为它利用了动态 trait 对象、原子操作、panic、展开或多线程等复杂功能。在代码生成后端中实现这些功能尤其棘手,因为 LLVM 中间表示 (IR) 和 CIL 存在根本性差异,并且并非所有 LLVM intrinsic 都有 .NET 等价物。

然而,这并没有阻止 Michał。他不知疲倦地致力于这个项目,实现新功能,修复各种问题,每天学习更多关于编译器内部的知识。他还在 Zulip 上以(几乎)每日更新的方式记录他的旅程,这些更新读起来引人入胜。一旦他达到了最初的目标,他就将目标提高到了一个新的水平,并尝试使用 .NET 后端运行编译器自身的测试套件。这帮助他发现了额外的边缘情况,也导致了整个后端的重构,从而显著提高了性能。

到 GSoC 项目结束时,.NET 后端能够正确编译和运行标准库 corestd 测试套件的近 90%。这是一个令人难以置信的数字,因为该套件包含数千个测试,其中一些相当*晦涩*。项目结束后,Michał 的步伐并未放慢,他仍在不断改进后端。哦,我们有没有提到他的后端还实验性地支持发出 *C* 代码,实际上充当了一个 *C* 代码生成后端?!Michał 在夏天非常忙碌。

我们感谢 Michał 在 .NET 后端上的所有工作,这确实令人鼓舞,并带来了与其他代码生成后端相关的富有成效的讨论。Michał 的下一个目标是将其后端合并到上游,并创建一个官方的 .NET 编译目标,这可能会为 Rust 成为 .NET 生态系统中的一等公民打开大门。

使用 WebAssembly 实现沙箱化且确定性的过程宏

Rust 过程 (proc) 宏目前以原生代码的形式运行,这些代码被编译成一个共享对象,直接加载到 Rust 编译器的进程中。由于这种设计,这些宏可以做任何它们想做的事情,例如任意访问文件系统或通过网络通信。这不仅带来明显的安全隐患,也影响性能,因为这种设计使得难以缓存过程宏调用。多年来,关于使过程宏更具*密封性*的讨论层出不穷,例如将它们编译成 WebAssembly 模块,这些模块可以在沙箱中轻松执行。这也有可能通过 crates.io 分发预编译的过程宏版本,以加快依赖于过程宏的 crates 的全新构建速度。

本项目旨在研究为过程宏实现 WebAssembly 模块支持需要做哪些工作,并创建此想法的原型。我们知道这将是一个非常雄心勃勃的项目,特别是因为 Apurva 没有贡献 Rust 编译器的先前经验,而且过程宏内部非常复杂。尽管如此,还是取得了一些进展。在他的导师 David 的帮助下,Apurva 成功创建了一个原型,可以通过共享对象将 WebAssembly 代码加载到编译器中。还完成了一些工作,以便利用编译器 proc_macro crate 中现有的 TokenStream 序列化和反序列化代码。

尽管这个项目没有实现其最初的目标,未来还需要更多工作才能获得一个功能性的 WebAssembly 过程宏原型,但我们仍然感谢 Apurva 的贡献。WebAssembly 加载原型是一个好的开端,并且 Apurva 对过程宏内部的探索应该为未来从事此功能的人提供有用的参考。展望未来,我们将尝试为我们的 GSoC 项目描述更多增量步骤,因为这个项目从一开始就可能过于雄心勃勃。

Miri 中对 Tokio 异步支持

miri 是一个解释器,可以查找 Rust 代码中可能存在的未定义行为实例。它正在 Rust 生态系统中得到使用,但之前无法在任何使用 tokio 的非平凡程序(那些需要 await 任何东西的程序)上运行它,这是由于一个根本性缺失的功能:对 Linux 上 epoll 系统调用(以及其他主要平台上的类似 API)的支持。

Tiffany 实现了覆盖大部分 tokio 测试套件所需的基本 epoll 操作,方法是创建纯 libc 代码示例来演示这些 epoll 操作,然后在 miri 本身中实现它们的模拟。有时,这需要重构 miri 的核心组件,例如文件描述符处理,因为它们最初并非考虑到 epoll 等系统调用而创建的。

令所有人(尽管可能不会令 tokio 内部专家)惊讶的是,一旦这些核心 epoll 操作完成,诸如异步文件读写之类的操作就开始在 miri 中开箱即用地工作了!由于操作系统提供的非阻塞文件操作的限制,tokio 将这些文件操作包装在专用线程中,而 miri 已经支持这一点。

Tiffany 完成项目后,包括实现异步文件操作等更高目标,她继续联系 tokio 维护者,并与他们合作在 CI 中使用 miri 运行大多数 tokio 测试。我们有好消息:到目前为止,没有发现任何健全性问题!Tiffany 已成为 miri 的常态贡献者,专注于继续扩展支持的文件描述符操作集合。我们感谢她的所有贡献!

总结

我们很荣幸能够参与 2024 年 Google 编程之夏项目,也感谢所有贡献者!我们期待明年再次加入 GSoC 项目。