diff --git a/prdoc/pr_7008.prdoc b/prdoc/pr_7008.prdoc new file mode 100644 index 000000000000..c40b5e3e0ce6 --- /dev/null +++ b/prdoc/pr_7008.prdoc @@ -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 diff --git a/substrate/primitives/consensus/beefy/src/payload.rs b/substrate/primitives/consensus/beefy/src/payload.rs index 9e792670fef5..2bc96548bdff 100644 --- a/substrate/primitives/consensus/beefy/src/payload.rs +++ b/substrate/primitives/consensus/beefy/src/payload.rs @@ -60,7 +60,7 @@ impl Payload { pub fn get_all_raw<'a>( &'a self, id: &'a BeefyPayloadId, - ) -> impl Iterator> + 'a { + ) -> impl Iterator> + 'a { self.0 .iter() .filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) }) diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index 5bdc743eac31..1d4182d62576 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -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 @@ -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 { @@ -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, @@ -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); }, }; diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index ce90f492e08f..df4757e5c4ee 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -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`. @@ -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. @@ -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, @@ -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 @@ -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. @@ -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() @@ -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") } } diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 9abfd1725237..4d4af9ed9054 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -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 } } @@ -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() { @@ -172,7 +205,10 @@ impl<'a> DummyCrate<'a> { fn check_wasm_toolchain_installed( cargo_command: CargoCommand, ) -> Result { - 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("".to_string()); @@ -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") => @@ -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"); diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 6530e4c22fb9..882f8d2b9771 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -846,9 +846,23 @@ fn build_bloaty_blob( let mut rustflags = String::new(); match target { RuntimeTarget::Wasm => { - rustflags.push_str( - "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table ", - ); + // For Rust >= 1.70 and Rust < 1.84 with `wasm32-unknown-unknown` target, + // it's required to disable default WASM features: + // - `sign-ext` (since Rust 1.70) + // - `multivalue` and `reference-types` (since Rust 1.82) + // + // For Rust >= 1.84, we use `wasm32v1-none` target + // (disables all "post-MVP" WASM features except `mutable-globals`): + // - https://doc.rust-lang.org/beta/rustc/platform-support/wasm32v1-none.html + // + // Also see: + // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features.html#disabling-on-by-default-webassembly-proposals + + if !cargo_cmd.supports_wasm32v1_none_target() { + rustflags.push_str("-C target-cpu=mvp "); + } + + rustflags.push_str("-C link-arg=--export-table "); }, RuntimeTarget::Riscv => (), } @@ -859,7 +873,7 @@ fn build_bloaty_blob( build_cmd .arg("rustc") - .arg(format!("--target={}", target.rustc_target())) + .arg(format!("--target={}", target.rustc_target(&cargo_cmd))) .arg(format!("--manifest-path={}", manifest_path.display())) .env("RUSTFLAGS", rustflags) // Manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock (cargo locks a target dir @@ -904,6 +918,15 @@ fn build_bloaty_blob( build_cmd.arg("--offline"); } + // For Rust >= 1.70 and Rust < 1.84 with `wasm32-unknown-unknown` target, + // it's required to disable default WASM features: + // - `sign-ext` (since Rust 1.70) + // - `multivalue` and `reference-types` (since Rust 1.82) + // + // For Rust >= 1.84, we use `wasm32v1-none` target + // (disables all "post-MVP" WASM features except `mutable-globals`): + // - https://doc.rust-lang.org/beta/rustc/platform-support/wasm32v1-none.html + // // Our executor currently only supports the WASM MVP feature set, however nowadays // when compiling WASM the Rust compiler has more features enabled by default. // @@ -914,7 +937,12 @@ fn build_bloaty_blob( // // So here we force the compiler to also compile the standard library crates for us // to make sure that they also only use the MVP features. - if let Some(arg) = target.rustc_target_build_std() { + // + // So the `-Zbuild-std` and `RUSTC_BOOTSTRAP=1` hacks are only used for Rust < 1.84. + // + // Also see: + // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features.html#disabling-on-by-default-webassembly-proposals + if let Some(arg) = target.rustc_target_build_std(&cargo_cmd) { build_cmd.arg("-Z").arg(arg); if !cargo_cmd.supports_nightly_features() { @@ -940,7 +968,7 @@ fn build_bloaty_blob( let blob_name = get_blob_name(target, &manifest_path); let target_directory = project .join("target") - .join(target.rustc_target_dir()) + .join(target.rustc_target_dir(&cargo_cmd)) .join(blob_build_profile.directory()); match target { RuntimeTarget::Riscv => {