diff --git a/Cargo.lock b/Cargo.lock index 7eaf3e44a1b6..5eab3cf7c24f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13805,6 +13805,28 @@ dependencies = [ "westend-runtime", ] +[[package]] +name = "polkavm" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" +dependencies = [ + "libc", + "log", + "polkavm-assembler", + "polkavm-common 0.9.0", + "polkavm-linux-raw", +] + +[[package]] +name = "polkavm-assembler" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa96d6d868243acc12de813dd48e756cbadcc8e13964c70d272753266deadc1" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.5.0" @@ -13813,9 +13835,12 @@ checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" [[package]] name = "polkavm-common" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +dependencies = [ + "log", +] [[package]] name = "polkavm-derive" @@ -13829,9 +13854,9 @@ dependencies = [ [[package]] name = "polkavm-derive" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ "polkavm-derive-impl-macro", ] @@ -13850,11 +13875,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ - "polkavm-common 0.8.0", + "polkavm-common 0.9.0", "proc-macro2", "quote", "syn 2.0.50", @@ -13862,11 +13887,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl 0.8.0", + "polkavm-derive-impl 0.9.0", "syn 2.0.50", ] @@ -13887,19 +13912,25 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.8.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdec1451cb18261d5d01de82acc15305e417fb59588cdcb3127d3dcc9672b925" +checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ "gimli 0.28.0", "hashbrown 0.14.3", "log", "object 0.32.2", - "polkavm-common 0.8.0", + "polkavm-common 0.9.0", "regalloc2 0.9.3", "rustc-demangle", ] +[[package]] +name = "polkavm-linux-raw" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" + [[package]] name = "polling" version = "2.8.0" @@ -16112,6 +16143,7 @@ dependencies = [ "paste", "regex", "sc-executor-common", + "sc-executor-polkavm", "sc-executor-wasmtime", "sc-runtime-test", "sc-tracing", @@ -16141,6 +16173,7 @@ dependencies = [ name = "sc-executor-common" version = "0.29.0" dependencies = [ + "polkavm", "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface 20.0.0", @@ -16148,6 +16181,16 @@ dependencies = [ "wasm-instrument", ] +[[package]] +name = "sc-executor-polkavm" +version = "0.29.0" +dependencies = [ + "log", + "polkavm", + "sc-executor-common", + "sp-wasm-interface 20.0.0", +] + [[package]] name = "sc-executor-wasmtime" version = "0.29.0" @@ -18781,6 +18824,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", + "polkavm-derive 0.9.1", "rustversion", "secp256k1", "sp-core", @@ -18973,7 +19017,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.8.0", + "polkavm-derive 0.9.1", "primitive-types", "rustversion", "sp-core", @@ -19995,7 +20039,7 @@ dependencies = [ "console", "filetime", "parity-wasm", - "polkavm-linker 0.8.2", + "polkavm-linker 0.9.2", "sp-maybe-compressed-blob", "strum 0.24.1", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 800685966858..f256d02808a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -249,6 +249,7 @@ members = [ "substrate/client/db", "substrate/client/executor", "substrate/client/executor/common", + "substrate/client/executor/polkavm", "substrate/client/executor/runtime-test", "substrate/client/executor/wasmtime", "substrate/client/informant", @@ -543,8 +544,9 @@ extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic [workspace.dependencies] -polkavm-linker = "0.8.2" -polkavm-derive = "0.8.0" +polkavm = "0.9.3" +polkavm-linker = "0.9.2" +polkavm-derive = "0.9.1" log = { version = "0.4.20", default-features = false } quote = { version = "1.0.33" } serde = { version = "1.0.197", default-features = false } diff --git a/polkadot/node/core/pvf/common/src/executor_interface.rs b/polkadot/node/core/pvf/common/src/executor_interface.rs index b69a87890f60..252e611db8a4 100644 --- a/polkadot/node/core/pvf/common/src/executor_interface.rs +++ b/polkadot/node/core/pvf/common/src/executor_interface.rs @@ -24,7 +24,7 @@ use polkadot_primitives::{ use sc_executor_common::{ error::WasmError, runtime_blob::RuntimeBlob, - wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmModule as _}, + wasm_runtime::{HeapAllocStrategy, WasmModule as _}, }; use sc_executor_wasmtime::{Config, DeterministicStackLimit, Semantics, WasmtimeRuntime}; use sp_core::storage::{ChildInfo, TrackedStorageKey}; @@ -119,7 +119,7 @@ pub unsafe fn execute_artifact( match sc_executor::with_externalities_safe(&mut ext, || { let runtime = create_runtime_from_artifact_bytes(compiled_artifact_blob, executor_params)?; - runtime.new_instance()?.call(InvokeMethod::Export("validate_block"), params) + runtime.new_instance()?.call("validate_block", params) }) { Ok(Ok(ok)) => Ok(ok), Ok(Err(err)) | Err(err) => Err(err), diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index 4e36de3c4f47..7fad7e3a2a0e 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -23,6 +23,7 @@ tracing = "0.1.29" codec = { package = "parity-scale-codec", version = "3.6.1" } sc-executor-common = { path = "common" } +sc-executor-polkavm = { path = "polkavm" } sc-executor-wasmtime = { path = "wasmtime" } sp-api = { path = "../../primitives/api" } sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index bfd1fa6b7401..8ff34c3709a5 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -22,6 +22,7 @@ wasm-instrument = "0.4" sc-allocator = { path = "../../allocator" } sp-maybe-compressed-blob = { path = "../../../primitives/maybe-compressed-blob" } sp-wasm-interface = { path = "../../../primitives/wasm-interface" } +polkavm = { workspace = true } [features] default = [] diff --git a/substrate/client/executor/common/src/error.rs b/substrate/client/executor/common/src/error.rs index 2a0dc364b410..b7c7e2b70198 100644 --- a/substrate/client/executor/common/src/error.rs +++ b/substrate/client/executor/common/src/error.rs @@ -150,6 +150,24 @@ pub enum WasmError { Other(String), } +impl From for WasmError { + fn from(error: polkavm::ProgramParseError) -> Self { + WasmError::Other(error.to_string()) + } +} + +impl From for WasmError { + fn from(error: polkavm::Error) -> Self { + WasmError::Other(error.to_string()) + } +} + +impl From for Error { + fn from(error: polkavm::Error) -> Self { + Error::Other(error.to_string()) + } +} + /// An error message with an attached backtrace. #[derive(Debug)] pub struct MessageWithBacktrace { diff --git a/substrate/client/executor/common/src/lib.rs b/substrate/client/executor/common/src/lib.rs index 751801fb30da..ff9fab2c8889 100644 --- a/substrate/client/executor/common/src/lib.rs +++ b/substrate/client/executor/common/src/lib.rs @@ -25,3 +25,7 @@ pub mod error; pub mod runtime_blob; pub mod util; pub mod wasm_runtime; + +pub(crate) fn is_polkavm_enabled() -> bool { + std::env::var_os("SUBSTRATE_ENABLE_POLKAVM").map_or(false, |value| value == "1") +} diff --git a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs index becf9e219b0b..d689083b2f85 100644 --- a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -17,23 +17,23 @@ // along with this program. If not, see . use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; -use wasm_instrument::{ - export_mutable_globals, - parity_wasm::elements::{ - deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, - Module, Section, - }, +use wasm_instrument::parity_wasm::elements::{ + deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, + Module, Section, }; -/// A bunch of information collected from a WebAssembly module. +/// A program blob containing a Substrate runtime. #[derive(Clone)] -pub struct RuntimeBlob { - raw_module: Module, +pub struct RuntimeBlob(BlobKind); + +#[derive(Clone)] +enum BlobKind { + WebAssembly(Module), + PolkaVM(polkavm::ProgramBlob<'static>), } impl RuntimeBlob { - /// Create `RuntimeBlob` from the given wasm code. Will attempt to decompress the code before - /// deserializing it. + /// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob. /// /// See [`sp_maybe_compressed_blob`] for details about decompression. pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result { @@ -43,31 +43,26 @@ impl RuntimeBlob { Self::new(&wasm_code) } - /// Create `RuntimeBlob` from the given wasm code. + /// Create `RuntimeBlob` from the given WASM or PolkaVM program blob. /// - /// Returns `Err` if the wasm code cannot be deserialized. - pub fn new(wasm_code: &[u8]) -> Result { - let raw_module: Module = deserialize_buffer(wasm_code) - .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?; - Ok(Self { raw_module }) - } - - /// The number of globals defined in locally in this module. - pub fn declared_globals_count(&self) -> u32 { - self.raw_module - .global_section() - .map(|gs| gs.entries().len() as u32) - .unwrap_or(0) - } - - /// The number of imports of globals. - pub fn imported_globals_count(&self) -> u32 { - self.raw_module.import_section().map(|is| is.globals() as u32).unwrap_or(0) - } + /// Returns `Err` if the blob cannot be deserialized. + /// + /// Will only accept a PolkaVM program if the `SUBSTRATE_ENABLE_POLKAVM` environment + /// variable is set to `1`. + pub fn new(raw_blob: &[u8]) -> Result { + if raw_blob.starts_with(b"PVM\0") { + if crate::is_polkavm_enabled() { + return Ok(Self(BlobKind::PolkaVM( + polkavm::ProgramBlob::parse(raw_blob)?.into_owned(), + ))); + } else { + return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string())); + } + } - /// Perform an instrumentation that makes sure that the mutable globals are exported. - pub fn expose_mutable_globals(&mut self) { - export_mutable_globals(&mut self.raw_module, "exported_internal_global"); + let raw_module: Module = deserialize_buffer(raw_blob) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?; + Ok(Self(BlobKind::WebAssembly(raw_module))) } /// Run a pass that instrument this module so as to introduce a deterministic stack height @@ -80,26 +75,16 @@ impl RuntimeBlob { /// /// The stack cost of a function is computed based on how much locals there are and the maximum /// depth of the wasm operand stack. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result { let injected_module = - wasm_instrument::inject_stack_limiter(self.raw_module, stack_depth_limit).map_err( - |e| WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)), - )?; - - Ok(Self { raw_module: injected_module }) - } + wasm_instrument::inject_stack_limiter(self.into_webassembly_blob()?, stack_depth_limit) + .map_err(|e| { + WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)) + })?; - /// Perform an instrumentation that makes sure that a specific function `entry_point` is - /// exported - pub fn entry_point_exists(&self, entry_point: &str) -> bool { - self.raw_module - .export_section() - .map(|e| { - e.entries().iter().any(|e| { - matches!(e.internal(), Internal::Function(_)) && e.field() == entry_point - }) - }) - .unwrap_or_default() + Ok(Self(BlobKind::WebAssembly(injected_module))) } /// Converts a WASM memory import into a memory section and exports it. @@ -107,8 +92,11 @@ impl RuntimeBlob { /// Does nothing if there's no memory import. /// /// May return an error in case the WASM module is invalid. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> { - let import_section = match self.raw_module.import_section_mut() { + let raw_module = self.as_webassembly_blob_mut()?; + let import_section = match raw_module.import_section_mut() { Some(import_section) => import_section, None => return Ok(()), }; @@ -124,7 +112,7 @@ impl RuntimeBlob { let memory_name = entry.field().to_owned(); import_entries.remove(index); - self.raw_module + raw_module .insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty]))) .map_err(|error| { WasmError::Other(format!( @@ -133,14 +121,14 @@ impl RuntimeBlob { )) })?; - if self.raw_module.export_section_mut().is_none() { + if raw_module.export_section_mut().is_none() { // A module without an export section is somewhat unrealistic, but let's do this // just in case to cover all of our bases. - self.raw_module + raw_module .insert_section(Section::Export(Default::default())) .expect("an export section can be always inserted if it doesn't exist; qed"); } - self.raw_module + raw_module .export_section_mut() .expect("export section already existed or we just added it above, so it always exists; qed") .entries_mut() @@ -156,12 +144,14 @@ impl RuntimeBlob { /// /// Will return an error in case there is no memory section present, /// or if the memory section is empty. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn setup_memory_according_to_heap_alloc_strategy( &mut self, heap_alloc_strategy: HeapAllocStrategy, ) -> Result<(), WasmError> { - let memory_section = self - .raw_module + let raw_module = self.as_webassembly_blob_mut()?; + let memory_section = raw_module .memory_section_mut() .ok_or_else(|| WasmError::Other("no memory section found".into()))?; @@ -187,8 +177,11 @@ impl RuntimeBlob { /// Scans the wasm blob for the first section with the name that matches the given. Returns the /// contents of the custom section if found or `None` otherwise. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> { - self.raw_module + self.as_webassembly_blob() + .ok()? .custom_sections() .find(|cs| cs.name() == section_name) .map(|cs| cs.payload()) @@ -196,11 +189,45 @@ impl RuntimeBlob { /// Consumes this runtime blob and serializes it. pub fn serialize(self) -> Vec { - serialize(self.raw_module).expect("serializing into a vec should succeed; qed") + match self.0 { + BlobKind::WebAssembly(raw_module) => + serialize(raw_module).expect("serializing into a vec should succeed; qed"), + BlobKind::PolkaVM(ref blob) => blob.as_bytes().to_vec(), + } + } + + fn as_webassembly_blob(&self) -> Result<&Module, WasmError> { + match self.0 { + BlobKind::WebAssembly(ref raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } } - /// Destructure this structure into the underlying parity-wasm Module. - pub fn into_inner(self) -> Module { - self.raw_module + fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> { + match self.0 { + BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } + } + + fn into_webassembly_blob(self) -> Result { + match self.0 { + BlobKind::WebAssembly(raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } + } + + /// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program. + pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> { + match self.0 { + BlobKind::WebAssembly(..) => None, + BlobKind::PolkaVM(ref blob) => Some(blob), + } } } diff --git a/substrate/client/executor/common/src/wasm_runtime.rs b/substrate/client/executor/common/src/wasm_runtime.rs index d8e142b9d559..e8f429a3dbb2 100644 --- a/substrate/client/executor/common/src/wasm_runtime.rs +++ b/substrate/client/executor/common/src/wasm_runtime.rs @@ -19,7 +19,6 @@ //! Definitions for a wasm runtime. use crate::error::Error; -use sp_wasm_interface::Value; pub use sc_allocator::AllocationStats; @@ -30,46 +29,6 @@ pub const DEFAULT_HEAP_ALLOC_STRATEGY: HeapAllocStrategy = /// Default heap allocation pages. pub const DEFAULT_HEAP_ALLOC_PAGES: u32 = 2048; -/// A method to be used to find the entrypoint when calling into the runtime -/// -/// Contains variants on how to resolve wasm function that will be invoked. -pub enum InvokeMethod<'a> { - /// Call function exported with this name. - /// - /// Located function should have (u32, u32) -> u64 signature. - Export(&'a str), - /// Call a function found in the exported table found under the given index. - /// - /// Located function should have (u32, u32) -> u64 signature. - Table(u32), - /// Call function by reference from table through a wrapper. - /// - /// Invoked function (`dispatcher_ref`) function - /// should have (u32, u32, u32) -> u64 signature. - /// - /// `func` will be passed to the invoked function as a first argument. - TableWithWrapper { - /// Wrapper for the call. - /// - /// Function pointer, index into runtime exported table. - dispatcher_ref: u32, - /// Extra argument for dispatch. - /// - /// Common usage would be to use it as an actual wasm function pointer - /// that should be invoked, but can be used as any extra argument on the - /// callee side. - /// - /// This is typically generated and invoked by the runtime itself. - func: u32, - }, -} - -impl<'a> From<&'a str> for InvokeMethod<'a> { - fn from(val: &'a str) -> InvokeMethod<'a> { - InvokeMethod::Export(val) - } -} - /// A trait that defines an abstract WASM runtime module. /// /// This can be implemented by an execution engine. @@ -87,7 +46,7 @@ pub trait WasmInstance: Send { /// Before execution, instance is reset. /// /// Returns the encoded result on success. - fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error> { + fn call(&mut self, method: &str, data: &[u8]) -> Result, Error> { self.call_with_allocation_stats(method, data).0 } @@ -98,7 +57,7 @@ pub trait WasmInstance: Send { /// Returns the encoded result on success. fn call_with_allocation_stats( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], ) -> (Result, Error>, Option); @@ -110,11 +69,6 @@ pub trait WasmInstance: Send { fn call_export(&mut self, method: &str, data: &[u8]) -> Result, Error> { self.call(method.into(), data) } - - /// Get the value from a global with the given `name`. - /// - /// This method is only suitable for getting immutable globals. - fn get_global_const(&mut self, name: &str) -> Result, Error>; } /// Defines the heap pages allocation strategy the wasm runtime should use. diff --git a/substrate/client/executor/polkavm/Cargo.toml b/substrate/client/executor/polkavm/Cargo.toml new file mode 100644 index 000000000000..9d0eb8ccf0ee --- /dev/null +++ b/substrate/client/executor/polkavm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sc-executor-polkavm" +version = "0.29.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "PolkaVM executor for Substrate" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { workspace = true } +polkavm = { workspace = true } + +sc-executor-common = { path = "../common" } +sp-wasm-interface = { path = "../../../primitives/wasm-interface" } diff --git a/substrate/client/executor/polkavm/README.md b/substrate/client/executor/polkavm/README.md new file mode 100644 index 000000000000..64fc2fa0c284 --- /dev/null +++ b/substrate/client/executor/polkavm/README.md @@ -0,0 +1 @@ +License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/substrate/client/executor/polkavm/src/lib.rs b/substrate/client/executor/polkavm/src/lib.rs new file mode 100644 index 000000000000..1bd72eb33d30 --- /dev/null +++ b/substrate/client/executor/polkavm/src/lib.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use polkavm::{Caller, Reg}; +use sc_executor_common::{ + error::{Error, WasmError}, + wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, +}; +use sp_wasm_interface::{ + Function, FunctionContext, HostFunctions, Pointer, Value, ValueType, WordSize, +}; + +#[repr(transparent)] +pub struct InstancePre(polkavm::InstancePre<()>); + +#[repr(transparent)] +pub struct Instance(polkavm::Instance<()>); + +impl WasmModule for InstancePre { + fn new_instance(&self) -> Result, Error> { + Ok(Box::new(Instance(self.0.instantiate()?))) + } +} + +impl WasmInstance for Instance { + fn call_with_allocation_stats( + &mut self, + name: &str, + raw_data: &[u8], + ) -> (Result, Error>, Option) { + let Some(method_index) = self.0.module().lookup_export(name) else { + return ( + Err(format!("cannot call into the runtime: export not found: '{name}'").into()), + None, + ); + }; + + let Ok(raw_data_length) = u32::try_from(raw_data.len()) else { + return ( + Err(format!("cannot call runtime method '{name}': input payload is too big").into()), + None, + ); + }; + + // TODO: This will leak guest memory; find a better solution. + let mut state_args = polkavm::StateArgs::new(); + + // Make sure the memory is cleared... + state_args.reset_memory(true); + // ...and allocate space for the input payload. + state_args.sbrk(raw_data_length); + + match self.0.update_state(state_args) { + Ok(()) => {}, + Err(polkavm::ExecutionError::Trap(trap)) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {trap}").into()), None); + }, + Err(polkavm::ExecutionError::Error(error)) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {error}").into()), None); + }, + Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + } + + // Grab the address of where the guest's heap starts; that's where we've just allocated + // the memory for the input payload. + let data_pointer = self.0.module().memory_map().heap_base(); + + if let Err(error) = self.0.write_memory(data_pointer, raw_data) { + return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {error}").into()), None); + } + + let mut state = (); + let mut call_args = polkavm::CallArgs::new(&mut state, method_index); + call_args.args_untyped(&[data_pointer, raw_data_length]); + + match self.0.call(Default::default(), call_args) { + Ok(()) => {}, + Err(polkavm::ExecutionError::Trap(trap)) => { + return ( + Err(format!("call into the runtime method '{name}' failed: {trap}").into()), + None, + ); + }, + Err(polkavm::ExecutionError::Error(error)) => { + return ( + Err(format!("call into the runtime method '{name}' failed: {error}").into()), + None, + ); + }, + Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + } + + let result_pointer = self.0.get_reg(Reg::A0); + let result_length = self.0.get_reg(Reg::A1); + let output = match self.0.read_memory_into_vec(result_pointer, result_length) { + Ok(output) => output, + Err(error) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None) + }, + }; + + (Ok(output), None) + } +} + +struct Context<'r, 'a>(&'r mut polkavm::Caller<'a, ()>); + +impl<'r, 'a> FunctionContext for Context<'r, 'a> { + fn read_memory_into( + &self, + address: Pointer, + dest: &mut [u8], + ) -> sp_wasm_interface::Result<()> { + self.0 + .read_memory_into_slice(u32::from(address), dest) + .map_err(|error| error.to_string()) + .map(|_| ()) + } + + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { + self.0.write_memory(u32::from(address), data).map_err(|error| error.to_string()) + } + + fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { + let pointer = self.0.sbrk(0).expect("fetching the current heap pointer never fails"); + + // TODO: This will leak guest memory; find a better solution. + self.0.sbrk(size).ok_or_else(|| String::from("allocation failed"))?; + + Ok(Pointer::new(pointer)) + } + + fn deallocate_memory(&mut self, _ptr: Pointer) -> sp_wasm_interface::Result<()> { + // This is only used by the allocator host function, which is unused under PolkaVM. + unimplemented!("'deallocate_memory' is never used when running under PolkaVM"); + } + + fn register_panic_error_message(&mut self, _message: &str) { + unimplemented!("'register_panic_error_message' is never used when running under PolkaVM"); + } +} + +fn call_host_function( + caller: &mut Caller<()>, + function: &dyn Function, +) -> Result<(), polkavm::Trap> { + let mut args = [Value::I64(0); Reg::ARG_REGS.len()]; + let mut nth_reg = 0; + for (nth_arg, kind) in function.signature().args.iter().enumerate() { + match kind { + ValueType::I32 => { + args[nth_arg] = Value::I32(caller.get_reg(Reg::ARG_REGS[nth_reg]) as i32); + nth_reg += 1; + }, + ValueType::F32 => { + args[nth_arg] = Value::F32(caller.get_reg(Reg::ARG_REGS[nth_reg])); + nth_reg += 1; + }, + ValueType::I64 => { + let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = + Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); + }, + ValueType::F64 => { + let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); + }, + } + } + + log::trace!( + "Calling host function: '{}', args = {:?}", + function.name(), + &args[..function.signature().args.len()] + ); + + let value = match function + .execute(&mut Context(caller), &mut args.into_iter().take(function.signature().args.len())) + { + Ok(value) => value, + Err(error) => { + log::warn!("Call into the host function '{}' failed: {error}", function.name()); + return Err(polkavm::Trap::default()); + }, + }; + + if let Some(value) = value { + match value { + Value::I32(value) => { + caller.set_reg(Reg::A0, value as u32); + }, + Value::F32(value) => { + caller.set_reg(Reg::A0, value); + }, + Value::I64(value) => { + caller.set_reg(Reg::A0, value as u32); + caller.set_reg(Reg::A1, (value >> 32) as u32); + }, + Value::F64(value) => { + caller.set_reg(Reg::A0, value as u32); + caller.set_reg(Reg::A1, (value >> 32) as u32); + }, + } + } + + Ok(()) +} + +pub fn create_runtime(blob: &polkavm::ProgramBlob) -> Result, WasmError> +where + H: HostFunctions, +{ + static ENGINE: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + let engine = ENGINE.get_or_init(|| { + let config = polkavm::Config::from_env()?; + polkavm::Engine::new(&config) + }); + + let engine = match engine { + Ok(ref engine) => engine, + Err(ref error) => { + return Err(WasmError::Other(error.to_string())); + }, + }; + + let module = polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob)?; + let mut linker = polkavm::Linker::new(&engine); + for function in H::host_functions() { + linker.func_new(function.name(), |mut caller| call_host_function(&mut caller, function))?; + } + + let instance_pre = linker.instantiate_pre(&module)?; + Ok(Box::new(InstancePre(instance_pre))) +} diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index 501279a312cc..be8344ba79b7 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -297,6 +297,10 @@ pub fn create_wasm_runtime_with_code( where H: HostFunctions, { + if let Some(blob) = blob.as_polkavm_blob() { + return sc_executor_polkavm::create_runtime::(blob); + } + match wasm_method { WasmExecutionMethod::Compiled { instantiation_strategy } => sc_executor_wasmtime::create_runtime::( diff --git a/substrate/client/executor/wasmtime/src/instance_wrapper.rs b/substrate/client/executor/wasmtime/src/instance_wrapper.rs index 8852532adbca..4f67d1df9c5f 100644 --- a/substrate/client/executor/wasmtime/src/instance_wrapper.rs +++ b/substrate/client/executor/wasmtime/src/instance_wrapper.rs @@ -22,36 +22,12 @@ use std::sync::Arc; use crate::runtime::{InstanceCounter, ReleaseInstanceHandle, Store, StoreData}; -use sc_executor_common::{ - error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}, - wasm_runtime::InvokeMethod, -}; -use sp_wasm_interface::{Pointer, Value, WordSize}; -use wasmtime::{ - AsContext, AsContextMut, Engine, Extern, Instance, InstancePre, Memory, Table, Val, -}; - -/// Invoked entrypoint format. -pub enum EntryPointType { - /// Direct call. - /// - /// Call is made by providing only payload reference and length. - Direct { entrypoint: wasmtime::TypedFunc<(u32, u32), u64> }, - /// Indirect call. - /// - /// Call is made by providing payload reference and length, and extra argument - /// for advanced routing. - Wrapped { - /// The extra argument passed to the runtime. It is typically a wasm function pointer. - func: u32, - dispatcher: wasmtime::TypedFunc<(u32, u32, u32), u64>, - }, -} +use sc_executor_common::error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}; +use sp_wasm_interface::{Pointer, WordSize}; +use wasmtime::{AsContext, AsContextMut, Engine, Instance, InstancePre, Memory}; /// Wasm blob entry point. -pub struct EntryPoint { - call_type: EntryPointType, -} +pub struct EntryPoint(wasmtime::TypedFunc<(u32, u32), u64>); impl EntryPoint { /// Call this entry point. @@ -64,13 +40,7 @@ impl EntryPoint { let data_ptr = u32::from(data_ptr); let data_len = u32::from(data_len); - match self.call_type { - EntryPointType::Direct { ref entrypoint } => - entrypoint.call(&mut *store, (data_ptr, data_len)), - EntryPointType::Wrapped { func, ref dispatcher } => - dispatcher.call(&mut *store, (func, data_ptr, data_len)), - } - .map_err(|trap| { + self.0.call(&mut *store, (data_ptr, data_len)).map_err(|trap| { let host_state = store .data_mut() .host_state @@ -99,18 +69,7 @@ impl EntryPoint { let entrypoint = func .typed::<(u32, u32), u64>(ctx) .map_err(|_| "Invalid signature for direct entry point")?; - Ok(Self { call_type: EntryPointType::Direct { entrypoint } }) - } - - pub fn wrapped( - dispatcher: wasmtime::Func, - func: u32, - ctx: impl AsContext, - ) -> std::result::Result { - let dispatcher = dispatcher - .typed::<(u32, u32, u32), u64>(ctx) - .map_err(|_| "Invalid signature for wrapped entry point")?; - Ok(Self { call_type: EntryPointType::Wrapped { func, dispatcher } }) + Ok(Self(entrypoint)) } } @@ -178,10 +137,8 @@ impl InstanceWrapper { })?; let memory = get_linear_memory(&instance, &mut store)?; - let table = get_table(&instance, &mut store); store.data_mut().memory = Some(memory); - store.data_mut().table = table; Ok(InstanceWrapper { instance, store, _release_instance_handle }) } @@ -190,61 +147,17 @@ impl InstanceWrapper { /// /// An entrypoint must have a signature `(i32, i32) -> i64`, otherwise this function will return /// an error. - pub fn resolve_entrypoint(&mut self, method: InvokeMethod) -> Result { - Ok(match method { - InvokeMethod::Export(method) => { - // Resolve the requested method and verify that it has a proper signature. - let export = - self.instance.get_export(&mut self.store, method).ok_or_else(|| { - Error::from(format!("Exported method {} is not found", method)) - })?; - let func = export - .into_func() - .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; - EntryPoint::direct(func, &self.store).map_err(|_| { - Error::from(format!("Exported function '{}' has invalid signature.", method)) - })? - }, - InvokeMethod::Table(func_ref) => { - let table = self - .instance - .get_table(&mut self.store, "__indirect_function_table") - .ok_or(Error::NoTable)?; - let val = table - .get(&mut self.store, func_ref) - .ok_or(Error::NoTableEntryWithIndex(func_ref))?; - let func = val - .funcref() - .ok_or(Error::TableElementIsNotAFunction(func_ref))? - .ok_or(Error::FunctionRefIsNull(func_ref))?; - - EntryPoint::direct(*func, &self.store).map_err(|_| { - Error::from(format!( - "Function @{} in exported table has invalid signature for direct call.", - func_ref, - )) - })? - }, - InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { - let table = self - .instance - .get_table(&mut self.store, "__indirect_function_table") - .ok_or(Error::NoTable)?; - let val = table - .get(&mut self.store, dispatcher_ref) - .ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?; - let dispatcher = val - .funcref() - .ok_or(Error::TableElementIsNotAFunction(dispatcher_ref))? - .ok_or(Error::FunctionRefIsNull(dispatcher_ref))?; - - EntryPoint::wrapped(*dispatcher, func, &self.store).map_err(|_| { - Error::from(format!( - "Function @{} in exported table has invalid signature for wrapped call.", - dispatcher_ref, - )) - })? - }, + pub fn resolve_entrypoint(&mut self, method: &str) -> Result { + // Resolve the requested method and verify that it has a proper signature. + let export = self + .instance + .get_export(&mut self.store, method) + .ok_or_else(|| Error::from(format!("Exported method {} is not found", method)))?; + let func = export + .into_func() + .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; + EntryPoint::direct(func, &self.store).map_err(|_| { + Error::from(format!("Exported function '{}' has invalid signature.", method)) }) } @@ -268,24 +181,6 @@ impl InstanceWrapper { Ok(heap_base as u32) } - - /// Get the value from a global with the given `name`. - pub fn get_global_val(&mut self, name: &str) -> Result> { - let global = match self.instance.get_export(&mut self.store, name) { - Some(global) => global, - None => return Ok(None), - }; - - let global = global.into_global().ok_or_else(|| format!("`{}` is not a global", name))?; - - match global.get(&mut self.store) { - Val::I32(val) => Ok(Some(Value::I32(val))), - Val::I64(val) => Ok(Some(Value::I64(val))), - Val::F32(val) => Ok(Some(Value::F32(val))), - Val::F64(val) => Ok(Some(Value::F64(val))), - _ => Err("Unknown value type".into()), - } - } } /// Extract linear memory instance from the given instance. @@ -301,15 +196,6 @@ fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result Option { - instance - .get_export(ctx, "__indirect_function_table") - .as_ref() - .cloned() - .and_then(Extern::into_table) -} - /// Functions related to memory. impl InstanceWrapper { pub(crate) fn store(&self) -> &Store { diff --git a/substrate/client/executor/wasmtime/src/runtime.rs b/substrate/client/executor/wasmtime/src/runtime.rs index ac88663f4e79..595cc503272e 100644 --- a/substrate/client/executor/wasmtime/src/runtime.rs +++ b/substrate/client/executor/wasmtime/src/runtime.rs @@ -30,10 +30,10 @@ use sc_executor_common::{ error::{Error, Result, WasmError}, runtime_blob::RuntimeBlob, util::checked_range, - wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, WordSize}; use std::{ path::{Path, PathBuf}, sync::{ @@ -41,7 +41,7 @@ use std::{ Arc, }, }; -use wasmtime::{AsContext, Engine, Memory, Table}; +use wasmtime::{AsContext, Engine, Memory}; const MAX_INSTANCE_COUNT: u32 = 64; @@ -51,8 +51,6 @@ pub(crate) struct StoreData { pub(crate) host_state: Option, /// This will be always set once the store is initialized. pub(crate) memory: Option, - /// This will be set only if the runtime actually contains a table. - pub(crate) table: Option
, } impl StoreData { @@ -164,7 +162,7 @@ pub struct WasmtimeInstance { impl WasmtimeInstance { fn call_impl( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], allocation_stats: &mut Option, ) -> Result> { @@ -184,20 +182,13 @@ impl WasmtimeInstance { impl WasmInstance for WasmtimeInstance { fn call_with_allocation_stats( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], ) -> (Result>, Option) { let mut allocation_stats = None; let result = self.call_impl(method, data, &mut allocation_stats); (result, allocation_stats) } - - fn get_global_const(&mut self, name: &str) -> Result> { - match &mut self.strategy { - Strategy::RecreateInstance(ref mut instance_creator) => - instance_creator.instantiate()?.get_global_val(name), - } - } } /// Prepare a directory structure and a config file to enable wasmtime caching. diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index c78def9bf442..e47775d56d9e 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -38,6 +38,9 @@ tracing-core = { version = "0.1.32", default-features = false } # Required for backwards compatibility reason, but only used for verifying when `UseDalekExt` is set. ed25519-dalek = { version = "2.1", default-features = false, optional = true } +[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] +polkavm-derive = { workspace = true } + [build-dependencies] rustversion = "1.0.6" diff --git a/substrate/primitives/io/src/global_alloc_riscv.rs b/substrate/primitives/io/src/global_alloc_riscv.rs new file mode 100644 index 000000000000..8d7c69c20945 --- /dev/null +++ b/substrate/primitives/io/src/global_alloc_riscv.rs @@ -0,0 +1,19 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[global_allocator] +static ALLOCATOR: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; diff --git a/substrate/primitives/io/src/global_alloc_wasm.rs b/substrate/primitives/io/src/global_alloc_wasm.rs new file mode 100644 index 000000000000..cf19a6e21a2d --- /dev/null +++ b/substrate/primitives/io/src/global_alloc_wasm.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::alloc::{GlobalAlloc, Layout}; + +/// Allocator used by Substrate from within the runtime. +struct RuntimeAllocator; + +#[global_allocator] +static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; + +unsafe impl GlobalAlloc for RuntimeAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + crate::allocator::malloc(layout.size() as u32) + } + + unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { + crate::allocator::free(ptr) + } +} diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 6d34199416a3..684854ea5c8d 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -129,6 +129,16 @@ use sp_externalities::{Externalities, ExternalitiesExt}; pub use sp_externalities::MultiRemovalResults; +#[cfg(all(not(feature = "disable_allocator"), substrate_runtime, target_family = "wasm"))] +mod global_alloc_wasm; + +#[cfg(all( + not(feature = "disable_allocator"), + substrate_runtime, + any(target_arch = "riscv32", target_arch = "riscv64") +))] +mod global_alloc_riscv; + #[cfg(feature = "std")] const LOG_TARGET: &str = "runtime::io"; @@ -1737,30 +1747,6 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Allocator used by Substrate from within the runtime. -#[cfg(substrate_runtime)] -struct RuntimeAllocator; - -#[cfg(all(not(feature = "disable_allocator"), substrate_runtime))] -#[global_allocator] -static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; - -#[cfg(substrate_runtime)] -mod allocator_impl { - use super::*; - use core::alloc::{GlobalAlloc, Layout}; - - unsafe impl GlobalAlloc for RuntimeAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - allocator::malloc(layout.size() as u32) - } - - unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) { - allocator::free(ptr) - } - } -} - /// Crashes the execution of the program. /// /// Equivalent to the WASM `unreachable` instruction, RISC-V `unimp` instruction, diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 9ffb5c72fd95..13db9fce4310 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -855,7 +855,7 @@ fn build_bloaty_blob( println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); // Use `process::exit(1)` to have a clean error output. - if !matches!(build_cmd.status().map(|s| s.success()), Ok(true)) { + if !build_cmd.status().map_or(false, |s| s.success()) { process::exit(1); }