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-wasip1
或 wasm32-wasip2
目标。
wasm32-unknown-unknown
目标完全支持 core
和 alloc
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
上的用户不应遇到中断。