wasm32-unknown-unknown

级别:2

wasm32-unknown-unknown 目标是一个 WebAssembly 编译目标,它不从主机导入任何标准库函数。从对主机环境的假设最少这个意义上来说,它是“最小”的 WebAssembly。此目标通常在编译到 Web 或 JavaScript 环境时使用,因为 Web 上可以导入哪些函数没有标准。此目标也可用于创建最小或精简的 WebAssembly 二进制文件。

wasm32-unknown-unknown 目标支持 Rust 标准库,但标准库的许多部分无法正常工作并返回错误。例如,println! 不执行任何操作,std::fs 总是返回错误,而 std::thread::spawn 会发生 panic。没有办法可以覆盖这些。对于更全面支持标准库的 WebAssembly 目标,请参阅 wasm32-wasip1wasm32-wasip2 目标。

wasm32-unknown-unknown 目标完全支持 corealloc crate。它还支持 std crate 中的 HashMap 类型,尽管哈希映射不像在其他平台上那样是随机化的。

此目标的一个现有用户(也请随时编辑和扩展此列表)是 wasm-bindgen 项目,它有助于 Rust 代码与 JavaScript 代码进行互操作。但请注意,并非所有 wasm32-unknown-unknown 的使用都使用 JavaScript 和 Web。

目标维护者

当此目标添加到编译器时,当时并未维护此处特定于平台的文档。这意味着下面的列表并不详尽,并且对此目标有更多感兴趣的参与者。话虽如此,那些有兴趣维护此目标的人是

  • Alex Crichton, https://github.com/alexcrichton

要求

此目标是交叉编译的。该目标本身包含对 std 的支持,但如上所述,许多需要操作系统的功能无法正常工作并会返回错误。

此目标当前在 C/C++ 中没有等效项。此目标没有 C/C++ 工具链。虽然理论上可以进行互操作,但建议改为使用以下之一

  • wasm32-unknown-emscripten - 对于基于 Web 的用例,通常选择 Emscripten 工具链来运行 C/C++。
  • wasm32-wasip1 - wasi-sdk 工具链用于在此目标上编译 C/C++,并且可以与 Rust 代码互操作。到目前为止,WASI 在 Web 上可以工作,没有障碍,但是必须选择或重新实现 WASI API 的实现。

除了 Rust 存储库中的内容之外,此目标没有构建要求。链接二进制文件需要为 wasm-ld 驱动程序启用 LLD。此目标使用 dlmalloc crate 作为默认的全局分配器。

构建目标

可以通过以下方式构建此目标

  • 配置 wasm32-unknown-unknown 目标以进行构建。
  • 配置要构建的 LLD。
  • 确保 LLVM 中未禁用 WebAssembly 目标后端。

这些都通过 config.toml 选项进行控制。应该可以在任何平台上构建此目标。

构建 Rust 程序

可以通过 rustup 添加此目标来编译 Rust 程序

$ rustup target add wasm32-unknown-unknown

然后使用目标进行编译

$ rustc foo.rs --target wasm32-unknown-unknown
$ file foo.wasm

交叉编译

此目标可以从任何主机进行交叉编译。

测试

此目标未在 rust-lang/rust 存储库的 CI 中进行测试。必须禁用许多测试才能在此目标上运行,并且由于 println! 在标准库中不起作用,因此失败并不明显。建议改为测试 wasm32-wasip1 目标以获得 WebAssembly 兼容性。

条件编译代码

建议使用以下方式有条件地为此目标编译代码

#[cfg(all(target_family = "wasm", target_os = "unknown"))]

请注意,无法通过 #[cfg] 判断代码是否将在 Web 上运行。

已启用的 WebAssembly 功能

WebAssembly 是一个不断发展的标准,随着时间的推移会添加新功能,例如新指令。此目标的默认受支持 WebAssembly 功能集也会随着时间的推移而变化。wasm32-unknown-unknown 目标继承了 LLVM 的默认设置,该设置通常也与 Emscripten 的默认设置匹配。

WebAssembly 的更改会经过一个 提案流程,但达到最终阶段(第 5 阶段)并不意味着该功能会自动在 LLVM 和 Rust 中默认启用。目前的一般指导原则是,功能必须在大多数引擎中存在“很长一段时间”后才能在 LLVM 中默认启用。目前没有确切的月份数或引擎数要求才能默认启用功能。

在撰写本文时,默认启用的提案(LLVM 术语中的 generic CPU)是

  • multivalue
  • mutable-globals
  • reference-types
  • sign-ext

如果要为不支持 LLVM 默认功能集中的功能的引擎编译 WebAssembly 代码,则必须在编译时禁用该功能。有两种方法可供选择

  • 如果你的目标功能集不小于 W3C WebAssembly Core 1.0 建议 - 这相当于 WebAssembly MVP 加上 mutable-globals 功能 - 并且你正在构建 no_std,那么你可以简单地使用 wasm32v1-none 目标 而不是 wasm32-unknown-unknown,它仅使用这些最小的功能,并且包括一个仅使用这些最小功能构建的核心和 alloc 库。

  • 否则 - 如果你需要 std,或者如果你需要以超最小的 “MVP” 功能集为目标,排除 mutable-globals - 你将需要手动指定 -Ctarget-cpu=mvp,并使用该目标重建 stdlib,以确保 stdlib 中不使用任何功能。这反过来又需要使用 Nightly 编译器。

为 WebAssembly 的初始版本编译所有代码如下所示

$ export RUSTFLAGS=-Ctarget-cpu=mvp
$ cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown

这里的 mvp “cpu” 是 LLVM 中的一个占位符,用于默认禁用所有受支持的功能。然后,Cargo 的 -Zbuild-std 功能(Nightly Rust 功能)用于重新编译标准库以及你自己的代码。这将生成一个默认情况下仅使用原始 WebAssembly 功能且自其创建以来没有提案的二进制文件。

要在该目标或 wasm32v1-none 上启用单个功能,请传递 -Ctarget-feature=+foo 形式的参数。Rust 代码本身可用的功能记录在 参考文档 中,也可以通过以下方式找到

$ rustc -Ctarget-feature=help --target wasm32-unknown-unknown

你需要查阅你的 WebAssembly 引擎的文档,以了解有关该引擎支持的 WebAssembly 功能的更多信息。

请注意,Rust crate 和库仍然可以在每个函数级别启用 WebAssembly 功能。这意味着上面的构建命令可能不足以禁用所有 WebAssembly 功能。例如,如果最终二进制文件仍然有 SIMD 指令,则需要找到有问题的函数,并且有问题的 crate 可能包含类似于

#[target_feature(enable = "simd128")]
fn foo() {
    // ...
}

在这种情况下,没有编译器标志可以禁用 SIMD 指令的发射,并且必须修改 crate 以在编译时默认或通过 Cargo 功能不包含此函数。对于 crate 作者,建议避免 #[target_feature(enable = "...")],除非必要,否则应使用

#[cfg(target_feature = "simd128")]
fn foo() {
    // ...
}

也就是说,建议有条件地编译代码,而不是启用目标功能。这与 x86_64 等本机平台的工作方式明显不同,这是因为 WebAssembly 二进制文件必须仅包含引擎可以理解的代码。只要 CPU 在运行时不动态执行未知代码,本机二进制文件就可以工作。

损坏的 extern "C" ABI

此目标目前具有被认为是损坏的 extern "C" ABI 实现。值得注意的是,Rust 和 C 中的相同签名将编译为不同的 WebAssembly 函数并且不兼容。这被认为是错误,它将在 Rust 的未来版本中修复。

例如,以下 Rust 代码

#[repr(C)]
struct MyPair {
    a: u32,
    b: u32,
}

extern "C" {
    fn take_my_pair(pair: MyPair) -> u32;
}

#[no_mangle]
pub unsafe extern "C" fn call_c() -> u32 {
    take_my_pair(MyPair { a: 1, b: 2 })
}

编译为如下所示的 WebAssembly 模块

(module
  (import "env" "take_my_pair" (func $take_my_pair (param i32 i32) (result i32)))
  (func $call_c
    i32.const 1
    i32.const 2
    call $take_my_pair
  )
)

但是,当在 C 中定义时,该函数如下所示

struct my_pair {
    unsigned a;
    unsigned b;
};

unsigned take_my_pair(struct my_pair pair) {
    return pair.a + pair.b;
}
(module
  (import "env" "__linear_memory" (memory 0))
  (func $take_my_pair (param i32) (result i32)
    local.get 0
    i32.load offset=4
    local.get 0
    i32.load
    i32.add
  )
)

请注意,Rust 认为 take_my_pair 采用两个 i32 参数,但 C 认为它只采用一个参数。

WebAssembly 的 extern "C" ABI 的正确定义位于 WebAssembly/tool-conventions 存储库中。wasm32-unknown-unknown 目标(并且只有此目标,而不是其他 WebAssembly 目标 Rust 支持)没有正确遵循此文档。

有关此错误的 Rust 存储库中的示例问题包括

wasm32-unknown-unknown 后端的当前状态是由于一个不幸的意外而造成的,该意外被依赖。0.2.89 之前的 wasm-bindgen 项目与 extern "C" 的“正确”定义不兼容,并且历史上被认为不值得为修复编译器中的此问题而破坏 wasm-bindgen 的权衡。

然而,由于许多相关人员的英勇努力,wasm-bindgen 0.2.89 及更高版本与 extern "C" 的正确定义兼容,并且 Nightly 编译器当前支持在 #117919 中实现的 -Zwasm-c-abi。这个仅限 Nightly 的标志可以用来指示是否应使用规范定义的 extern "C" 版本,而不是 Rust 目标最初实现的任何“旧”版本。例如,使用上面的代码,你可以看到(为清楚起见进行了少量编辑)

$ rustc +nightly -Zwasm-c-abi=spec foo.rs --target wasm32-unknown-unknown --crate-type lib --emit obj -O
$ wasm-tools print foo.o
(module
  (import "env" "take_my_pair" (func $take_my_pair (param i32) (result i32)))
  (func $call_c (result i32)
    ;; ...
  )
  ;; ...
)

这表明,现在 C 和 Rust 对同一函数的定义达成了一致,就像它们应该做的那样。

-Zwasm-c-abi 编译器标志在 #122532 中进行跟踪,并在 #117918 中实现了一个 lint,以帮助警告使用 wasm-bindgen 0.2.88 或更早版本的用户关于过渡。目前的计划是,将来将 -Zwasm-c-api=spec 设置为默认值。在那之后的一段时间,将删除 -Zwasm-c-abi 标志和 “旧” 实现。在此过程中,在足够更新版本的 wasm-bindgen 上的用户不应遇到中断。