WebAssembly 目标:你需要了解的默认目标特性变更
Rust 编译器近期升级至 LLVM 19,这一变更伴随着对 Rust 编译器 WebAssembly 目标默认启用的目标特性集的一些更新。当前的 Beta 版 Rust(将于 2024 年 10 月 17 日成为 Rust 1.82)反映了所有这些变更,可用于测试。
WebAssembly 是一个不断演进的标准,通过提案过程逐步添加扩展。WebAssembly 提案成熟后,会被合并到规范本身,在引擎中实现,并在生产者工具链(如 LLVM)更新以默认启用这些足够成熟的提案之前保持一段时间。在 LLVM 19 中,多值和引用类型提案(对应 LLVM/Rust 目标特性 multivalue
和 reference-types
)已默认启用。这意味着这些特性现在在 LLVM 中默认启用,并间接意味着在 Rust 中也默认启用。
Rust 的 WebAssembly 目标现在改进了关于 WebAssembly 提案及其对应目标特性的文档。本文将回顾这些变更,并深入探讨 LLVM 中的变化。
WebAssembly 提案和编译器目标特性
WebAssembly 提案是 WebAssembly 标准本身随时间演进的正式手段。大多数提案需要某种形式的工具链集成,例如 LLVM 或 Rust 编译器中的新标志。目前使用 -Ctarget-feature=...
机制来实现这一点。这是向 LLVM 和 Rust 编译器发出的信号,表明哪些 WebAssembly 提案被启用或禁用。
提案名称(通常是提案的 GitHub 仓库名称)与 LLVM/Rust 使用的特性名称之间存在松散耦合。例如,有多值提案,但特性名称为 multivalue
。
在 Rust/LLVM 中实现特性的生命周期通常如下:
- 在新的仓库中创建新的 WebAssembly 提案,例如 WebAssembly/foo。
- 最终 Rust/LLVM 在
-Ctarget-feature=+foo
下实现该提案。 - 上游提案被合并到规范中,WebAssembly/foo 成为归档仓库。
- Rust/LLVM 默认启用
-Ctarget-feature=+foo
特性,但通常保留禁用它的能力。
Rust 中的 reference-types
和 multivalue
目标特性现在处于第(4)步,本文解释了这样做的后果。
默认启用引用类型
WebAssembly 的引用类型提案向 WebAssembly 引入了一些新概念,特别是 externref
类型,这是一种主机定义的 GC 资源,WebAssembly 无法访问但可以传递。Rust 不支持 WebAssembly 的 externref
类型,LLVM 19 也没有改变这一点。从 Rust 生成的 WebAssembly 模块将继续不使用 externref
类型,也没有能力这样做。
这可能在未来启用(例如假设的 core::arch::wasm32::Externref
类型或类似物),但很可能仅以选择加入的方式进行,并且默认不会影响现有代码。
然而,引用类型提案还包括在单个模块中拥有多个 WebAssembly 表的能力。在 WebAssembly 规范的原始版本中,只允许一个表,这一限制在引用类型提案中得到了放宽。WebAssembly 表被 LLVM 和 Rust 用于实现间接函数调用。例如,WebAssembly 中的函数指针实际上是表索引,间接函数调用是带有此表索引的 WebAssembly call_indirect
指令。
随着引用类型提案,call_indirect
指令的二进制编码被更新。在引用类型提案之前,call_indirect
在其指令中编码有一个固定的零字节(必须恰好是 0x00)。这个固定的零字节被放宽为一个 32 位 LEB,以指示 call_indirect
指令使用的是哪个表。
对于那些不熟悉 LEB 的人,LEB 是一种以较少字节编码多字节整数的方法,适用于较小的整数。例如,32 位整数 0 可以用 LEB 编码为 0x00。LEB 灵活地允许“过长”编码,因此整数 0 还可以编码为 0x80 0x00。
LLVM 对源代码到 WebAssembly 二进制文件的单独编译支持意味着,当发出对象文件时,它不知道最终二进制文件中将使用的表的最终索引。在引用类型之前,只有一个选项,表 0,因此在编码 call_indirect
指令时总是使用 0x00。
然而,在引用类型之后,LLVM 将发出形式为 0x80 0x80 0x80 0x80 0x00 的过长 LEB,这是 32 位 LEB 的最大长度。然后,链接器用重定位填充此 LEB,以指向最终模块使用的实际表索引。
当所有这些结合在一起时,这意味着在默认启用 reference-types
特性的 LLVM 19 中,任何具有间接函数调用的 WebAssembly 模块(这几乎是 Rust 代码的常见情况)将产生一个无法由不支持引用类型提案的引擎和工具解码的 WebAssembly 二进制文件。
由于引用类型提案的年龄和引擎实现的广泛性,预计此变更的影响较低。然而,鉴于 WebAssembly 引擎的多样性,建议任何 WebAssembly 用户测试 Rust 1.82 beta,并查看生成的模块是否仍在其选择的引擎上运行。
LLVM、Rust 和多个表
一个值得提及的有趣点是,尽管引用类型提案启用了 WebAssembly 模块中的多个表,但目前 LLVM 或 Rust 并未实际利用这一点。发出的 WebAssembly 模块仍然最多有一个函数表。这意味着目前实际上不需要将索引 0 的过长 5 字节编码为 0x80 0x80 0x80 0x80 0x00。
LLVM 的 WebAssembly 链接器 LLD 希望以类似方式处理所有 LEB 重定位,这目前强制了这种 5 字节的零编码。例如,当函数调用另一个函数时,调用指令将目标函数索引编码为 5 字节 LEB,由链接器填充。通常有多个函数,因此 5 字节编码允许编码所有可能的函数索引。
未来 LLVM 可能开始使用多个表。例如,LLVM 可能在未来有一种模式,其中每个函数类型有一个表,而不是单个异构表。这可以使引擎更有效地实现 call_indirect
。然而,目前尚未实现。
对于希望获得最小尺寸 WebAssembly 模块的用户(例如,如果您在 Web 上下文中并通过网络发送字节),建议使用优化工具(如 wasm-opt
)来缩小 LLVM 输出的尺寸。即使在引用类型变更之前,也建议这样做,因为 wasm-opt
通常可以进一步优化 LLVM 的默认输出。当通过 wasm-opt
优化模块时,这些索引 0 的 5 字节编码都会被缩小到单个字节。
默认启用多值
LLVM 19 中默认启用的第二个特性是 multivalue
。WebAssembly 的多值提案使函数能够拥有多个返回值,例如。WebAssembly 指令也被允许拥有多个返回值。该提案是原始 MVP 之后最早合并到 WebAssembly 规范中的提案之一,并已在许多引擎中实现了相当长的时间。
然而,在 LLVM 中默认启用此特性对 Rust 的影响比默认启用引用类型特性更小。即使启用了多值,LLVM 的默认 C ABI 用于 WebAssembly 代码也没有改变。此外,Rust 的 extern "C"
ABI 用于 WebAssembly 也没有改变,并继续匹配 LLVM 的(或努力匹配,与 LLVM 的差异被视为需要修复的错误)。
尽管如此,此变更仍有可能影响 Rust 用户。Rust 一段时间以来在 Nightly 版本中支持 extern "wasm"
ABI,这是一种实验性手段,用于暴露在 Rust 中定义返回多个值的函数的能力(例如使用多值提案)。由于 LLVM 本身的基础设施变更和重构,Rust 的这一特性已被移除,在 Nightly 版本中不再支持。因此,不再有任何可能的方法在 Rust 中编写在 WebAssembly 函数类型级别返回多个值的函数。
总之,预计此变更不会影响任何在野的 Rust 代码,除非您正在使用 extern "wasm"
的 Nightly 特性,在这种情况下,您将被迫放弃对该特性的支持并使用 extern "C"
代替。在 Rust 中支持 WebAssembly 多返回函数是一个比本文所能涵盖的更广泛的话题,但目前这是一个适合有动力的贡献者贡献的领域。
在讨论 ABI 和多值特性时,或许也值得稍微探讨一下 ABI 对 WebAssembly 的意义。当前 extern "C"
ABI 用于 WebAssembly 的定义记录在工具约定仓库中,这也是 Clang 为 C 代码实现的内容。
LLVM 实现了足够的支持来降低到 WebAssembly,以支持所有这些。extern "Rust"
ABI 在 WebAssembly 上不稳定,就像所有 Rust 目标一样,并随时间变化。目前没有关于 WebAssembly 上 extern "Rust"
的参考文档。
extern "C"
ABI(C 代码默认使用的)难以更改,因为通常需要跨不同编译器版本的稳定性。例如,用 LLVM 18 编译的 WebAssembly 代码可能期望与用 LLVM 20 编译的代码一起工作。这意味着更改 ABI 是一项艰巨的任务,需要版本字段、显式标记等,以帮助防止不匹配。
然而,extern "Rust"
ABI 随时间变化。一个很好的例子是,当启用多值特性时,extern "Rust"
ABI 可以被重新定义以使用 WebAssembly 随后支持的多个返回值。这将使大于 64 位的值返回更加高效。实现这一点需要 LLVM 的支持,但目前不存在。
这一切意味着,实际在函数中使用多返回,或多值启用的 WebAssembly 特性,仍然在远景中,尚未实现。首先,LLVM 需要实现完整的降低支持以生成具有多个返回值的 WebAssembly 函数,然后 extern "Rust"
可以在完全支持时更改以使用这一点。在更远的未来,C 代码可能能够更改,但由于其跨版本兼容性故事,这将需要相当长的时间。
启用未来的 WebAssembly 提案
这不是 WebAssembly 提案在 LLVM 中从默认关闭变为默认打开的第一次,也不会是最后一次。例如,LLVM 已经默认启用了符号扩展提案,而 MVP WebAssembly 没有。预计在不久的将来,非陷阱浮点到整数提案很可能默认启用。这些变更目前没有严格的标准(例如 N 个引擎必须实现此特性 M 年),并且可能会发生中断。
如果您使用的 WebAssembly 引擎不支持 Rust 1.82 beta 和 LLVM 19 发出的模块,那么您的选项是:
- 尝试查看您使用的引擎是否有任何可用的更新。您可能正在使用不支持某个特性的旧版本,而新版本支持该特性。
- 打开一个问题以提高意识,表明变更导致中断。这可以在您的引擎仓库、Rust 仓库或 WebAssembly 工具约定仓库中完成。但建议首先搜索以确认没有已经开放的问题。
- 禁用特性重新编译您的代码,更多内容见下一节。
默认启用新特性的总体假设是,对最终用户来说这是一个相对无忧的操作,同时为每个人带来性能好处(例如,非陷阱浮点到整数将使浮点到整数转换更优化)。如果更新最终导致麻烦,最好尽早标记,以便在需要时调整推出计划。
禁用默认启用的 WebAssembly 提案
出于各种原因,您可能希望禁用默认启用的 WebAssembly 特性:例如,您的引擎难以更新或不支持新特性。不幸的是,禁用默认启用的特性不是最简单的任务。值得注意的是,使用 -Ctarget-features=-sign-ext
仅禁用您自己项目编译的特性是不够的,因为以预编译形式发布的 Rust 标准库仍然使用启用的特性编译。
要禁用默认启用的 WebAssembly 提案,需要使用 Cargo 的 -Zbuild-std
特性。例如:
|
|
这将重新编译 Rust 标准库以及您自己的代码,使用“MVP CPU”,这是 LLVM 的占位符,用于所有禁用的 WebAssembly 提案。这将禁用 sign-ext
、reference-types
、multi-value
等。
Alex Crichton 代表编译器团队
Also published here
Photo by engin akyurt on Unsplash
L O A D I N G. . . comments & more!
关于作者
Rust(技术文档)@Rust 订阅 Rust 是一种开源编程语言,被小型和大型公司(如 Amazon、Google 和 Microsoft)使用。 阅读我的故事 关于 @Rust
主题
编程 #rust #rustlang #webassembly #reference-types #llvm #abi-stability #webassembly-proposals #rust-changes
本文特色于…
Arweave Terminal Lite Hackernoon Bsky
相关故事
NymVPN:从私有到真正私有的升级!访问 Nym #Sponsored 不当存储的会话 Cookie - crates.io 团队正在采取的修复措施 by Rust May 03, 2025 #rust C++ Hello World 和无心的可爱彩虹 by corentin.jabot Jan 16, 2018 #programming C++ Hello World 和玫瑰金色的厄运围墙花园 by corentin.jabot Jan 19, 2018 #programming 介绍 YALT:因为有些人不喜欢 LLVM。 by isacc-barker May 09, 2020 #programming 我正在构建一种名为 Lumen 的新编程语言 - 原因和方法 by makalin May 01, 2025 #programming
加入 HackerNoon 最新技术趋势。定制体验。策划故事。发布您的想法 趋势话题 blockchain cryptocurrency hackernoon-top-story programming software-development technology startup hackernoon-books Bitcoin books 登录 注册 经典 霓虹 Noir 薄荷 报纸 HN 初创公司