diff --git a/Cargo.lock b/Cargo.lock index afdb768c805d4..bdb2926858d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8098,16 +8098,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "sc-allocator" -version = "4.1.0-dev" -dependencies = [ - "log", - "sp-core", - "sp-wasm-interface", - "thiserror", -] - [[package]] name = "sc-authority-discovery" version = "0.10.0-dev" @@ -8722,7 +8712,7 @@ dependencies = [ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ - "sc-allocator", + "sp-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface", "thiserror", @@ -8735,8 +8725,8 @@ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ "log", - "sc-allocator", "sc-executor-common", + "sp-allocator", "sp-runtime-interface", "sp-wasm-interface", "wasmi 0.13.2", @@ -8755,9 +8745,9 @@ dependencies = [ "parity-scale-codec", "paste", "rustix 0.36.8", - "sc-allocator", "sc-executor-common", "sc-runtime-test", + "sp-allocator", "sp-io", "sp-runtime-interface", "sp-wasm-interface", @@ -9928,6 +9918,16 @@ dependencies = [ "sha-1", ] +[[package]] +name = "sp-allocator" +version = "4.1.0-dev" +dependencies = [ + "log", + "parity-scale-codec", + "sp-wasm-interface-common", + "thiserror", +] + [[package]] name = "sp-api" version = "4.0.0-dev" @@ -10227,6 +10227,7 @@ dependencies = [ "secrecy", "serde", "serde_json", + "sp-allocator", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-debug-derive", @@ -10734,11 +10735,21 @@ dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", + "sp-allocator", "sp-std", - "wasmi 0.13.2", + "sp-wasm-interface-common", "wasmtime", ] +[[package]] +name = "sp-wasm-interface-common" +version = "7.0.0" +dependencies = [ + "parity-scale-codec", + "sp-std", + "wasmi 0.13.2", +] + [[package]] name = "sp-weights" version = "4.0.0" diff --git a/Cargo.toml b/Cargo.toml index de562ad79e47e..0aefc73013641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ members = [ "client/network/test", "client/offchain", "client/peerset", - "client/allocator", "client/proposer-metrics", "client/rpc", "client/rpc-api", @@ -172,6 +171,7 @@ members = [ "frame/vesting", "frame/glutton", "frame/whitelist", + "primitives/allocator", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml deleted file mode 100644 index 729decb5ebb3f..0000000000000 --- a/client/allocator/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "sc-allocator" -version = "4.1.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "Collection of allocator implementations." -documentation = "https://docs.rs/sc-allocator" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -log = "0.4.17" -thiserror = "1.0.30" -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index e0a08e80c44bb..c36b2e1b31655 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] thiserror = "1.0.30" wasm-instrument = "0.3" wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] } -sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } +sp-allocator = { version = "4.1.0-dev", path = "../../../primitives/allocator" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index c47164a60655f..124349227ca76 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -67,7 +67,7 @@ pub enum Error { Other(String), #[error(transparent)] - Allocator(#[from] sc_allocator::Error), + Allocator(#[from] sp_allocator::Error), #[error("Host function {0} execution failed with: {1}")] FunctionExecution(String, String), diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index 34967f86595d6..ac6e957ebb042 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -20,14 +20,6 @@ use crate::error::Result; use sp_wasm_interface::Pointer; -use std::ops::Range; - -/// Construct a range from an offset to a data length after the offset. -/// Returns None if the end of the range would exceed some maximum offset. -pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { - let end = offset.checked_add(len)?; - (end <= max).then(|| offset..end) -} /// Provides safe memory access interface using an external buffer pub trait MemoryTransfer { diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index e3db7e52fc7e7..34883f9676173 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -21,7 +21,7 @@ use crate::error::Error; use sp_wasm_interface::Value; -pub use sc_allocator::AllocationStats; +pub use sp_allocator::AllocationStats; /// A method to be used to find the entrypoint when calling into the runtime /// diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 9fa393bb2f730..0b00bb6cead7a 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.17" wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] } -sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } +sp-allocator = { version = "4.1.0-dev", path = "../../../primitives/allocator" } sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index c757ff8afe43d..b253ee1acb385 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -28,19 +28,19 @@ use wasmi::{ TableRef, }; -use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; +use sp_allocator::AllocationStats; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; +use sp_wasm_interface::{Function, FunctionContext, FunctionContextToken, Pointer, Result as WResult, WordSize, Caller, StoreData}; /// Wrapper around [`MemorRef`] that implements [`sc_allocator::Memory`]. struct MemoryWrapper<'a>(&'a MemoryRef); -impl sc_allocator::Memory for MemoryWrapper<'_> { +impl sp_allocator::Memory for MemoryWrapper<'_> { fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { self.0.with_direct_access_mut(run) } @@ -73,7 +73,7 @@ impl sc_allocator::Memory for MemoryWrapper<'_> { } struct FunctionExecutor { - heap: RefCell, + heap: RefCell, memory: MemoryRef, host_functions: Arc>, allow_missing_func_imports: bool, @@ -90,7 +90,7 @@ impl FunctionExecutor { missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - heap: RefCell::new(FreeingBumpHeapAllocator::new(heap_base)), + heap: RefCell::new(sp_allocator::FreeingBumpHeapAllocator::new(heap_base)), memory: m, host_functions, allow_missing_func_imports, @@ -126,6 +126,9 @@ impl FunctionContext for FunctionExecutor { fn register_panic_error_message(&mut self, message: &str) { self.panic_message = Some(message.to_owned()); } + + fn with_caller_mut_impl(&mut self, _: FunctionContextToken, _context: *mut (), _callback: fn(*mut (), &mut Caller)) { + } } /// Will be used on initialization of a module to resolve function and memory imports. diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index bffccd12badfb..03c132b0ec8c4 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -27,8 +27,8 @@ wasmtime = { version = "6.0.1", default-features = false, features = [ "pooling-allocator" ] } anyhow = "1.0.68" -sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } +sp-allocator = { version = "4.1.0-dev", path = "../../../primitives/allocator" } sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 9bd3ca3dade5e..539a1095d6158 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -21,41 +21,10 @@ use wasmtime::Caller; -use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; -use sp_wasm_interface::{Pointer, WordSize}; +use sp_wasm_interface::{FunctionContextToken, Pointer, WordSize, util}; +pub use sp_wasm_interface::HostState; -use crate::{instance_wrapper::MemoryWrapper, runtime::StoreData, util}; - -/// The state required to construct a HostContext context. The context only lasts for one host -/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make -/// many different host calls that must share state. -pub struct HostState { - /// The allocator instance to keep track of allocated memory. - /// - /// This is stored as an `Option` as we need to temporarly set this to `None` when we are - /// allocating/deallocating memory. The problem being that we can only mutable access `caller` - /// once. - allocator: Option, - panic_message: Option, -} - -impl HostState { - /// Constructs a new `HostState`. - pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { allocator: Some(allocator), panic_message: None } - } - - /// Takes the error message out of the host state, leaving a `None` in its place. - pub fn take_panic_message(&mut self) -> Option { - self.panic_message.take() - } - - pub(crate) fn allocation_stats(&self) -> AllocationStats { - self.allocator.as_ref() - .expect("Allocator is always set and only unavailable when doing an allocation/deallocation; qed") - .stats() - } -} +use crate::runtime::StoreData; /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime /// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from @@ -64,15 +33,6 @@ pub(crate) struct HostContext<'a> { pub(crate) caller: Caller<'a, StoreData>, } -impl<'a> HostContext<'a> { - fn host_state_mut(&mut self) -> &mut HostState { - self.caller - .data_mut() - .host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") - } -} - impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn read_memory_into( &self, @@ -87,42 +47,22 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - let memory = self.caller.data().memory(); - let mut allocator = self - .host_state_mut() - .allocator - .take() - .expect("allocator is not empty when calling a function in wasm; qed"); - - // We can not return on error early, as we need to store back allocator. - let res = allocator - .allocate(&mut MemoryWrapper(&memory, &mut self.caller), size) - .map_err(|e| e.to_string()); - - self.host_state_mut().allocator = Some(allocator); - - res + util::allocate_memory(&mut self.caller, size) } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { - let memory = self.caller.data().memory(); - let mut allocator = self - .host_state_mut() - .allocator - .take() - .expect("allocator is not empty when calling a function in wasm; qed"); - - // We can not return on error early, as we need to store back allocator. - let res = allocator - .deallocate(&mut MemoryWrapper(&memory, &mut self.caller), ptr) - .map_err(|e| e.to_string()); - - self.host_state_mut().allocator = Some(allocator); - - res + util::deallocate_memory(&mut self.caller, ptr) } fn register_panic_error_message(&mut self, message: &str) { - self.host_state_mut().panic_message = Some(message.to_owned()); + self.caller + .data_mut() + .host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") + .panic_message = Some(message.to_owned()); } + + fn with_caller_mut_impl(&mut self, _: FunctionContextToken, context: *mut (), callback: fn(*mut (), &mut Caller)) { + callback(context, &mut self.caller) + } } diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 6d319cce509e5..3d0c4aa1db000 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -112,41 +112,6 @@ impl EntryPoint { } } -/// Wrapper around [`Memory`] that implements [`sc_allocator::Memory`]. -pub(crate) struct MemoryWrapper<'a, C>(pub &'a wasmtime::Memory, pub &'a mut C); - -impl sc_allocator::Memory for MemoryWrapper<'_, C> { - fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { - run(self.0.data(&self.1)) - } - - fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { - run(self.0.data_mut(&mut self.1)) - } - - fn grow(&mut self, additional: u32) -> std::result::Result<(), ()> { - self.0 - .grow(&mut self.1, additional as u64) - .map_err(|e| { - log::error!( - target: "wasm-executor", - "Failed to grow memory by {} pages: {}", - additional, - e, - ) - }) - .map(drop) - } - - fn pages(&self) -> u32 { - self.0.size(&self.1) as u32 - } - - fn max_pages(&self) -> Option { - self.0.ty(&self.1).maximum().map(|p| p as _) - } -} - /// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. /// /// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index e01a51f6cf2a7..b1cfa15661b4c 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -20,21 +20,21 @@ use crate::{ host::HostState, - instance_wrapper::{EntryPoint, InstanceWrapper, MemoryWrapper}, + instance_wrapper::{EntryPoint, InstanceWrapper}, util::{self, replace_strategy_if_broken}, }; -use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; +use sp_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::{Result, WasmError}, runtime_blob::{ self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, }, - util::checked_range, wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize, MemoryWrapper, util as memory_util}; +pub use sp_wasm_interface::StoreData; use std::{ path::{Path, PathBuf}, sync::{ @@ -42,29 +42,7 @@ use std::{ Arc, }, }; -use wasmtime::{AsContext, Engine, Memory, Table}; - -#[derive(Default)] -pub(crate) struct StoreData { - /// This will only be set when we call into the runtime. - 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 { - /// Returns a mutable reference to the host state. - pub fn host_state_mut(&mut self) -> Option<&mut HostState> { - self.host_state.as_mut() - } - - /// Returns the host memory. - pub fn memory(&self) -> Memory { - self.memory.expect("memory is always set; qed") - } -} +use wasmtime::{AsContext, Engine}; pub(crate) type Store = wasmtime::Store; @@ -183,7 +161,7 @@ impl WasmtimeInstance { let entrypoint = instance_wrapper.resolve_entrypoint(method)?; data_segments_snapshot.apply(|offset, contents| { - util::write_memory_from( + memory_util::write_memory_from( instance_wrapper.store_mut(), Pointer::new(offset), contents, @@ -757,8 +735,8 @@ fn inject_input_data( let mut ctx = instance.store_mut(); let memory = ctx.data().memory(); let data_len = data.len() as WordSize; - let data_ptr = allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len)?; - util::write_memory_from(instance.store_mut(), data_ptr, data)?; + let data_ptr = allocator.allocate(&mut MemoryWrapper::from((&memory, &mut ctx)), data_len)?; + memory_util::write_memory_from(instance.store_mut(), data_ptr, data)?; Ok((data_ptr, data_len)) } @@ -775,11 +753,11 @@ fn extract_output_data( // // Get the size of the WASM memory in bytes. let memory_size = ctx.as_context().data().memory().data_size(ctx); - if checked_range(output_ptr as usize, output_len as usize, memory_size).is_none() { + if memory_util::checked_range(output_ptr as usize, output_len as usize, memory_size).is_none() { Err(WasmError::Other("output exceeds bounds of wasm memory".into()))? } let mut output = vec![0; output_len as usize]; - util::read_memory_into(ctx, Pointer::new(output_ptr), &mut output)?; + memory_util::read_memory_into(ctx, Pointer::new(output_ptr), &mut output)?; Ok(output) } diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 5c64fc01c13a8..3737cedbc3656 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -16,13 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{runtime::StoreData, InstantiationStrategy}; -use sc_executor_common::{ - error::{Error, Result}, - util::checked_range, -}; -use sp_wasm_interface::{Pointer, Value}; -use wasmtime::{AsContext, AsContextMut}; +use crate::InstantiationStrategy; +use sp_wasm_interface::Value; /// Converts a [`wasmtime::Val`] into a substrate runtime interface [`Value`]. /// @@ -48,39 +43,6 @@ pub fn into_wasmtime_val(value: Value) -> wasmtime::Val { } } -/// Read data from the instance memory into a slice. -/// -/// Returns an error if the read would go out of the memory bounds. -pub(crate) fn read_memory_into( - ctx: impl AsContext, - address: Pointer, - dest: &mut [u8], -) -> Result<()> { - let memory = ctx.as_context().data().memory().data(&ctx); - - let range = checked_range(address.into(), dest.len(), memory.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - dest.copy_from_slice(&memory[range]); - Ok(()) -} - -/// Write data to the instance memory from a slice. -/// -/// Returns an error if the write would go out of the memory bounds. -pub(crate) fn write_memory_from( - mut ctx: impl AsContextMut, - address: Pointer, - data: &[u8], -) -> Result<()> { - let memory = ctx.as_context().data().memory(); - let memory = memory.data_mut(&mut ctx); - - let range = checked_range(address.into(), data.len(), memory.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - memory[range].copy_from_slice(data); - Ok(()) -} - /// Checks whether the `madvise(MADV_DONTNEED)` works as expected. /// /// In certain environments (e.g. when running under the QEMU user-mode emulator) diff --git a/primitives/allocator/Cargo.toml b/primitives/allocator/Cargo.toml new file mode 100644 index 0000000000000..18241f38901bf --- /dev/null +++ b/primitives/allocator/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "sp-allocator" +version = "4.1.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Collection of allocator implementations." +documentation = "https://docs.rs/sc-allocator" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { version = "0.4.17", default-features = false } +thiserror = { version = "1.0.30", optional = true } +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +sp-wasm-interface-common = { version = "7.0.0", path = "../wasm-interface-common", default-features = false } + +[features] +default = [ "std" ] +std = [ "codec/std", "log/std", "sp-wasm-interface-common/std", "thiserror" ] diff --git a/client/allocator/README.md b/primitives/allocator/README.md similarity index 100% rename from client/allocator/README.md rename to primitives/allocator/README.md diff --git a/client/allocator/src/error.rs b/primitives/allocator/src/error.rs similarity index 100% rename from client/allocator/src/error.rs rename to primitives/allocator/src/error.rs diff --git a/client/allocator/src/freeing_bump.rs b/primitives/allocator/src/freeing_bump.rs similarity index 99% rename from client/allocator/src/freeing_bump.rs rename to primitives/allocator/src/freeing_bump.rs index c3cb827afec08..907ef61b91c44 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/primitives/allocator/src/freeing_bump.rs @@ -67,9 +67,8 @@ //! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation //! sizes. -use crate::{Error, Memory, MAX_WASM_PAGES, PAGE_SIZE}; -pub use sp_core::MAX_POSSIBLE_ALLOCATION; -use sp_wasm_interface::{Pointer, WordSize}; +use crate::{Error, Memory, MAX_POSSIBLE_ALLOCATION, MAX_WASM_PAGES, PAGE_SIZE}; +use sp_wasm_interface_common::{Pointer, WordSize}; use std::{ cmp::{max, min}, mem, diff --git a/client/allocator/src/lib.rs b/primitives/allocator/src/lib.rs similarity index 86% rename from client/allocator/src/lib.rs rename to primitives/allocator/src/lib.rs index e50d7d54c8e97..ce0c13867f16a 100644 --- a/client/allocator/src/lib.rs +++ b/primitives/allocator/src/lib.rs @@ -20,12 +20,18 @@ //! This crate provides the following allocator implementations: //! - A freeing-bump allocator: [`FreeingBumpHeapAllocator`](freeing_bump::FreeingBumpHeapAllocator) +#![cfg_attr(not(feature = "std"), no_std)] + #![warn(missing_docs)] +#[cfg(feature = "std")] mod error; +#[cfg(feature = "std")] mod freeing_bump; +#[cfg(feature = "std")] pub use error::Error; +#[cfg(feature = "std")] pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; /// The size of one wasm page in bytes. @@ -59,3 +65,9 @@ pub trait Memory { /// If `None` is returned, there is no maximum (besides the maximum defined in the wasm spec). fn max_pages(&self) -> Option; } + +/// The maximum number of bytes that can be allocated at one time. +// The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for +// everybody. +// 2^25 bytes, 32 MiB +pub const MAX_POSSIBLE_ALLOCATION: u32 = 33_554_432; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 092baeeda95f8..5c7403d5ab125 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -55,6 +55,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = ["static- merlin = { version = "2.0", default-features = false, optional = true } secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } ss58-registry = { version = "1.34.0", default-features = false } +sp-allocator = { version = "4.1.0-dev", path = "../allocator", default-features = false } sp-core-hashing = { version = "5.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } @@ -92,6 +93,7 @@ std = [ "hash256-std-hasher/std", "hash-db/std", "sp-std/std", + "sp-allocator/std", "serde", "blake2/std", "array-bytes", diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index efccd0378e95a..ffb8f9ef797f0 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -388,10 +388,7 @@ macro_rules! impl_maybe_marker { } } -/// The maximum number of bytes that can be allocated at one time. -// The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for -// everybody. -pub const MAX_POSSIBLE_ALLOCATION: u32 = 33554432; // 2^25 bytes, 32 MiB +pub use sp_allocator::MAX_POSSIBLE_ALLOCATION; /// Generates a macro for checking if a certain feature is enabled. /// diff --git a/primitives/wasm-interface-common/Cargo.toml b/primitives/wasm-interface-common/Cargo.toml new file mode 100644 index 0000000000000..bf0f4cc85e5f0 --- /dev/null +++ b/primitives/wasm-interface-common/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sp-wasm-interface-common" +version = "7.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Types and traits for interfacing between the host and the wasm runtime." +documentation = "https://docs.rs/sp-wasm-interface" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", default-features = false, optional = true } +sp-std = { version = "5.0.0", default-features = false, path = "../std" } + +[features] +default = [ "std" ] +std = [ "codec/std", "sp-std/std", "wasmi/std" ] diff --git a/primitives/wasm-interface-common/README.md b/primitives/wasm-interface-common/README.md new file mode 100644 index 0000000000000..7e6c46581ae43 --- /dev/null +++ b/primitives/wasm-interface-common/README.md @@ -0,0 +1,3 @@ +Types and traits for interfacing between the host and the wasm runtime. + +License: Apache-2.0 \ No newline at end of file diff --git a/primitives/wasm-interface-common/src/lib.rs b/primitives/wasm-interface-common/src/lib.rs new file mode 100644 index 0000000000000..6d021a1e3553a --- /dev/null +++ b/primitives/wasm-interface-common/src/lib.rs @@ -0,0 +1,344 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Types and traits for interfacing between the host and the wasm runtime. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::borrow::Cow; +use core::{mem, marker::PhantomData}; + +#[cfg(feature = "wasmi")] +pub use wasmi; + +#[cfg(feature = "wasmi")] +pub mod wasmi_impl; + +/// Value types supported by Substrate on the boundary between host/Wasm. +#[derive(Copy, Clone, PartialEq, Debug, Eq)] +pub enum ValueType { + /// An `i32` value type. + I32, + /// An `i64` value type. + I64, + /// An `f32` value type. + F32, + /// An `f64` value type. + F64, +} + +impl From for u8 { + fn from(val: ValueType) -> u8 { + match val { + ValueType::I32 => 0, + ValueType::I64 => 1, + ValueType::F32 => 2, + ValueType::F64 => 3, + } + } +} + +impl TryFrom for ValueType { + type Error = (); + + fn try_from(val: u8) -> core::result::Result { + match val { + 0 => Ok(Self::I32), + 1 => Ok(Self::I64), + 2 => Ok(Self::F32), + 3 => Ok(Self::F64), + _ => Err(()), + } + } +} + +/// Values supported by Substrate on the boundary between host/Wasm. +#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)] +pub enum Value { + /// A 32-bit integer. + I32(i32), + /// A 64-bit integer. + I64(i64), + /// A 32-bit floating-point number stored as raw bit pattern. + /// + /// You can materialize this value using `f32::from_bits`. + F32(u32), + /// A 64-bit floating-point number stored as raw bit pattern. + /// + /// You can materialize this value using `f64::from_bits`. + F64(u64), +} + +impl Value { + /// Returns the type of this value. + pub fn value_type(&self) -> ValueType { + match self { + Value::I32(_) => ValueType::I32, + Value::I64(_) => ValueType::I64, + Value::F32(_) => ValueType::F32, + Value::F64(_) => ValueType::F64, + } + } + + /// Return `Self` as `i32`. + pub fn as_i32(&self) -> Option { + match self { + Self::I32(val) => Some(*val), + _ => None, + } + } +} + +/// Something that can be converted into a wasm compatible `Value`. +pub trait IntoValue { + /// The type of the value in wasm. + const VALUE_TYPE: ValueType; + + /// Convert `self` into a wasm `Value`. + fn into_value(self) -> Value; +} + +/// Something that can may be created from a wasm `Value`. +pub trait TryFromValue: Sized { + /// Try to convert the given `Value` into `Self`. + fn try_from_value(val: Value) -> Option; +} + +macro_rules! impl_into_and_from_value { + ( + $( + $type:ty, $( < $gen:ident >, )? $value_variant:ident, + )* + ) => { + $( + impl $( <$gen> )? IntoValue for $type { + const VALUE_TYPE: ValueType = ValueType::$value_variant; + fn into_value(self) -> Value { Value::$value_variant(self as _) } + } + + impl $( <$gen> )? TryFromValue for $type { + fn try_from_value(val: Value) -> Option { + match val { + Value::$value_variant(val) => Some(val as _), + _ => None, + } + } + } + )* + } +} + +impl_into_and_from_value! { + u8, I32, + u16, I32, + u32, I32, + u64, I64, + i8, I32, + i16, I32, + i32, I32, + i64, I64, +} + +/// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this +/// crate. +mod private { + pub trait Sealed {} + + impl Sealed for u8 {} + impl Sealed for u16 {} + impl Sealed for u32 {} + impl Sealed for u64 {} + + impl Sealed for i32 {} + impl Sealed for i64 {} +} + +/// Something that can be wrapped in a wasm `Pointer`. +/// +/// This trait is sealed. +pub trait PointerType: Sized + private::Sealed { + /// The size of the type in wasm. + const SIZE: u32 = mem::size_of::() as u32; +} + +impl PointerType for u8 {} +impl PointerType for u16 {} +impl PointerType for u32 {} +impl PointerType for u64 {} + +/// Type to represent a pointer in wasm at the host. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Pointer { + ptr: u32, + _marker: PhantomData, +} + +impl Pointer { + /// Create a new instance of `Self`. + pub fn new(ptr: u32) -> Self { + Self { ptr, _marker: Default::default() } + } + + /// Calculate the offset from this pointer. + /// + /// `offset` is in units of `T`. So, `3` means `3 * mem::size_of::()` as offset to the + /// pointer. + /// + /// Returns an `Option` to respect that the pointer could probably overflow. + pub fn offset(self, offset: u32) -> Option { + offset + .checked_mul(T::SIZE) + .and_then(|o| self.ptr.checked_add(o)) + .map(|ptr| Self { ptr, _marker: Default::default() }) + } + + /// Create a null pointer. + pub fn null() -> Self { + Self::new(0) + } + + /// Cast this pointer of type `T` to a pointer of type `R`. + pub fn cast(self) -> Pointer { + Pointer::new(self.ptr) + } +} + +impl From for Pointer { + fn from(ptr: u32) -> Self { + Pointer::new(ptr) + } +} + +impl From> for u32 { + fn from(ptr: Pointer) -> Self { + ptr.ptr + } +} + +impl From> for u64 { + fn from(ptr: Pointer) -> Self { + u64::from(ptr.ptr) + } +} + +impl From> for usize { + fn from(ptr: Pointer) -> Self { + ptr.ptr as _ + } +} + +// trait implementations for Pointer. +impl IntoValue for Pointer { + const VALUE_TYPE: ValueType = ValueType::I32; + fn into_value(self) -> Value { + Value::I32(self.ptr as _) + } +} + +impl TryFromValue for Pointer { + fn try_from_value(val: Value) -> Option { + match val { + Value::I32(val) => Some(Self::new(val as _)), + _ => None, + } + } +} + +/// The Signature of a function +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct Signature { + /// The arguments of a function. + pub args: Cow<'static, [ValueType]>, + /// The optional return value of a function. + pub return_value: Option, +} + +impl Signature { + /// Create a new instance of `Signature`. + pub fn new>>( + args: T, + return_value: Option, + ) -> Self { + Self { args: args.into(), return_value } + } + + /// Create a new instance of `Signature` with the given `args` and without any return value. + pub fn new_with_args>>(args: T) -> Self { + Self { args: args.into(), return_value: None } + } +} + +/// The word size used in wasm. Normally known as `usize` in Rust. +pub type WordSize = u32; + +/// Sandbox memory identifier. +pub type MemoryId = u32; + +/// Host pointer: suit both for 32-bit and 64-bit archs. +pub type HostPointer = u64; + +/// Typed value that can be returned from a function. +/// +/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. +#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)] +pub enum ReturnValue { + /// For returning nothing. + Unit, + /// For returning some concrete value. + Value(Value), +} + +impl From for ReturnValue { + fn from(v: Value) -> ReturnValue { + ReturnValue::Value(v) + } +} + +impl ReturnValue { + /// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`. + /// + /// Breakdown: + /// 1 byte for encoding unit/value variant + /// 1 byte for encoding value type + /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. + pub const ENCODED_MAX_SIZE: usize = 10; +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::Encode; + + #[test] + fn pointer_offset_works() { + let ptr = Pointer::::null(); + + assert_eq!(ptr.offset(10).unwrap(), Pointer::new(40)); + assert_eq!(ptr.offset(32).unwrap(), Pointer::new(128)); + + let ptr = Pointer::::null(); + + assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80)); + assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256)); + } + + #[test] + fn return_value_encoded_max_size() { + let encoded = ReturnValue::Value(Value::I64(-1)).encode(); + assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE); + } +} diff --git a/primitives/wasm-interface/src/wasmi_impl.rs b/primitives/wasm-interface-common/src/wasmi_impl.rs similarity index 100% rename from primitives/wasm-interface/src/wasmi_impl.rs rename to primitives/wasm-interface-common/src/wasmi_impl.rs diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 96bf305dacd51..d8877675066d3 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -17,12 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", optional = true } -wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", optional = true, features = ["virtual_memory"] } wasmtime = { version = "6.0.1", default-features = false, optional = true } anyhow = { version = "1.0.68", optional = true } sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-wasm-interface-common = { version = "7.0.0", path = "../wasm-interface-common", default-features = false } +sp-allocator = { version = "4.1.0-dev", path = "../allocator", default-features = false } [features] default = [ "std" ] -std = [ "codec/std", "log", "sp-std/std", "wasmi", "wasmtime" ] +std = [ "codec/std", "log", "sp-allocator/std", "sp-std/std", "sp-wasm-interface-common/std", "wasmtime" ] wasmtime = [ "dep:wasmtime", "anyhow" ] +wasmi = [ "sp-wasm-interface-common/wasmi" ] diff --git a/primitives/wasm-interface/src/host_state.rs b/primitives/wasm-interface/src/host_state.rs new file mode 100644 index 0000000000000..440c9271dbf17 --- /dev/null +++ b/primitives/wasm-interface/src/host_state.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! This module defines `HostState` struct which provide logic and state +//! required for execution of host. + +use sp_allocator::{AllocationStats, FreeingBumpHeapAllocator}; + +/// The state required to construct a HostContext context. The context only lasts for one host +/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make +/// many different host calls that must share state. +pub struct HostState { + /// The allocator instance to keep track of allocated memory. + /// + /// This is stored as an `Option` as we need to temporarly set this to `None` when we are + /// allocating/deallocating memory. The problem being that we can only mutable access `caller` + /// once. + pub allocator: Option, + pub panic_message: Option, +} + +impl HostState { + /// Constructs a new `HostState`. + pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { + HostState { allocator: Some(allocator), panic_message: None } + } + + /// Takes the error message out of the host state, leaving a `None` in its place. + pub fn take_panic_message(&mut self) -> Option { + self.panic_message.take() + } + + pub fn allocation_stats(&self) -> AllocationStats { + self.allocator.as_ref() + .expect("Allocator is always set and only unavailable when doing an allocation/deallocation; qed") + .stats() + } +} diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index b096d236c01eb..f70ccc934c0fd 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -19,10 +19,24 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec, vec::Vec}; +use sp_std::{iter::Iterator, marker::PhantomData, result, vec, vec::Vec}; +pub use sp_wasm_interface_common::{self as common, Value, ValueType, Pointer, PointerType, IntoValue, TryFromValue, ReturnValue, WordSize, MemoryId, HostPointer, Signature}; -#[cfg(feature = "wasmi")] -mod wasmi_impl; +if_wasmtime_is_enabled! { + mod host_state; + pub use host_state::HostState; + + mod store_data; + pub use store_data::StoreData; + + mod memory_wrapper; + pub use memory_wrapper::MemoryWrapper; + + pub mod util; +} + +#[cfg(not(all(feature = "std", feature = "wasmtime")))] +pub struct StoreData; #[cfg(not(all(feature = "std", feature = "wasmtime")))] #[macro_export] @@ -52,81 +66,6 @@ pub type Result = result::Result; #[cfg(not(feature = "std"))] pub type Result = result::Result; -/// Value types supported by Substrate on the boundary between host/Wasm. -#[derive(Copy, Clone, PartialEq, Debug, Eq)] -pub enum ValueType { - /// An `i32` value type. - I32, - /// An `i64` value type. - I64, - /// An `f32` value type. - F32, - /// An `f64` value type. - F64, -} - -impl From for u8 { - fn from(val: ValueType) -> u8 { - match val { - ValueType::I32 => 0, - ValueType::I64 => 1, - ValueType::F32 => 2, - ValueType::F64 => 3, - } - } -} - -impl TryFrom for ValueType { - type Error = (); - - fn try_from(val: u8) -> sp_std::result::Result { - match val { - 0 => Ok(Self::I32), - 1 => Ok(Self::I64), - 2 => Ok(Self::F32), - 3 => Ok(Self::F64), - _ => Err(()), - } - } -} - -/// Values supported by Substrate on the boundary between host/Wasm. -#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)] -pub enum Value { - /// A 32-bit integer. - I32(i32), - /// A 64-bit integer. - I64(i64), - /// A 32-bit floating-point number stored as raw bit pattern. - /// - /// You can materialize this value using `f32::from_bits`. - F32(u32), - /// A 64-bit floating-point number stored as raw bit pattern. - /// - /// You can materialize this value using `f64::from_bits`. - F64(u64), -} - -impl Value { - /// Returns the type of this value. - pub fn value_type(&self) -> ValueType { - match self { - Value::I32(_) => ValueType::I32, - Value::I64(_) => ValueType::I64, - Value::F32(_) => ValueType::F32, - Value::F64(_) => ValueType::F64, - } - } - - /// Return `Self` as `i32`. - pub fn as_i32(&self) -> Option { - match self { - Self::I32(val) => Some(*val), - _ => None, - } - } -} - /// Provides `Sealed` trait to prevent implementing trait `PointerType` and `WasmTy` outside of this /// crate. mod private { @@ -139,123 +78,8 @@ mod private { impl Sealed for i32 {} impl Sealed for i64 {} -} - -/// Something that can be wrapped in a wasm `Pointer`. -/// -/// This trait is sealed. -pub trait PointerType: Sized + private::Sealed { - /// The size of the type in wasm. - const SIZE: u32 = mem::size_of::() as u32; -} - -impl PointerType for u8 {} -impl PointerType for u16 {} -impl PointerType for u32 {} -impl PointerType for u64 {} - -/// Type to represent a pointer in wasm at the host. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct Pointer { - ptr: u32, - _marker: PhantomData, -} - -impl Pointer { - /// Create a new instance of `Self`. - pub fn new(ptr: u32) -> Self { - Self { ptr, _marker: Default::default() } - } - - /// Calculate the offset from this pointer. - /// - /// `offset` is in units of `T`. So, `3` means `3 * mem::size_of::()` as offset to the - /// pointer. - /// - /// Returns an `Option` to respect that the pointer could probably overflow. - pub fn offset(self, offset: u32) -> Option { - offset - .checked_mul(T::SIZE) - .and_then(|o| self.ptr.checked_add(o)) - .map(|ptr| Self { ptr, _marker: Default::default() }) - } - - /// Create a null pointer. - pub fn null() -> Self { - Self::new(0) - } - - /// Cast this pointer of type `T` to a pointer of type `R`. - pub fn cast(self) -> Pointer { - Pointer::new(self.ptr) - } -} - -impl From for Pointer { - fn from(ptr: u32) -> Self { - Pointer::new(ptr) - } -} - -impl From> for u32 { - fn from(ptr: Pointer) -> Self { - ptr.ptr - } -} - -impl From> for u64 { - fn from(ptr: Pointer) -> Self { - u64::from(ptr.ptr) - } -} - -impl From> for usize { - fn from(ptr: Pointer) -> Self { - ptr.ptr as _ - } -} -impl IntoValue for Pointer { - const VALUE_TYPE: ValueType = ValueType::I32; - fn into_value(self) -> Value { - Value::I32(self.ptr as _) - } -} - -impl TryFromValue for Pointer { - fn try_from_value(val: Value) -> Option { - match val { - Value::I32(val) => Some(Self::new(val as _)), - _ => None, - } - } -} - -/// The word size used in wasm. Normally known as `usize` in Rust. -pub type WordSize = u32; - -/// The Signature of a function -#[derive(Eq, PartialEq, Debug, Clone)] -pub struct Signature { - /// The arguments of a function. - pub args: Cow<'static, [ValueType]>, - /// The optional return value of a function. - pub return_value: Option, -} - -impl Signature { - /// Create a new instance of `Signature`. - pub fn new>>( - args: T, - return_value: Option, - ) -> Self { - Self { args: args.into(), return_value } - } - - /// Create a new instance of `Signature` with the given `args` and without any return value. - pub fn new_with_args>>(args: T) -> Self { - Self { args: args.into(), return_value: None } - } + pub(super) struct Token; } /// A trait that requires `RefUnwindSafe` when `feature = std`. @@ -290,6 +114,20 @@ impl PartialEq for dyn Function { } } +#[cfg(not(all(feature = "std", feature = "wasmtime")))] +pub struct Caller<'a, T>(PhantomData<&'a T>); + +#[cfg(all(feature = "std", feature = "wasmtime"))] +pub use wasmtime::Caller; + +pub struct FunctionContextToken(private::Token); + +impl FunctionContextToken { + fn new() -> Self { + Self(private::Token) + } +} + /// Context used by `Function` to interact with the allocator and the memory of the wasm instance. pub trait FunctionContext { /// Read memory from `address` into a vector. @@ -328,6 +166,19 @@ pub trait FunctionContext { /// It should only be called once, however calling it more than once /// is harmless and will overwrite the previously set error message. fn register_panic_error_message(&mut self, message: &str); + + fn with_caller_mut_impl(&mut self, _: FunctionContextToken, context: *mut (), callback: fn(*mut (), &mut Caller)); + +} + +pub fn with_caller_mut)>(context: &mut dyn FunctionContext, mut callback: T) { + let callback: *mut T = &mut callback; + context.with_caller_mut_impl(FunctionContextToken::new(), callback.cast(), |callback, caller| { + let callback: *mut T = callback.cast(); + let callback: &mut T = unsafe { callback.as_mut().expect("we own the value, obtain mutable reference to it and cast to pointer (correct (not null) and aligned properly); qed") }; + + callback(caller); + }) } if_wasmtime_is_enabled! { @@ -522,105 +373,3 @@ impl WasmTy for i32 {} impl WasmTy for u32 {} impl WasmTy for i64 {} impl WasmTy for u64 {} - -/// Something that can be converted into a wasm compatible `Value`. -pub trait IntoValue { - /// The type of the value in wasm. - const VALUE_TYPE: ValueType; - - /// Convert `self` into a wasm `Value`. - fn into_value(self) -> Value; -} - -/// Something that can may be created from a wasm `Value`. -pub trait TryFromValue: Sized { - /// Try to convert the given `Value` into `Self`. - fn try_from_value(val: Value) -> Option; -} - -macro_rules! impl_into_and_from_value { - ( - $( - $type:ty, $( < $gen:ident >, )? $value_variant:ident, - )* - ) => { - $( - impl $( <$gen> )? IntoValue for $type { - const VALUE_TYPE: ValueType = ValueType::$value_variant; - fn into_value(self) -> Value { Value::$value_variant(self as _) } - } - - impl $( <$gen> )? TryFromValue for $type { - fn try_from_value(val: Value) -> Option { - match val { - Value::$value_variant(val) => Some(val as _), - _ => None, - } - } - } - )* - } -} - -impl_into_and_from_value! { - u8, I32, - u16, I32, - u32, I32, - u64, I64, - i8, I32, - i16, I32, - i32, I32, - i64, I64, -} - -/// Typed value that can be returned from a function. -/// -/// Basically a `TypedValue` plus `Unit`, for functions which return nothing. -#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)] -pub enum ReturnValue { - /// For returning nothing. - Unit, - /// For returning some concrete value. - Value(Value), -} - -impl From for ReturnValue { - fn from(v: Value) -> ReturnValue { - ReturnValue::Value(v) - } -} - -impl ReturnValue { - /// Maximum number of bytes `ReturnValue` might occupy when serialized with `SCALE`. - /// - /// Breakdown: - /// 1 byte for encoding unit/value variant - /// 1 byte for encoding value type - /// 8 bytes for encoding the biggest value types available in wasm: f64, i64. - pub const ENCODED_MAX_SIZE: usize = 10; -} - -#[cfg(test)] -mod tests { - use super::*; - use codec::Encode; - - #[test] - fn pointer_offset_works() { - let ptr = Pointer::::null(); - - assert_eq!(ptr.offset(10).unwrap(), Pointer::new(40)); - assert_eq!(ptr.offset(32).unwrap(), Pointer::new(128)); - - let ptr = Pointer::::null(); - - assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80)); - assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256)); - } - - #[test] - fn return_value_encoded_max_size() { - let encoded = ReturnValue::Value(Value::I64(-1)).encode(); - assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE); - } -} diff --git a/primitives/wasm-interface/src/memory_wrapper.rs b/primitives/wasm-interface/src/memory_wrapper.rs new file mode 100644 index 0000000000000..5a3960f6ded31 --- /dev/null +++ b/primitives/wasm-interface/src/memory_wrapper.rs @@ -0,0 +1,56 @@ +// 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. + +/// Wrapper around [`Memory`] that implements [`sp_allocator::Memory`]. +pub struct MemoryWrapper<'a, C>(&'a wasmtime::Memory, &'a mut C); + +impl<'a, C> From<(&'a wasmtime::Memory, &'a mut C)> for MemoryWrapper<'a, C> { + fn from((memory, caller): (&'a wasmtime::Memory, &'a mut C)) -> Self { + Self(memory, caller) + } +} + +impl sp_allocator::Memory for MemoryWrapper<'_, C> { + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + run(self.0.data(&self.1)) + } + + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + run(self.0.data_mut(&mut self.1)) + } + + fn grow(&mut self, additional: u32) -> std::result::Result<(), ()> { + self.0 + .grow(&mut self.1, additional as u64) + .map_err(|e| { + log::error!( + "Failed to grow memory by {} pages: {}", + additional, + e, + ) + }) + .map(drop) + } + + fn pages(&self) -> u32 { + self.0.size(&self.1) as u32 + } + + fn max_pages(&self) -> Option { + self.0.ty(&self.1).maximum().map(|p| p as _) + } +} diff --git a/primitives/wasm-interface/src/store_data.rs b/primitives/wasm-interface/src/store_data.rs new file mode 100644 index 0000000000000..570429f21e750 --- /dev/null +++ b/primitives/wasm-interface/src/store_data.rs @@ -0,0 +1,24 @@ +use crate::HostState; +use wasmtime::{Memory, Table}; + +#[derive(Default)] +pub struct StoreData { + /// This will only be set when we call into the runtime. + pub host_state: Option, + /// This will be always set once the store is initialized. + pub memory: Option, + /// This will be set only if the runtime actually contains a table. + pub table: Option
, +} + +impl StoreData { + /// Returns a mutable reference to the host state. + pub fn host_state_mut(&mut self) -> Option<&mut HostState> { + self.host_state.as_mut() + } + + /// Returns the host memory. + pub fn memory(&self) -> Memory { + self.memory.expect("memory is always set; qed") + } +} diff --git a/primitives/wasm-interface/src/util.rs b/primitives/wasm-interface/src/util.rs new file mode 100644 index 0000000000000..52a59cc2ae8c1 --- /dev/null +++ b/primitives/wasm-interface/src/util.rs @@ -0,0 +1,107 @@ +// 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 super::*; +use wasmtime::{AsContext, AsContextMut}; +use sp_std::ops::Range; + +/// Construct a range from an offset to a data length after the offset. +/// Returns None if the end of the range would exceed some maximum offset. +pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { + let end = offset.checked_add(len)?; + (end <= max).then(|| offset..end) +} + +pub fn write_memory_from( + mut ctx: impl AsContextMut, + address: Pointer, + data: &[u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory(); + let memory = memory.data_mut(&mut ctx); + + let range = checked_range(address.into(), data.len(), memory.len()) + .ok_or_else(|| String::from("memory write is out of bounds"))?; + memory[range].copy_from_slice(data); + Ok(()) +} + +pub fn read_memory_into( + ctx: impl AsContext, + address: Pointer, + dest: &mut [u8], +) -> Result<()> { + let memory = ctx.as_context().data().memory().data(&ctx); + + let range = checked_range(address.into(), dest.len(), memory.len()) + .ok_or_else(|| String::from("memory read is out of bounds"))?; + dest.copy_from_slice(&memory[range]); + Ok(()) +} + +pub fn read_memory( + ctx: impl AsContext, + address: Pointer, + size: WordSize, +) -> Result> { + let mut vec = vec![0; size as usize]; + read_memory_into(ctx, address, &mut vec)?; + Ok(vec) +} + +#[track_caller] +fn host_state_mut<'a>(caller: &'a mut Caller<'_, StoreData>) -> &'a mut HostState { + caller + .data_mut() + .host_state_mut() + .expect("host state is not empty when calling a function in wasm; qed") +} + +pub fn allocate_memory(caller: &mut Caller<'_, StoreData>, size: WordSize) -> Result> { + let mut allocator = host_state_mut(caller) + .allocator + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + let memory = caller.data().memory(); + // We can not return on error early, as we need to store back allocator. + let res = allocator + .allocate(&mut MemoryWrapper::from((&memory, &mut caller.as_context_mut())), size) + .map_err(|e| e.to_string()); + + host_state_mut(caller).allocator = Some(allocator); + + res +} + +pub fn deallocate_memory(caller: &mut Caller<'_, StoreData>, ptr: Pointer) -> Result<()> { + let mut allocator = host_state_mut(caller) + .allocator + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + let memory = caller.data().memory(); + + // We can not return on error early, as we need to store back allocator. + let res = allocator + .deallocate(&mut MemoryWrapper::from((&memory, &mut caller.as_context_mut())), ptr) + .map_err(|e| e.to_string()); + + host_state_mut(caller).allocator = Some(allocator); + + res +}