Skip to content

Commit

Permalink
feat(wasm-builder): add support for new wasm32v1-none target (#7008)
Browse files Browse the repository at this point in the history
# Description

Resolves #5777

Previously `wasm-builder` used hacks such as `-Zbuild-std` (required
`rust-src` component) and `RUSTC_BOOTSTRAP=1` to build WASM runtime
without WASM features: `sign-ext`, `multivalue` and `reference-types`,
but since Rust 1.84 (will be stable on 9 January, 2025) the situation
has improved as there is new
[`wasm32v1-none`](https://doc.rust-lang.org/beta/rustc/platform-support/wasm32v1-none.html)
target that disables all "post-MVP" WASM features except
`mutable-globals`.

Previously, your `rust-toolchain.toml` looked like this:

```toml
[toolchain]
channel = "stable"
components = ["rust-src"]
targets = ["wasm32-unknown-unknown"]
profile = "default"
```

It should now be updated to something like this:

```toml
[toolchain]
channel = "stable"
targets = ["wasm32v1-none"]
profile = "default"
```

To build the runtime:

```bash
cargo build --package minimal-template-runtime --release
```

## Integration

If you are using Rust 1.84 and above, then install the `wasm32v1-none`
target instead of `wasm32-unknown-unknown` as shown above. You can also
remove the unnecessary `rust-src` component.

Also note the slight differences in conditional compilation:
- `wasm32-unknown-unknown`: `#[cfg(all(target_family = "wasm", target_os
= "unknown"))]`
- `wasm32v1-none`: `#[cfg(all(target_family = "wasm", target_os =
"none"))]`

Avoid using `target_os = "unknown"` in `#[cfg(...)]` or
`#[cfg_attr(...)]` and instead use `target_family = "wasm"` or
`target_arch = "wasm32"` in the runtime code.

## Review Notes

Wasm builder requires the following prerequisites for building the WASM
binary:
- Rust >= 1.68 and Rust < 1.84:
  - `wasm32-unknown-unknown` target
  - `rust-src` component
- Rust >= 1.84:
  - `wasm32v1-none` target
- no more `-Zbuild-std` and `RUSTC_BOOTSTRAP=1` hacks and `rust-src`
component requirements!

---------

Co-authored-by: Bastian Köcher <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
  • Loading branch information
3 people authored Feb 9, 2025
1 parent ea51bbf commit 2970ab1
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 73 deletions.
25 changes: 25 additions & 0 deletions prdoc/pr_7008.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: 'feat(wasm-builder): add support for new `wasm32v1-none` target'
doc:
- audience: Runtime Dev
description: |
Resolves [#5777](https://github.com/paritytech/polkadot-sdk/issues/5777)

Previously `wasm-builder` used hacks such as `-Zbuild-std` (required `rust-src` component) and `RUSTC_BOOTSTRAP=1` to build WASM runtime without WASM features: `sign-ext`, `multivalue` and `reference-types`, but since Rust 1.84 (will be stable on 9 January, 2025) the situation has improved as there is new [`wasm32v1-none`](https://doc.rust-lang.org/beta/rustc/platform-support/wasm32v1-none.html) target that disables all "post-MVP" WASM features except `mutable-globals`.

Wasm builder requires the following prerequisites for building the WASM binary:
- Rust >= 1.68 and Rust < 1.84:
- `wasm32-unknown-unknown` target
- `rust-src` component
- Rust >= 1.84:
- `wasm32v1-none` target
- no more `-Zbuild-std` and `RUSTC_BOOTSTRAP=1` hacks and `rust-src` component requirements!

crates:
- name: substrate-wasm-builder
bump: minor
validate: false
- name: sp-consensus-beefy
bump: patch
14 changes: 10 additions & 4 deletions scripts/generate-umbrella.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ def main(path, version):

# No search for a no_std attribute:
with open(lib_path, "r") as f:
content = f.read()
if "#![no_std]" in content or '#![cfg_attr(not(feature = "std"), no_std)]' in content:
nostd_crate = False
for line in f:
line = line.strip()
if line == "#![no_std]" or line == '#![cfg_attr(not(feature = "std"), no_std)]':
nostd_crate = True
break
elif "no_std" in line:
print(line)

if nostd_crate:
nostd_crates.append((crate, path))
elif 'no_std' in content:
raise Exception(f"Found 'no_std' in {lib_path} without knowing how to handle it")
else:
std_crates.append((crate, path))

Expand Down
8 changes: 4 additions & 4 deletions substrate/utils/wasm-builder/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl WasmBuilder {

/// Enable exporting `__heap_base` as global variable in the WASM binary.
///
/// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`.
/// This adds `-C link-arg=--export=__heap_base` to `RUST_FLAGS`.
pub fn export_heap_base(mut self) -> Self {
self.export_heap_base = true;
self
Expand Down Expand Up @@ -239,7 +239,7 @@ impl WasmBuilder {

if target == RuntimeTarget::Wasm {
if self.export_heap_base {
self.rust_flags.push("-Clink-arg=--export=__heap_base".into());
self.rust_flags.push("-C link-arg=--export=__heap_base".into());
}

if self.import_memory {
Expand All @@ -265,7 +265,7 @@ impl WasmBuilder {
target,
file_path,
self.project_cargo_toml,
self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(),
self.rust_flags.join(" "),
self.features_to_enable,
self.file_name,
!self.disable_runtime_version_section_check,
Expand Down Expand Up @@ -353,7 +353,7 @@ fn build_project(
let cargo_cmd = match crate::prerequisites::check(target) {
Ok(cmd) => cmd,
Err(err_msg) => {
eprintln!("{}", err_msg);
eprintln!("{err_msg}");
process::exit(1);
},
};
Expand Down
99 changes: 70 additions & 29 deletions substrate/utils/wasm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
//!
//! By using environment variables, you can configure which Wasm binaries are built and how:
//!
//! - `SUBSTRATE_RUNTIME_TARGET` - Sets the target for building runtime. Supported values are `wasm`
//! or `riscv` (experimental, do not use it in production!). By default the target is equal to
//! `wasm`.
//! - `SKIP_WASM_BUILD` - Skips building any Wasm binary. This is useful when only native should be
//! recompiled. If this is the first run and there doesn't exist a Wasm binary, this will set both
//! variables to `None`.
Expand All @@ -78,14 +81,15 @@
//! - `WASM_TARGET_DIRECTORY` - Will copy any build Wasm binary to the given directory. The path
//! needs to be absolute.
//! - `WASM_BUILD_TOOLCHAIN` - The toolchain that should be used to build the Wasm binaries. The
//! format needs to be the same as used by cargo, e.g. `nightly-2020-02-20`.
//! format needs to be the same as used by cargo, e.g. `nightly-2024-12-26`.
//! - `WASM_BUILD_WORKSPACE_HINT` - Hint the workspace that is being built. This is normally not
//! required as we walk up from the target directory until we find a `Cargo.toml`. If the target
//! directory is changed for the build, this environment variable can be used to point to the
//! actual workspace.
//! - `WASM_BUILD_STD` - Sets whether the Rust's standard library crates will also be built. This is
//! necessary to make sure the standard library crates only use the exact WASM feature set that
//! our executor supports. Enabled by default.
//! - `WASM_BUILD_STD` - Sets whether the Rust's standard library crates (`core` and `alloc`) will
//! also be built. This is necessary to make sure the standard library crates only use the exact
//! WASM feature set that our executor supports. Enabled by default for RISC-V target and WASM
//! target (but only if Rust < 1.84). Disabled by default for WASM target and Rust >= 1.84.
//! - `WASM_BUILD_CARGO_ARGS` - This can take a string as space separated list of `cargo` arguments.
//! It was added specifically for the use case of enabling JSON diagnostic messages during the
//! build phase, to be used by IDEs that parse them, but it might be useful for other cases too.
Expand All @@ -99,18 +103,21 @@
//! ## Prerequisites:
//!
//! Wasm builder requires the following prerequisites for building the Wasm binary:
//! - Rust >= 1.68 and Rust < 1.84:
//! - `wasm32-unknown-unknown` target
//! - `rust-src` component
//! - Rust >= 1.84:
//! - `wasm32v1-none` target
//!
//! - rust nightly + `wasm32-unknown-unknown` toolchain
//!
//! or
//!
//! - rust stable and version at least 1.68.0 + `wasm32-unknown-unknown` toolchain
//!
//! If a specific rust is installed with `rustup`, it is important that the wasm target is
//! installed as well. For example if installing the rust from 20.02.2020 using `rustup
//! install nightly-2020-02-20`, the wasm target needs to be installed as well `rustup target add
//! wasm32-unknown-unknown --toolchain nightly-2020-02-20`.
//! If a specific Rust is installed with `rustup`, it is important that the WASM
//! target is installed as well. For example if installing the Rust from
//! 26.12.2024 using `rustup install nightly-2024-12-26`, the WASM target
//! (`wasm32-unknown-unknown` or `wasm32v1-none`) needs to be installed as well
//! `rustup target add wasm32-unknown-unknown --toolchain nightly-2024-12-26`.
//! To install the `rust-src` component, use `rustup component add rust-src
//! --toolchain nightly-2024-12-26`.
use prerequisites::DummyCrate;
use std::{
env, fs,
io::BufRead,
Expand Down Expand Up @@ -162,7 +169,7 @@ const FORCE_WASM_BUILD_ENV: &str = "FORCE_WASM_BUILD";
/// Environment variable that hints the workspace we are building.
const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT";

/// Environment variable to set whether we'll build `core`/`std`.
/// Environment variable to set whether we'll build `core`/`alloc`.
const WASM_BUILD_STD: &str = "WASM_BUILD_STD";

/// Environment variable to set additional cargo arguments that might be useful
Expand Down Expand Up @@ -327,6 +334,27 @@ impl CargoCommand {
// Check if major and minor are greater or equal than 1.68 or this is a nightly.
version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly
}

/// Returns whether this version of the toolchain supports the `wasm32v1-none` target.
fn supports_wasm32v1_none_target(&self) -> bool {
self.version.map_or(false, |version| {
// Check if major and minor are greater or equal than 1.84.
version.major > 1 || (version.major == 1 && version.minor >= 84)
})
}

/// Returns whether the `wasm32v1-none` target is installed in this version of the toolchain.
fn is_wasm32v1_none_target_installed(&self) -> bool {
let dummy_crate = DummyCrate::new(self, RuntimeTarget::Wasm, true);
dummy_crate.is_target_installed("wasm32v1-none").unwrap_or(false)
}

/// Returns whether the `wasm32v1-none` target is available in this version of the toolchain.
fn is_wasm32v1_none_target_available(&self) -> bool {
// Check if major and minor are greater or equal than 1.84 and that the `wasm32v1-none`
// target is installed for this toolchain.
self.supports_wasm32v1_none_target() && self.is_wasm32v1_none_target_installed()
}
}

/// Wraps a [`CargoCommand`] and the version of `rustc` the cargo command uses.
Expand Down Expand Up @@ -371,8 +399,7 @@ fn get_bool_environment_variable(name: &str) -> Option<bool> {
Some(false)
} else {
build_helper::warning!(
"the '{}' environment variable has an invalid value; it must be either '1' or '0'",
name
"the '{name}' environment variable has an invalid value; it must be either '1' or '0'",
);
std::process::exit(1);
}
Expand Down Expand Up @@ -404,9 +431,14 @@ impl RuntimeTarget {
}

/// Figures out the target parameter value for rustc.
fn rustc_target(self) -> String {
fn rustc_target(self, cargo_command: &CargoCommand) -> String {
match self {
RuntimeTarget::Wasm => "wasm32-unknown-unknown".to_string(),
RuntimeTarget::Wasm =>
if cargo_command.is_wasm32v1_none_target_available() {
"wasm32v1-none".into()
} else {
"wasm32-unknown-unknown".into()
},
RuntimeTarget::Riscv => {
let path = polkavm_linker::target_json_32_path().expect("riscv not found");
path.into_os_string().into_string().unwrap()
Expand All @@ -415,25 +447,34 @@ impl RuntimeTarget {
}

/// Figures out the target directory name used by cargo.
fn rustc_target_dir(self) -> &'static str {
fn rustc_target_dir(self, cargo_command: &CargoCommand) -> &'static str {
match self {
RuntimeTarget::Wasm => "wasm32-unknown-unknown",
RuntimeTarget::Wasm =>
if cargo_command.is_wasm32v1_none_target_available() {
"wasm32v1-none".into()
} else {
"wasm32-unknown-unknown".into()
},
RuntimeTarget::Riscv => "riscv32emac-unknown-none-polkavm",
}
}

/// Figures out the build-std argument.
fn rustc_target_build_std(self) -> Option<&'static str> {
if !crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(true) {
fn rustc_target_build_std(self, cargo_command: &CargoCommand) -> Option<&'static str> {
if !crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or_else(
|| match self {
RuntimeTarget::Wasm => !cargo_command.is_wasm32v1_none_target_available(),
RuntimeTarget::Riscv => true,
},
) {
return None;
}

// This is a nightly-only flag.
let arg = match self {
RuntimeTarget::Wasm => "build-std",
RuntimeTarget::Riscv => "build-std=core,alloc",
};

Some(arg)
// We only build `core` and `alloc` crates since wasm-builder disables `std` featue for
// runtime. Thus the runtime is `#![no_std]` crate.

Some("build-std=core,alloc")
}
}
Loading

0 comments on commit 2970ab1

Please sign in to comment.