正如我们之前宣布的那样,Rust 项目今年首次参与了 Google 编程之夏 (GSoC)。九位贡献者在过去几个月里不知疲倦地致力于他们令人兴奋的项目。这些项目的持续时间各不相同;其中一些已于 8 月结束,而最后一个项目已于 10 月中旬结束。现在所有项目的最终报告都已提交,我们可以高兴地宣布,所有九位贡献者都通过了最终评审!这意味着我们认为他们的所有项目都是成功的,即使他们可能没有实现所有最初的目标(但这也在意料之中)。
我们与 GSoC 贡献者进行了许多很棒的互动,并且根据他们的反馈,他们似乎对 GSoC 计划也很满意,并且学到了很多东西。我们当然也非常感谢他们所做的所有贡献——其中一些人甚至在项目结束后仍在继续贡献,这真的很棒。总的来说,我们认为 2024 年 Google 编程之夏对于 Rust 项目来说是成功的,我们期待在不久的将来再次参与 GSoC(或类似的项目)。如果您有兴趣成为 (GSoC) 贡献者,请查看我们的项目想法列表。
您可以在下面找到我们每个 2024 年 GSoC 项目的简要概述,包括贡献者和导师本人的反馈。您可以在此处找到有关这些项目的更多信息。
为 cargo-semver-checks 添加 lint 级别配置
- 贡献者:Max Carr
- 导师:Predrag Gruevski
- 最终报告
cargo-semver-checks 是一种用于自动检测语义版本控制冲突的工具,计划有一天成为 Cargo 本身的一部分。该项目的目标是使 cargo-semver-checks 能够通过允许用户配置在哪些情况下运行哪些 lint,以及它们的发现是报告为错误还是警告,来发布额外的选择加入 lint。Max 通过实现一个全面的系统来实现此目标,该系统用于直接在 Cargo.toml
清单文件中配置 cargo-semver-checks
lint。他还与 Cargo 团队广泛讨论了该设计,以确保它与其他 Cargo lint 的配置方式兼容,并且不会为将 cargo-semver-checks 合并到 Cargo 中带来未来的兼容性问题。
cargo-semver-checks
的作者 Predrag 担任 Max 这个项目的导师,他对 Max 的贡献非常满意,甚至超出了他最初的项目范围
他设计并构建了我们最需要的功能之一,并制作了更多用户会喜欢的功能的设计原型。他还观察到编写高质量的 CLI 和功能测试很困难,因此他彻底改造了我们的测试系统,使制作更好的测试变得更容易。感谢 Max 今夏的辛勤工作,未来对 cargo-semver-checks 的工作将变得更加容易。
干得好,Max!
为 Cranelift 实现更快的寄存器分配器
- 贡献者:Demilade Sonuga
- 导师:Chris Fallin 和 Amanieu d'Antras
- 最终报告
Rust 编译器可以使用各种后端来生成可执行代码。主要的当然是 LLVM 后端,但还有其他后端,例如 GCC、.NET 或 Cranelift。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 crate 的速度更快。
改进 Rust 基准测试套件
- 贡献者:Eitaro Kubotera
- 导师:Jakub Beránek
- 最终报告
该项目定义相对宽松,其总体目标是改进 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 中的所有测试目标!如果您有兴趣,请参阅说明来尝试一下。该链接还列出了您可以提供反馈的位置。
您还可以查看以下问题,以了解在将其稳定之前还剩下哪些工作
使用健壮的 Rust 功能重写深奥且容易出错的 makefile 测试
- 贡献者: Julien Robert
- 导师: Jieyou Xu
- 最终报告
Rust 编译器有几个测试套件,用于确保它在各种条件下正常工作。其中一个套件是 run-make
测试套件,该套件的测试以前是使用 Makefile
编写的。然而,这种设置带来了一些问题。无法在 Tier 1 Windows MSVC 目标 (x86_64-pc-windows-msvc
) 上运行该套件,并且让它在 Windows 上运行本身就非常具有挑战性。此外,Makefile
的语法非常深奥,这经常导致即使经过多人审查,错误也未被注意到。
Julien 帮助将基于 Makefile
的 run-make
测试转换为基于纯 Rust 的测试,并由名为 run_make_support
的测试支持库提供支持。然而,这并不是一个简单的“用 Rust 重写”的问题。在这个项目中,Julien
- 显著改进了测试文档;
- 修复了
Makefile
版本中存在多年且未被注意到的多个错误——有些测试从未测试任何东西或静默地忽略了失败,因此即使被测试的对象出现回归,这些测试也不会发现。 - 添加并改进了测试支持库 API 和实现;以及
- 改进了测试中的代码组织,使其更容易理解和维护。
为了让您了解他的工作范围,他在他的 GSoC 项目期间移植了近 250 个 Makefile 测试!如果您喜欢双关语,请查看 Julien 的 PR 的分支名称,因为它们简直太棒了。
因此,Julien 显著提高了 run-make
测试套件的健壮性,并改进了修改现有 run-make
测试和编写新的 run-make
测试的人体工程学。多位贡献者表示,他们更愿意使用基于 Rust 的 run-make
测试,而不是以前的 Makefile
版本。
绝大多数 run-make
测试现在使用基于 Rust 的测试基础设施,由于各种怪癖,还剩下一些遗留问题。在这些问题解决后,我们最终可以删除遗留的 Makefile
测试基础设施。
重写 Rewrite trait
- 贡献者: SeoYoung Lee
- 导师: Yacin Tmimi
- 最终报告
rustfmt 是一个 Rust 代码格式化工具,由于它直接集成在 Cargo 中,因此在 Rust 生态系统中得到广泛使用。通常,您只需运行 cargo fmt
即可立即享受格式正确的 Rust 项目。然而,在某些极端情况下,rustfmt
可能无法格式化您的代码。这本身不是什么问题,但当它静默失败时,问题就变得更加严重,因为它没有向用户提供任何关于哪里出错的上下文信息。这就是 rustfmt
中发生的情况,因为许多函数只是返回 Option
而不是 Result
,这使得添加正确的错误报告变得很困难。
SeoYoung 项目的目标是对 rustfmt
进行大规模的内部重构,这将允许跟踪关于重新格式化期间出错的上下文信息。反过来,这将使静默失败转化为适当的错误消息,可以帮助用户检查和调试哪里出错,甚至可以使 rustfmt
在更多情况下重试格式化。
乍一看,这可能听起来像是一项简单的任务,但在 rustfmt
这样的复杂项目中执行如此大规模的重构并非易事。SeoYoung 需要提出一种逐步应用这些重构的方法,以便易于审查并且不会一次影响整个代码库。她引入了一个增强了原始 Rewrite
trait 的新 trait,并修改了现有的实现以使其与之对齐。她还必须处理项目开始之前我们没有预料到的各种极端情况。SeoYoung 以一丝不苟和系统的方式进行操作,并确保没有遗漏任何格式化函数或方法。
最终,重构取得了成功!在内部,rustfmt 现在保留了更多与格式化失败相关的信息,包括以前不可能报告的错误,例如宏格式化的问题。它还能够提供关于源代码跨度的信息,这有助于识别超出最大行宽时需要调整间距的代码部分。我们尚未将额外的失败上下文作为面向用户的错误消息传播,因为这是一个我们没有时间完成的扩展目标,但 SeoYoung 表示有兴趣继续将其作为未来的改进工作!
除了处理错误上下文传播之外,SeoYoung 还进行了其他各种改进,提高了代码库的整体质量,并且她还在帮助其他贡献者理解 rustfmt
。感谢您为所有人打造了更好的格式化基础!
Rust 到 .NET 编译器 - 添加编译和运行 cargo 测试的支持
- 贡献者: Michał Kostrubiec
- 导师: Jack Huey
- 最终报告
正如上面已经提到的,Rust 编译器可以与各种代码生成后端一起使用。其中之一是 .NET 后端,它将 Rust 代码编译为通用中间语言 (CIL),然后可以由 .NET 公共语言运行时 (CLR) 执行。该后端允许 Rust 和 .NET(例如 C#)代码互操作,旨在将这两个生态系统更紧密地结合在一起。
在今年年初,.NET 后端已经能够编译复杂的 Rust 程序,但它仍然缺少某些关键功能。这个 GSoC 项目的目标,由实际上是后端唯一作者的 Michał 实现,是在各个领域扩展此后端的功能。作为目标,他着手扩展后端,以便可以使用 cargo test
命令运行测试。尽管这听起来可能微不足道,但正确编译和运行 Rust 测试框架并非易事,因为它使用了诸如动态 trait 对象、原子操作、panic、展开或多线程之类的复杂功能。这些功能在代码生成后端中尤其难以实现,因为 LLVM 中间表示 (IR) 和 CIL 存在根本差异,并且并非所有 LLVM 内在函数都有 .NET 等效项。
然而,这并没有阻止 Michał。他一直在孜孜不倦地进行这个项目,每天都在实现新功能、修复各种问题和学习更多关于编译器内部结构的信息。他还一直在记录他的旅程,并在 Zulip 上(几乎)每天 更新,这些更新读起来令人着迷。一旦他达到了最初的目标,他就将目标提高到了另一个级别,并尝试使用 .NET 后端运行编译器自己的测试套件。这帮助他发现了更多的极端情况,并导致了整个后端的重构,从而显着提高了性能。
在 GSoC 项目结束时,.NET 后端能够正确编译和运行近 90% 的标准库 core
和 std
测试套件。这是一个令人难以置信的数字,因为该套件包含数千个测试,其中一些测试相当神秘。即使在项目结束后,Michał 的步伐也没有放慢,他仍在不断改进后端。哦,我们是否已经提到他的后端还具有发射 C 代码的实验性支持,有效地充当 C 代码生成后端?!Michał 这个夏天一直很忙。
我们感谢 Michał 在 .NET 后端上的所有工作,因为它确实令人鼓舞,并引发了对其他代码生成后端也相关的问题的富有成效的讨论。Michał 的下一个目标是将他的后端上传,并创建一个官方的 .NET 编译目标,这可能会打开 Rust 成为 .NET 生态系统中一等公民的大门。
使用 WebAssembly 的沙盒化和确定性 proc 宏
- 贡献者: Apurva Mishra
- 导师: David Lattimore
- 最终报告
Rust 过程 (proc) 宏目前作为本机代码运行,这些代码被编译为共享对象,然后直接加载到 Rust 编译器的进程中。由于这种设计,这些宏可以做任何他们想做的事情,例如任意访问文件系统或通过网络进行通信。这不仅具有明显的安全隐患,而且还影响性能,因为这种设计使得缓存 proc 宏调用变得困难。多年来,关于使 proc 宏更封闭的各种讨论,例如将它们编译为 WebAssembly 模块,这些模块可以很容易地在沙箱中执行。这也将打开通过 crates.io 分发 proc 宏的预编译版本的可能性,以加快依赖 proc 宏的 crate 的全新构建速度。
这个项目的目标是研究实现对 proc 宏的 WebAssembly 模块支持需要什么,并创建这个想法的原型。我们知道这将是一个非常雄心勃勃的项目,特别是由于 Apurva 没有为 Rust 编译器做出贡献的经验,并且 proc 宏内部结构非常复杂。尽管如此,还是取得了一些进展。在他的导师 David 的帮助下,Apurva 能够创建一个原型,该原型可以通过共享对象将 WebAssembly 代码加载到编译器中。还完成了一些工作,以利用编译器 proc_macro
crate 中现有的 TokenStream
序列化和反序列化代码。
尽管这个项目没有实现其最初的目标,并且未来还需要更多的工作来实现 WebAssembly 过程宏的功能原型,我们仍然感谢 Apurva 的贡献。WebAssembly 加载原型是一个良好的开端,Apurva 对过程宏内部机制的探索应该可以为未来从事此功能的任何人提供有用的参考。展望未来,我们将尝试为我们的 GSoC 项目描述更多的增量步骤,因为这个项目从一开始可能就过于雄心勃勃了。
Miri 中对 Tokio 异步的支持
- 贡献者:Tiffany Pek Yuan
- 导师:Oli Scherer
- 最终报告
miri 是一个解释器,可以找出 Rust 代码中可能存在的未定义行为实例。它正在整个 Rust 生态系统中使用,但以前不可能在任何非平凡的程序(那些曾经在任何事情上使用 await
的程序)上运行它,这些程序使用了 tokio,原因是一个基本的缺失功能:支持 Linux 上的 epoll
系统调用(以及其他主要平台上类似的 API)。
Tiffany 通过编写纯 libc
代码示例来练习这些 epoll
操作,然后在其本身 miri 中实现这些操作的模拟,从而实现了覆盖大多数 tokio 测试套件所需的基本 epoll
操作。有时,这需要重构核心 miri 组件,例如文件描述符处理,因为它们最初的创建并非考虑到像 epoll
这样的系统调用。
令所有人惊讶的是(尽管可能对 tokio 内部专家来说并不惊讶),一旦这些核心 epoll
操作完成,像异步文件读写这样的操作就开始在 miri 中开箱即用地工作了!由于操作系统提供的非阻塞文件操作的限制,tokio 将这些文件操作包装在专用线程中,而 miri 已经支持这种方式。
一旦 Tiffany 完成了这个项目,包括实现异步文件操作之类的扩展目标,她就开始联系 tokio 维护人员,并与他们合作在 CI 中对大多数 tokio 测试运行 miri。我们有好消息:到目前为止,没有发现任何健全性问题!Tiffany 已经成为 miri 的常规贡献者,专注于继续扩展受支持的文件描述符操作集。我们感谢她所做的所有贡献!
结论
我们很感激能够参与 2024 年的 Google Summer of Code 项目,我们还要向所有贡献者表示感谢!我们期待明年再次加入 GSoC 项目。