Skip to content

Commit

Permalink
feat(wasm-builder): add support for new wasm32v1-none target
Browse files Browse the repository at this point in the history
  • Loading branch information
StackOverflowExcept1on committed Dec 27, 2024
1 parent ca78179 commit 78c5f4c
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 58 deletions.
22 changes: 22 additions & 0 deletions prdoc/pr_7008.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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` WASM features, 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
2 changes: 1 addition & 1 deletion substrate/primitives/consensus/beefy/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl Payload {
pub fn get_all_raw<'a>(
&'a self,
id: &'a BeefyPayloadId,
) -> impl Iterator<Item = &Vec<u8>> + 'a {
) -> impl Iterator<Item = &'a Vec<u8>> + 'a {
self.0
.iter()
.filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) })
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
79 changes: 53 additions & 26 deletions substrate/utils/wasm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
//!
//! 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 +80,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,17 +102,19 @@
//! ## 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 std::{
env, fs,
Expand Down Expand Up @@ -162,7 +167,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 +332,14 @@ 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)
})
}
}

/// Wraps a [`CargoCommand`] and the version of `rustc` the cargo command uses.
Expand Down Expand Up @@ -404,9 +417,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.supports_wasm32v1_none_target() {
"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 +433,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.supports_wasm32v1_none_target() {
"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.supports_wasm32v1_none_target(),
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")
}
}
78 changes: 57 additions & 21 deletions substrate/utils/wasm-builder/src/prerequisites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,55 @@ impl<'a> DummyCrate<'a> {
fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed");

let manifest_path = project_dir.join("Cargo.toml");
write_file_if_changed(
&manifest_path,
r#"
[package]
name = "dummy-crate"
version = "1.0.0"
edition = "2021"
[workspace]
"#,
);
match target {
RuntimeTarget::Wasm => {
write_file_if_changed(
&manifest_path,
r#"
[package]
name = "dummy-crate"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[workspace]
"#,
);

write_file_if_changed(
project_dir.join("src/lib.rs"),
r#"
#![no_std]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo<'_>) -> ! {
loop {}
}
"#,
);
},
RuntimeTarget::Riscv => {
write_file_if_changed(
&manifest_path,
r#"
[package]
name = "dummy-crate"
version = "1.0.0"
edition = "2021"
[workspace]
"#,
);

write_file_if_changed(
project_dir.join("src/main.rs"),
"#![allow(missing_docs)] fn main() {}",
);
},
}

write_file_if_changed(
project_dir.join("src/main.rs"),
"#![allow(missing_docs)] fn main() {}",
);
DummyCrate { cargo_command, temp, manifest_path, target }
}

Expand All @@ -115,7 +148,7 @@ impl<'a> DummyCrate<'a> {
// by accident - it can happen in some CI environments.
cmd.current_dir(&self.temp);
cmd.arg(subcommand)
.arg(format!("--target={}", self.target.rustc_target()))
.arg(format!("--target={}", self.target.rustc_target(self.cargo_command)))
.args(&["--manifest-path", &self.manifest_path.display().to_string()]);

if super::color_output_enabled() {
Expand Down Expand Up @@ -172,7 +205,10 @@ impl<'a> DummyCrate<'a> {
fn check_wasm_toolchain_installed(
cargo_command: CargoCommand,
) -> Result<CargoCommandVersioned, String> {
let dummy_crate = DummyCrate::new(&cargo_command, RuntimeTarget::Wasm);
let target = RuntimeTarget::Wasm;
let rustc_target = target.rustc_target(&cargo_command);

let dummy_crate = DummyCrate::new(&cargo_command, target);

if let Err(error) = dummy_crate.try_build() {
let toolchain = dummy_crate.get_toolchain().unwrap_or("<unknown>".to_string());
Expand All @@ -181,9 +217,9 @@ fn check_wasm_toolchain_installed(
);
return match error {
None => Err(basic_error_message),
Some(error) if error.contains("the `wasm32-unknown-unknown` target may not be installed") => {
Err(colorize_error_message(&format!("Cannot compile the WASM runtime: the `wasm32-unknown-unknown` target is not installed!\n\
You can install it with `rustup target add wasm32-unknown-unknown --toolchain {toolchain}` if you're using `rustup`.")))
Some(error) if error.contains(&format!("the `{rustc_target}` target may not be installed")) => {
Err(colorize_error_message(&format!("Cannot compile the WASM runtime: the `{rustc_target}` target is not installed!\n\
You can install it with `rustup target add {rustc_target} --toolchain {toolchain}` if you're using `rustup`.")))
},
// Apparently this can happen when we're running on a non Tier 1 platform.
Some(ref error) if error.contains("linker `rust-lld` not found") =>
Expand All @@ -203,7 +239,7 @@ fn check_wasm_toolchain_installed(

let target = RuntimeTarget::new();
assert!(target == RuntimeTarget::Wasm);
if target.rustc_target_build_std().is_some() {
if target.rustc_target_build_std(&cargo_command).is_some() {
if let Some(sysroot) = dummy_crate.get_sysroot() {
let src_path =
Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust");
Expand Down
Loading

0 comments on commit 78c5f4c

Please sign in to comment.