Skip to content

Commit 620a42a

Browse files
committed
fix(wasm64): heal toolchain and bump binaryen to v129
Resolves #1576. Building for wasm64-unknown-unknown failed in two ways introduced by #1553: 1. `rustup target add wasm64-unknown-unknown` failed because wasm64 is a tier-3 target with no prebuilt artifacts. Users had to work around this with `--mode force`. 2. The bundled wasm-opt (binaryen v117) could not parse 64-bit tables; support landed in binaryen v118. The cargo target triple — declared in `.cargo/config.toml`, `CARGO_BUILD_TARGET`, or as an extra cargo argument (`-- --target wasm64-unknown-unknown`) — is the source of truth for what wasm-pack builds. The target triple resolver now follows the same precedence cargo uses (CLI > env > `.cargo/config.toml` walk-up > `$CARGO_HOME/config.toml` > default), so wasm-pack and cargo always agree on the target. For tier-3 wasm targets (currently the `wasm64-*` family) wasm-pack stays out of the cargo invocation — it does not inject `+nightly` or `-Z build-std` (those would override a project's `rust-toolchain.toml` pin or surprise users who hadn't intended a nightly build). Instead it: * verifies the active toolchain is nightly, with a helpful error pointing at `rust-toolchain.toml` and `[unstable] build-std` in `.cargo/config.toml` if it isn't, * heals the `rust-src` component for the active toolchain via rustup if missing, * skips the `rustup target add` attempt (which always fails for tier-3 targets), * passes `--enable-memory64` to wasm-opt so the optimiser accepts 64-bit memories and tables. The bundled binaryen is bumped from `version_117` to `version_129` (latest stable) so wasm-opt accepts 64-bit memories and tables. `--panic-unwind` is untouched. Tests: * New unit test `build::tests::tier3_wasm_detection` covers the triple-classification helper. * New `command::build::tests` unit tests cover the cargo-config walk-up resolution. * New `wasm_opt_prebuilt_url_is_pinned_version` test guards against regressing the binaryen version below v118 (the first release with 64-bit table support). * Existing `all_latest_tool_download_urls_valid` validates the v129 release URLs across all supported architectures.
1 parent 583572d commit 620a42a

10 files changed

Lines changed: 340 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 40 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ ureq = { version = "2.12.1", features = ["json", "socks-proxy"] }
3535
walkdir = "2.5.0"
3636
which = "8.0.0"
3737
path-clean = "1.0.1"
38+
dirs = "6.0.0"
3839

3940
[dev-dependencies]
4041
assert_cmd = "2.1.1"

docs/src/cargo-toml-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The available configuration options and their default values are shown below:
2121
#
2222
# In most cases, the `-O[X]` flag is enough. However, if you require extreme
2323
# optimizations, see the full list of `wasm-opt` optimization flags
24-
# https://github.com/WebAssembly/binaryen/blob/version_117/test/lit/help/wasm-opt.test
24+
# https://github.com/WebAssembly/binaryen/blob/version_129/test/lit/help/wasm-opt.test
2525
wasm-opt = ['-O']
2626

2727
[package.metadata.wasm-pack.profile.dev.wasm-bindgen]

docs/src/commands/build.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,68 @@ See [Non-`rustup` setups][non-rustup].
174174
175175
`--panic-unwind` is also available for [`wasm-pack test`](./test.md).
176176

177+
## 64-bit WebAssembly (`wasm64-unknown-unknown`)
178+
179+
The cargo target triple is the source of truth for which WebAssembly ABI
180+
`wasm-pack` builds. To produce a `memory64` binary, declare the target the
181+
cargo-native way — either in `.cargo/config.toml`:
182+
183+
```toml
184+
# .cargo/config.toml
185+
[build]
186+
target = "wasm64-unknown-unknown"
187+
```
188+
189+
or as an extra cargo argument:
190+
191+
```
192+
wasm-pack build -- --target wasm64-unknown-unknown
193+
```
194+
195+
or via `CARGO_BUILD_TARGET=wasm64-unknown-unknown` in the environment.
196+
197+
`wasm64-unknown-unknown` is a [tier-3 Rust target][tier-3], so `rustup`
198+
has no prebuilt artifacts for it. You need to provide two pieces yourself
199+
via cargo's native config:
200+
201+
1. **A nightly toolchain**`rust-toolchain.toml` is the cargo-native
202+
way to pin one to your project:
203+
204+
```toml
205+
# rust-toolchain.toml
206+
[toolchain]
207+
channel = "nightly"
208+
components = ["rust-src"]
209+
```
210+
211+
Or set `RUSTUP_TOOLCHAIN=nightly` for one-off invocations.
212+
213+
2. **`-Z build-std` to build `std` from source**, since there is no
214+
prebuilt one. Add to your `.cargo/config.toml`:
215+
216+
```toml
217+
[unstable]
218+
build-std = ["std", "panic_abort"]
219+
```
220+
221+
Or pass `-Z build-std=std,panic_abort` as an extra cargo argument.
222+
223+
`wasm-pack` itself stays out of the cargo invocation — it does not inject
224+
`+nightly` or `-Z build-std` (those would override your toolchain pin or
225+
surprise users who hadn't intended a nightly build). What it does do when
226+
it sees a `wasm64-*` triple:
227+
228+
- Verifies the active toolchain is nightly, with a helpful error pointing
229+
at the config above if it isn't.
230+
- Installs the `rust-src` component for the active toolchain via `rustup`
231+
if missing.
232+
- Does **not** attempt `rustup target add wasm64-*` (which would always
233+
fail for a tier-3 target).
234+
- Passes `--enable-memory64` to `wasm-opt` so the optimiser accepts
235+
64-bit memories and tables.
236+
237+
[tier-3]: https://doc.rust-lang.org/nightly/rustc/platform-support.html
238+
177239
[wbg-catch-unwind]: https://wasm-bindgen.github.io/wasm-bindgen/reference/catch-unwind.html
178240
[non-rustup]: ../prerequisites/non-rustup-setups.md
179241

src/build/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ fn wasm_pack_local_version() -> Option<String> {
7474
Some(output.to_string())
7575
}
7676

77+
/// Returns true for tier-3 wasm targets that have no rustup-prebuilt sysroot
78+
/// and must be built via `-Z build-std`. Currently this is the wasm64 family
79+
/// (`wasm64-unknown-unknown`, future `wasm64-*` variants).
80+
pub fn is_tier3_wasm(target_triple: &str) -> bool {
81+
target_triple.starts_with("wasm64")
82+
}
83+
7784
/// Run `cargo build` for Wasm with config derived from the given `BuildProfile`.
7885
pub fn cargo_build_wasm(
7986
path: &Path,
@@ -260,3 +267,18 @@ pub fn cargo_build_wasm_tests(
260267
child::run(cmd, "cargo build").context("Compilation of your program failed")?;
261268
Ok(())
262269
}
270+
271+
#[cfg(test)]
272+
mod tests {
273+
use super::*;
274+
275+
#[test]
276+
fn tier3_wasm_detection() {
277+
assert!(is_tier3_wasm("wasm64-unknown-unknown"));
278+
assert!(is_tier3_wasm("wasm64-wasi"));
279+
assert!(!is_tier3_wasm("wasm32-unknown-unknown"));
280+
assert!(!is_tier3_wasm("wasm32-wasi"));
281+
assert!(!is_tier3_wasm("wasm32-unknown-emscripten"));
282+
assert!(!is_tier3_wasm("x86_64-unknown-linux-gnu"));
283+
}
284+
}

src/build/wasm_target.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ pub fn check_for_wasm_target(target: &str) -> Result<()> {
5656
let msg = format!("{}Checking for the Wasm target...", emoji::TARGET);
5757
PBAR.info(&msg);
5858

59+
// Tier-3 wasm targets (`wasm64-unknown-unknown`) have no rustup-prebuilt
60+
// sysroot — they are built from source via `-Z build-std`, which requires
61+
// a nightly toolchain and the `rust-src` component. wasm-pack doesn't
62+
// inject `+nightly` or `-Z build-std` itself (those would override the
63+
// user's `rust-toolchain.toml` or surprise users who hadn't intended a
64+
// nightly build); instead we verify the active toolchain is nightly,
65+
// heal the `rust-src` component if missing, and let cargo run.
66+
if crate::build::is_tier3_wasm(target) {
67+
return check_tier3_wasm_prerequisites(target);
68+
}
69+
5970
// Check if wasm32 target is present, otherwise bail.
6071
match check_target(target) {
6172
Ok(ref wasm32_check) if wasm32_check.found => Ok(()),
@@ -64,6 +75,67 @@ pub fn check_for_wasm_target(target: &str) -> Result<()> {
6475
}
6576
}
6677

78+
/// Tier-3 (currently `wasm64-*`) prerequisites: nightly active toolchain +
79+
/// `rust-src` component. Does not inject any cargo flags.
80+
fn check_tier3_wasm_prerequisites(target: &str) -> Result<()> {
81+
if !is_active_toolchain_nightly()? {
82+
bail!(
83+
"`{target}` is a tier-3 Rust target and requires the nightly \
84+
toolchain (rustup has no prebuilt artifacts for it).\n\n\
85+
Pin nightly for this project by adding a `rust-toolchain.toml`:\n\n\
86+
[toolchain]\n\
87+
channel = \"nightly\"\n\
88+
components = [\"rust-src\"]\n\n\
89+
Or set `RUSTUP_TOOLCHAIN=nightly` for a one-off invocation.\n\n\
90+
You also need cargo to build `std` from source. Add to your \
91+
`.cargo/config.toml`:\n\n\
92+
[unstable]\n\
93+
build-std = [\"std\", \"panic_abort\"]\n\n\
94+
Or pass `-Z build-std=std,panic_abort` as an extra cargo argument."
95+
);
96+
}
97+
98+
if !has_rust_src_component_for_active_toolchain()? {
99+
install_rust_src_for_active_toolchain()?;
100+
}
101+
102+
Ok(())
103+
}
104+
105+
/// Returns true if the currently-active rustc resolves to a nightly channel.
106+
fn is_active_toolchain_nightly() -> Result<bool> {
107+
let output = Command::new("rustc").arg("--version").output()?;
108+
if !output.status.success() {
109+
bail!("`rustc --version` failed: {}", output.status);
110+
}
111+
let stdout = String::from_utf8(output.stdout)?;
112+
// `rustc --version` prints e.g. `rustc 1.79.0-nightly (abc123 2024-04-01)`.
113+
Ok(stdout.contains("-nightly") || stdout.contains("-dev"))
114+
}
115+
116+
fn has_rust_src_component_for_active_toolchain() -> Result<bool> {
117+
let output = Command::new("rustup")
118+
.args(["component", "list", "--installed"])
119+
.output()?;
120+
if !output.status.success() {
121+
return Ok(false);
122+
}
123+
let stdout = String::from_utf8(output.stdout)?;
124+
Ok(stdout.lines().any(|line| line.starts_with("rust-src")))
125+
}
126+
127+
fn install_rust_src_for_active_toolchain() -> Result<()> {
128+
let msg = format!(
129+
"{}Installing rust-src component for the active toolchain...",
130+
emoji::TARGET
131+
);
132+
PBAR.info(&msg);
133+
let mut cmd = Command::new("rustup");
134+
cmd.arg("component").arg("add").arg("rust-src");
135+
child::run(cmd, "rustup").context("Adding the rust-src component with rustup")?;
136+
Ok(())
137+
}
138+
67139
/// Get rustc's sysroot as a PathBuf
68140
fn get_rustc_sysroot() -> Result<PathBuf> {
69141
let command = Command::new("rustc")

0 commit comments

Comments
 (0)