diff --git a/Cargo.lock b/Cargo.lock index 597c3303b3d..69a3e6b50cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3207,6 +3207,7 @@ dependencies = [ "enum-iterator", "enumset", "indexmap", + "memoffset", "more-asserts", "rkyv", "serde", diff --git a/lib/api/src/js/export.rs b/lib/api/src/js/export.rs index f396e617c53..0241899019f 100644 --- a/lib/api/src/js/export.rs +++ b/lib/api/src/js/export.rs @@ -7,6 +7,7 @@ use std::fmt; use wasm_bindgen::{JsCast, JsValue}; use wasmer_types::{ExternType, FunctionType, GlobalType, MemoryType, TableType}; +/// Represents linear memory that is managed by the javascript runtime #[derive(Clone, Debug, PartialEq)] pub struct VMMemory { pub(crate) memory: Memory, @@ -20,6 +21,11 @@ impl VMMemory { pub(crate) fn new(memory: Memory, ty: MemoryType) -> Self { Self { memory, ty } } + + /// Attempts to clone this memory (if its clonable) + pub(crate) fn try_clone(&self) -> Option { + Some(self.clone()) + } } #[derive(Clone, Debug, PartialEq)] diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index 6c5229ce92f..015769b896e 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -6,7 +6,6 @@ use crate::js::{MemoryAccessError, MemoryType}; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; -use thiserror::Error; #[cfg(feature = "tracing")] use tracing::warn; @@ -16,22 +15,7 @@ use wasmer_types::Pages; use super::MemoryView; -/// Error type describing things that can go wrong when operating on Wasm Memories. -#[derive(Error, Debug, Clone, PartialEq, Hash)] -pub enum MemoryError { - /// The operation would cause the size of the memory to exceed the maximum or would cause - /// an overflow leading to unindexable memory. - #[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)] - CouldNotGrow { - /// The current size in pages. - current: Pages, - /// The attempted amount to grow by in pages. - attempted_delta: Pages, - }, - /// A user defined error value, used for error cases not listed above. - #[error("A user-defined error occurred: {0}")] - Generic(String), -} +pub use wasmer_types::MemoryError; #[wasm_bindgen] extern "C" { @@ -113,7 +97,25 @@ impl Memory { .map_err(|_e| MemoryError::Generic("Error while creating the memory".to_owned()))?; let vm_memory = VMMemory::new(js_memory, ty); - Ok(Self::from_vm_export(store, vm_memory)) + let handle = StoreHandle::new(store.objects_mut(), vm_memory); + Ok(Self::from_vm_extern(store, handle.internal_handle())) + } + + /// Creates a new host `Memory` from provided JavaScript memory. + pub fn new_raw( + store: &mut impl AsStoreMut, + js_memory: js_sys::WebAssembly::Memory, + ty: MemoryType, + ) -> Result { + let vm_memory = VMMemory::new(js_memory, ty); + let handle = StoreHandle::new(store.objects_mut(), vm_memory); + Ok(Self::from_vm_extern(store, handle.internal_handle())) + } + + /// Create a memory object from an existing memory and attaches it to the store + pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self { + let handle = StoreHandle::new(new_store.objects_mut(), memory); + Self::from_vm_extern(new_store, handle.internal_handle()) } /// Returns the [`MemoryType`] of the `Memory`. @@ -193,12 +195,6 @@ impl Memory { Ok(Pages(new_pages)) } - pub(crate) fn from_vm_export(store: &mut impl AsStoreMut, vm_memory: VMMemory) -> Self { - Self { - handle: StoreHandle::new(store.objects_mut(), vm_memory), - } - } - pub(crate) fn from_vm_extern( store: &mut impl AsStoreMut, internal: InternalStoreHandle, @@ -210,6 +206,12 @@ impl Memory { } } + /// Attempts to clone this memory (if its clonable) + pub fn try_clone(&self, store: &impl AsStoreRef) -> Option { + let mem = self.handle.get(store.as_store_ref().objects()); + mem.try_clone() + } + /// Checks whether this `Global` can be used with the given context. pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool { self.handle.store_id() == store.as_store_ref().objects().id() diff --git a/lib/api/src/js/externals/mod.rs b/lib/api/src/js/externals/mod.rs index 4a3c8031508..2748e08b124 100644 --- a/lib/api/src/js/externals/mod.rs +++ b/lib/api/src/js/externals/mod.rs @@ -46,7 +46,7 @@ impl Extern { } /// Create an `Extern` from an `wasmer_compiler::Export`. - pub fn from_vm_export(store: &mut impl AsStoreMut, export: Export) -> Self { + pub fn from_vm_extern(store: &mut impl AsStoreMut, export: Export) -> Self { match export { Export::Function(f) => Self::Function(Function::from_vm_extern(store, f)), Export::Memory(m) => Self::Memory(Memory::from_vm_extern(store, m)), diff --git a/lib/api/src/js/function_env.rs b/lib/api/src/js/function_env.rs index 1705f1a4713..524fc7a2dbe 100644 --- a/lib/api/src/js/function_env.rs +++ b/lib/api/src/js/function_env.rs @@ -36,7 +36,7 @@ impl FunctionEnv { } /// Get the data as reference - pub fn as_ref<'a>(&self, store: &'a impl AsStoreMut) -> &'a T + pub fn as_ref<'a>(&self, store: &'a impl AsStoreRef) -> &'a T where T: Any + Send + 'static + Sized, { @@ -112,6 +112,11 @@ impl FunctionEnvMut<'_, T> { self.func_env.as_mut(&mut self.store_mut) } + /// Borrows a new immmutable reference + pub fn as_ref(&self) -> FunctionEnv { + self.func_env.clone() + } + /// Borrows a new mutable reference pub fn as_mut<'a>(&'a mut self) -> FunctionEnvMut<'a, T> { FunctionEnvMut { diff --git a/lib/api/src/js/imports.rs b/lib/api/src/js/imports.rs index 95a9930995c..63a199abb9c 100644 --- a/lib/api/src/js/imports.rs +++ b/lib/api/src/js/imports.rs @@ -174,6 +174,32 @@ impl Imports { } imports } + + /// Iterates through all the imports in this structure + pub fn iter<'a>(&'a self) -> ImportsIterator<'a> { + ImportsIterator::new(self) + } +} + +pub struct ImportsIterator<'a> { + iter: std::collections::hash_map::Iter<'a, (String, String), Extern>, +} + +impl<'a> ImportsIterator<'a> { + fn new(imports: &'a Imports) -> Self { + let iter = imports.map.iter(); + Self { iter } + } +} + +impl<'a> Iterator for ImportsIterator<'a> { + type Item = (&'a str, &'a str, &'a Extern); + + fn next(&mut self) -> Option { + self.iter + .next() + .map(|(k, v)| (k.0.as_str(), k.1.as_str(), v)) + } } impl IntoIterator for &Imports { diff --git a/lib/api/src/js/instance.rs b/lib/api/src/js/instance.rs index 6d2762adf66..1b1eb089858 100644 --- a/lib/api/src/js/instance.rs +++ b/lib/api/src/js/instance.rs @@ -105,7 +105,7 @@ impl Instance { })?; let export: Export = Export::from_js_value(js_export, &mut store, extern_type)?.into(); - let extern_ = Extern::from_vm_export(&mut store, export); + let extern_ = Extern::from_vm_extern(&mut store, export); Ok((name.to_string(), extern_)) }) .collect::>()?; diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index 14594839a15..1e660a53013 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -73,6 +73,12 @@ pub use crate::js::types::{ pub use crate::js::value::Value; pub use crate::js::value::Value as Val; +pub mod vm { + //! The `vm` module re-exports wasmer-vm types. + + pub use crate::js::export::VMMemory; +} + pub use wasmer_types::is_wasm; pub use wasmer_types::{ Bytes, ExportIndex, GlobalInit, LocalFunctionIndex, Pages, ValueType, WASM_MAX_PAGES, diff --git a/lib/api/src/js/native.rs b/lib/api/src/js/native.rs index 0e9066edac8..69b350652a1 100644 --- a/lib/api/src/js/native.rs +++ b/lib/api/src/js/native.rs @@ -11,7 +11,6 @@ use std::marker::PhantomData; use crate::js::externals::Function; use crate::js::store::{AsStoreMut, AsStoreRef, StoreHandle}; -use crate::js::FunctionEnv; use crate::js::{FromToNativeWasmType, RuntimeError, WasmTypeList}; // use std::panic::{catch_unwind, AssertUnwindSafe}; use crate::js::export::VMFunction; diff --git a/lib/api/src/js/store.rs b/lib/api/src/js/store.rs index b6c10e84627..66d6d58ec8c 100644 --- a/lib/api/src/js/store.rs +++ b/lib/api/src/js/store.rs @@ -263,6 +263,11 @@ mod objects { self.id } + /// Sets the ID of this store + pub fn set_id(&mut self, id: StoreId) { + self.id = id; + } + /// Returns a pair of mutable references from two handles. /// /// Panics if both handles point to the same object. diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index c0db55fa8af..285acb231c6 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -11,7 +11,7 @@ use std::slice; #[cfg(feature = "tracing")] use tracing::warn; use wasmer_types::Pages; -use wasmer_vm::{InternalStoreHandle, MemoryError, StoreHandle, VMExtern, VMMemory}; +use wasmer_vm::{InternalStoreHandle, LinearMemory, MemoryError, StoreHandle, VMExtern, VMMemory}; use super::MemoryView; @@ -60,6 +60,12 @@ impl Memory { }) } + /// Create a memory object from an existing memory and attaches it to the store + pub fn new_from_existing(new_store: &mut impl AsStoreMut, memory: VMMemory) -> Self { + let handle = StoreHandle::new(new_store.objects_mut(), memory); + Self::from_vm_extern(new_store, handle.internal_handle()) + } + /// Returns the [`MemoryType`] of the `Memory`. /// /// # Example @@ -142,6 +148,12 @@ impl Memory { self.handle.store_id() == store.as_store_ref().objects().id() } + /// Attempts to clone this memory (if its clonable) + pub fn try_clone(&self, store: &impl AsStoreRef) -> Option { + let mem = self.handle.get(store.as_store_ref().objects()); + mem.try_clone().map(|mem| mem.into()) + } + pub(crate) fn to_vm_extern(&self) -> VMExtern { VMExtern::Memory(self.handle.internal_handle()) } diff --git a/lib/api/src/sys/externals/memory_view.rs b/lib/api/src/sys/externals/memory_view.rs index 891700c0854..a872ea7c259 100644 --- a/lib/api/src/sys/externals/memory_view.rs +++ b/lib/api/src/sys/externals/memory_view.rs @@ -5,6 +5,7 @@ use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; use wasmer_types::Pages; +use wasmer_vm::LinearMemory; use super::memory::MemoryBuffer; use super::Memory; diff --git a/lib/api/src/sys/tunables.rs b/lib/api/src/sys/tunables.rs index 00663cab749..0a1eadd7609 100644 --- a/lib/api/src/sys/tunables.rs +++ b/lib/api/src/sys/tunables.rs @@ -174,4 +174,138 @@ mod tests { s => panic!("Unexpected memory style: {:?}", s), } } + + use std::cell::UnsafeCell; + use std::ptr::NonNull; + use wasmer_types::{MemoryError, MemoryStyle, MemoryType, Pages, WASM_PAGE_SIZE}; + use wasmer_vm::{LinearMemory, MaybeInstanceOwned}; + + #[derive(Debug)] + struct VMTinyMemory { + mem: [u8; WASM_PAGE_SIZE], + } + + unsafe impl Send for VMTinyMemory {} + unsafe impl Sync for VMTinyMemory {} + + impl VMTinyMemory { + pub fn new() -> Result { + Ok(VMTinyMemory { + mem: [0; WASM_PAGE_SIZE], + }) + } + } + + impl LinearMemory for VMTinyMemory { + fn ty(&self) -> MemoryType { + MemoryType { + minimum: Pages::from(1u32), + maximum: Some(Pages::from(1u32)), + shared: false, + } + } + fn size(&self) -> Pages { + Pages::from(1u32) + } + fn style(&self) -> MemoryStyle { + MemoryStyle::Static { + bound: Pages::from(1u32), + offset_guard_size: 0, + } + } + fn grow(&mut self, delta: Pages) -> Result { + Err(MemoryError::CouldNotGrow { + current: Pages::from(100u32), + attempted_delta: delta, + }) + } + fn vmmemory(&self) -> NonNull { + MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMMemoryDefinition { + base: self.mem.as_ptr() as _, + current_length: WASM_PAGE_SIZE, + }))) + .as_ptr() + } + fn try_clone(&self) -> Option> { + None + } + } + + impl From for wasmer_vm::VMMemory { + fn from(mem: VMTinyMemory) -> Self { + Self(Box::new(mem)) + } + } + + struct TinyTunables; + impl Tunables for TinyTunables { + fn memory_style(&self, _memory: &MemoryType) -> MemoryStyle { + MemoryStyle::Static { + bound: Pages::from(1u32), + offset_guard_size: 0, + } + } + + /// Construct a `TableStyle` for the provided `TableType` + fn table_style(&self, _table: &TableType) -> TableStyle { + TableStyle::CallerChecksSignature + } + fn create_host_memory( + &self, + _ty: &MemoryType, + _style: &MemoryStyle, + ) -> Result { + let memory = VMTinyMemory::new().unwrap(); + Ok(VMMemory::from_custom(memory)) + } + unsafe fn create_vm_memory( + &self, + _ty: &MemoryType, + _style: &MemoryStyle, + _vm_definition_location: NonNull, + ) -> Result { + let memory = VMTinyMemory::new().unwrap(); + Ok(memory.into()) + } + + /// Create a table owned by the host given a [`TableType`] and a [`TableStyle`]. + fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result { + VMTable::new(ty, style) + } + + /// Create a table owned by the VM given a [`TableType`] and a [`TableStyle`]. + /// + /// # Safety + /// - `vm_definition_location` must point to a valid location in VM memory. + unsafe fn create_vm_table( + &self, + ty: &TableType, + style: &TableStyle, + vm_definition_location: NonNull, + ) -> Result { + VMTable::from_definition(ty, style, vm_definition_location) + } + } + + #[test] + fn check_linearmemory() { + let tunables = TinyTunables {}; + let vmmemory = tunables.create_host_memory( + &MemoryType::new(1u32, Some(100u32), true), + &MemoryStyle::Static { + bound: Pages::from(1u32), + offset_guard_size: 0u64, + }, + ); + let mut vmmemory = vmmemory.unwrap(); + assert!(vmmemory.grow(Pages::from(2u32)).is_err()); + assert_eq!(vmmemory.size(), Pages::from(1u32)); + assert_eq!( + vmmemory.grow(Pages::from(0u32)).err().unwrap(), + MemoryError::CouldNotGrow { + current: Pages::from(100u32), + attempted_delta: Pages::from(0u32) + } + ); + } } diff --git a/lib/compiler/src/engine/resolver.rs b/lib/compiler/src/engine/resolver.rs index 207380bb819..87fa45f3001 100644 --- a/lib/compiler/src/engine/resolver.rs +++ b/lib/compiler/src/engine/resolver.rs @@ -8,8 +8,9 @@ use wasmer_types::{ }; use wasmer_vm::{ - FunctionBodyPtr, Imports, MemoryStyle, StoreObjects, TableStyle, VMExtern, VMFunctionBody, - VMFunctionImport, VMFunctionKind, VMGlobalImport, VMMemoryImport, VMTableImport, + FunctionBodyPtr, Imports, LinearMemory, MemoryStyle, StoreObjects, TableStyle, VMExtern, + VMFunctionBody, VMFunctionImport, VMFunctionKind, VMGlobalImport, VMMemoryImport, + VMTableImport, }; /// Get an `ExternType` given a import index. @@ -149,7 +150,7 @@ pub fn resolve_imports( bound: import_bound, .. }, - ) = (export_memory_style.clone(), &import_memory_style) + ) = (export_memory_style, &import_memory_style) { assert_ge!(bound, *import_bound); } diff --git a/lib/types/Cargo.toml b/lib/types/Cargo.toml index e10aebf87df..3ee22d50426 100644 --- a/lib/types/Cargo.toml +++ b/lib/types/Cargo.toml @@ -21,6 +21,9 @@ enum-iterator = "0.7.0" target-lexicon = { version = "0.12.2", default-features = false } enumset = "1.0" +[dev-dependencies] +memoffset = "0.6" + [features] default = ["std"] std = [] diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 53555d6239c..0e0a1eb12fd 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -1,5 +1,5 @@ //! The WebAssembly possible errors -use crate::ExternType; +use crate::{ExternType, Pages}; use std::io; use thiserror::Error; @@ -37,6 +37,48 @@ pub enum DeserializeError { Compiler(#[from] CompileError), } +/// Error type describing things that can go wrong when operating on Wasm Memories. +#[derive(Error, Debug, Clone, PartialEq, Hash)] +pub enum MemoryError { + /// Low level error with mmap. + #[error("Error when allocating memory: {0}")] + Region(String), + /// The operation would cause the size of the memory to exceed the maximum or would cause + /// an overflow leading to unindexable memory. + #[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)] + CouldNotGrow { + /// The current size in pages. + current: Pages, + /// The attempted amount to grow by in pages. + attempted_delta: Pages, + }, + /// The operation would cause the size of the memory size exceed the maximum. + #[error("The memory is invalid because {}", reason)] + InvalidMemory { + /// The reason why the provided memory is invalid. + reason: String, + }, + /// Caller asked for more minimum memory than we can give them. + #[error("The minimum requested ({} pages) memory is greater than the maximum allowed memory ({} pages)", min_requested.0, max_allowed.0)] + MinimumMemoryTooLarge { + /// The number of pages requested as the minimum amount of memory. + min_requested: Pages, + /// The maximum amount of memory we can allocate. + max_allowed: Pages, + }, + /// Caller asked for a maximum memory greater than we can give them. + #[error("The maximum requested memory ({} pages) is greater than the maximum allowed memory ({} pages)", max_requested.0, max_allowed.0)] + MaximumMemoryTooLarge { + /// The number of pages requested as the maximum amount of memory. + max_requested: Pages, + /// The number of pages requested as the maximum amount of memory. + max_allowed: Pages, + }, + /// A user defined error value, used for error cases not listed above. + #[error("A user-defined error occurred: {0}")] + Generic(String), +} + /// An ImportError. /// /// Note: this error is not standard to WebAssembly, but it's @@ -52,6 +94,10 @@ pub enum ImportError { /// This error occurs when an import was expected but not provided. #[error("unknown import. Expected {0:?}")] UnknownImport(ExternType), + + /// Memory Error + #[error("memory error. {0}")] + MemoryError(String), } /// An error while preinstantiating a module. diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 3fcfbe9504d..4eebc53e25e 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -76,8 +76,8 @@ pub use crate::compilation::target::{ }; pub use crate::serialize::{MetadataHeader, SerializableCompilation, SerializableModule}; pub use error::{ - CompileError, DeserializeError, ImportError, MiddlewareError, ParseCpuFeatureError, - PreInstantiationError, SerializeError, WasmError, WasmResult, + CompileError, DeserializeError, ImportError, MemoryError, MiddlewareError, + ParseCpuFeatureError, PreInstantiationError, SerializeError, WasmError, WasmResult, }; /// The entity module, with common helpers for Rust structures diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index fafcb955a8a..2c986d90870 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -7,7 +7,7 @@ use std::iter::Sum; use std::ops::{Add, AddAssign}; /// Implementation styles for WebAssembly linear memory. -#[derive(Debug, Clone, PartialEq, Eq, Hash, RkyvSerialize, RkyvDeserialize, Archive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, RkyvSerialize, RkyvDeserialize, Archive)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[archive(as = "Self")] pub enum MemoryStyle { @@ -84,6 +84,9 @@ pub unsafe trait MemorySize: Copy { /// Zero value used for `WasmPtr::is_null`. const ZERO: Self::Offset; + /// One value used for counting. + const ONE: Self::Offset; + /// Convert an `Offset` to a `Native`. fn offset_to_native(offset: Self::Offset) -> Self::Native; @@ -98,6 +101,7 @@ unsafe impl MemorySize for Memory32 { type Offset = u32; type Native = i32; const ZERO: Self::Offset = 0; + const ONE: Self::Offset = 1; fn offset_to_native(offset: Self::Offset) -> Self::Native { offset as Self::Native } @@ -113,6 +117,7 @@ unsafe impl MemorySize for Memory64 { type Offset = u64; type Native = i64; const ZERO: Self::Offset = 0; + const ONE: Self::Offset = 1; fn offset_to_native(offset: Self::Offset) -> Self::Native { offset as Self::Native } diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index bf0be8a295b..c91a8f63fdc 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -10,16 +10,16 @@ mod allocator; use crate::export::VMExtern; use crate::imports::Imports; -use crate::memory::MemoryError; use crate::store::{InternalStoreHandle, StoreObjects}; use crate::table::TableElement; use crate::trap::{catch_traps, Trap, TrapCode}; use crate::vmcontext::{ - VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionContext, - VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, + memory_copy, memory_fill, VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, + VMFunctionContext, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, }; use crate::{FunctionBodyPtr, MaybeInstanceOwned, TrapHandlerFn, VMFunctionBody}; +use crate::{LinearMemory, VMMemoryDefinition}; use crate::{VMFuncRef, VMFunction, VMGlobal, VMMemory, VMTable}; pub use allocator::InstanceAllocator; use memoffset::offset_of; @@ -36,8 +36,8 @@ use std::sync::Arc; use wasmer_types::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap}; use wasmer_types::{ DataIndex, DataInitializer, ElemIndex, ExportIndex, FunctionIndex, GlobalIndex, GlobalInit, - LocalFunctionIndex, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, - ModuleInfo, Pages, SignatureIndex, TableIndex, TableInitializer, VMOffsets, + LocalFunctionIndex, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryError, + MemoryIndex, ModuleInfo, Pages, SignatureIndex, TableIndex, TableInitializer, VMOffsets, }; /// A WebAssembly instance. @@ -632,7 +632,7 @@ impl Instance { let memory = self.memory(memory_index); // The following memory copy is not synchronized and is not atomic: - unsafe { memory.memory_copy(dst, src, len) } + unsafe { memory_copy(&memory, dst, src, len) } } /// Perform a `memory.copy` on an imported memory. @@ -646,7 +646,7 @@ impl Instance { let import = self.imported_memory(memory_index); let memory = unsafe { import.definition.as_ref() }; // The following memory copy is not synchronized and is not atomic: - unsafe { memory.memory_copy(dst, src, len) } + unsafe { memory_copy(memory, dst, src, len) } } /// Perform the `memory.fill` operation on a locally defined memory. @@ -663,7 +663,7 @@ impl Instance { ) -> Result<(), Trap> { let memory = self.memory(memory_index); // The following memory fill is not synchronized and is not atomic: - unsafe { memory.memory_fill(dst, val, len) } + unsafe { memory_fill(&memory, dst, val, len) } } /// Perform the `memory.fill` operation on an imported memory. @@ -681,7 +681,7 @@ impl Instance { let import = self.imported_memory(memory_index); let memory = unsafe { import.definition.as_ref() }; // The following memory fill is not synchronized and is not atomic: - unsafe { memory.memory_fill(dst, val, len) } + unsafe { memory_fill(memory, dst, val, len) } } /// Performs the `memory.init` operation. diff --git a/lib/vm/src/lib.rs b/lib/vm/src/lib.rs index 1b30f1e61e4..19982e54fcc 100644 --- a/lib/vm/src/lib.rs +++ b/lib/vm/src/lib.rs @@ -45,7 +45,7 @@ pub use crate::function_env::VMFunctionEnvironment; pub use crate::global::*; pub use crate::imports::Imports; pub use crate::instance::{InstanceAllocator, InstanceHandle}; -pub use crate::memory::{MemoryError, VMMemory}; +pub use crate::memory::{LinearMemory, VMMemory}; pub use crate::mmap::Mmap; pub use crate::probestack::PROBESTACK; pub use crate::sig_registry::SignatureRegistry; @@ -60,6 +60,7 @@ pub use crate::vmcontext::{ VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, }; pub use wasmer_types::LibCall; +pub use wasmer_types::MemoryError; pub use wasmer_types::MemoryStyle; use wasmer_types::RawValue; pub use wasmer_types::TableStyle; diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index 787d5f390b5..f071365d19f 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -5,88 +5,156 @@ //! //! `Memory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. -use crate::vmcontext::VMMemoryDefinition; -use crate::{mmap::Mmap, store::MaybeInstanceOwned}; +use crate::{mmap::Mmap, store::MaybeInstanceOwned, vmcontext::VMMemoryDefinition}; use more_asserts::assert_ge; use std::cell::UnsafeCell; use std::convert::TryInto; use std::ptr::NonNull; -use thiserror::Error; -use wasmer_types::{Bytes, MemoryStyle, MemoryType, Pages}; - -/// Error type describing things that can go wrong when operating on Wasm Memories. -#[derive(Error, Debug, Clone, Eq, PartialEq, Hash)] -pub enum MemoryError { - /// Low level error with mmap. - #[error("Error when allocating memory: {0}")] - Region(String), - /// The operation would cause the size of the memory to exceed the maximum or would cause - /// an overflow leading to unindexable memory. - #[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)] - CouldNotGrow { - /// The current size in pages. - current: Pages, - /// The attempted amount to grow by in pages. - attempted_delta: Pages, - }, - /// The operation would cause the size of the memory size exceed the maximum. - #[error("The memory is invalid because {}", reason)] - InvalidMemory { - /// The reason why the provided memory is invalid. - reason: String, - }, - /// Caller asked for more minimum memory than we can give them. - #[error("The minimum requested ({} pages) memory is greater than the maximum allowed memory ({} pages)", min_requested.0, max_allowed.0)] - MinimumMemoryTooLarge { - /// The number of pages requested as the minimum amount of memory. - min_requested: Pages, - /// The maximum amount of memory we can allocate. - max_allowed: Pages, - }, - /// Caller asked for a maximum memory greater than we can give them. - #[error("The maximum requested memory ({} pages) is greater than the maximum allowed memory ({} pages)", max_requested.0, max_allowed.0)] - MaximumMemoryTooLarge { - /// The number of pages requested as the maximum amount of memory. - max_requested: Pages, - /// The number of pages requested as the maximum amount of memory. - max_allowed: Pages, - }, - /// A user defined error value, used for error cases not listed above. - #[error("A user-defined error occurred: {0}")] - Generic(String), +use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages}; + +// The memory mapped area +#[derive(Debug)] +struct WasmMmap { + // Our OS allocation of mmap'd memory. + alloc: Mmap, + // The current logical size in wasm pages of this linear memory. + size: Pages, + /// The owned memory definition used by the generated code + vm_memory_definition: MaybeInstanceOwned, } -/// A linear memory instance. -pub struct VMMemory { - // The underlying allocation. - mmap: WasmMmap, +impl WasmMmap { + fn get_vm_memory_definition(&self) -> NonNull { + self.vm_memory_definition.as_ptr() + } + fn size(&self) -> Pages { + unsafe { + let md_ptr = self.get_vm_memory_definition(); + let md = md_ptr.as_ref(); + Bytes::from(md.current_length).try_into().unwrap() + } + } + + fn grow(&mut self, delta: Pages, conf: VMMemoryConfig) -> Result { + // Optimization of memory.grow 0 calls. + if delta.0 == 0 { + return Ok(self.size); + } + + let new_pages = self + .size + .checked_add(delta) + .ok_or(MemoryError::CouldNotGrow { + current: self.size, + attempted_delta: delta, + })?; + let prev_pages = self.size; + + if let Some(maximum) = conf.maximum { + if new_pages > maximum { + return Err(MemoryError::CouldNotGrow { + current: self.size, + attempted_delta: delta, + }); + } + } + + // Wasm linear memories are never allowed to grow beyond what is + // indexable. If the memory has no maximum, enforce the greatest + // limit here. + if new_pages >= Pages::max_value() { + // Linear memory size would exceed the index range. + return Err(MemoryError::CouldNotGrow { + current: self.size, + attempted_delta: delta, + }); + } + + let delta_bytes = delta.bytes().0; + let prev_bytes = prev_pages.bytes().0; + let new_bytes = new_pages.bytes().0; + + if new_bytes > self.alloc.len() - conf.offset_guard_size { + // If the new size is within the declared maximum, but needs more memory than we + // have on hand, it's a dynamic heap and it can move. + let guard_bytes = conf.offset_guard_size; + let request_bytes = + new_bytes + .checked_add(guard_bytes) + .ok_or_else(|| MemoryError::CouldNotGrow { + current: new_pages, + attempted_delta: Bytes(guard_bytes).try_into().unwrap(), + })?; + + let mut new_mmap = + Mmap::accessible_reserved(new_bytes, request_bytes).map_err(MemoryError::Region)?; + + let copy_len = self.alloc.len() - conf.offset_guard_size; + new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.alloc.as_slice()[..copy_len]); + + self.alloc = new_mmap; + } else if delta_bytes > 0 { + // Make the newly allocated pages accessible. + self.alloc + .make_accessible(prev_bytes, delta_bytes) + .map_err(MemoryError::Region)?; + } + + self.size = new_pages; + + // update memory definition + unsafe { + let mut md_ptr = self.vm_memory_definition.as_ptr(); + let md = md_ptr.as_mut(); + md.current_length = new_pages.bytes().0; + md.base = self.alloc.as_mut_ptr() as _; + } + + Ok(prev_pages) + } +} + +/// A linear memory instance. +#[derive(Debug, Clone)] +struct VMMemoryConfig { // The optional maximum size in wasm pages of this linear memory. maximum: Option, - /// The WebAssembly linear memory description. memory: MemoryType, - /// Our chosen implementation style. style: MemoryStyle, - // Size in bytes of extra guard pages after the end to optimize loads and stores with // constant offsets. offset_guard_size: usize, +} - /// The owned memory definition used by the generated code - vm_memory_definition: MaybeInstanceOwned, +impl VMMemoryConfig { + fn ty(&self, minimum: Pages) -> MemoryType { + let mut out = self.memory; + out.minimum = minimum; + + out + } + + fn style(&self) -> MemoryStyle { + self.style + } } +/// A linear memory instance. #[derive(Debug)] -struct WasmMmap { - // Our OS allocation of mmap'd memory. - alloc: Mmap, - // The current logical size in wasm pages of this linear memory. - size: Pages, +pub struct VMOwnedMemory { + // The underlying allocation. + mmap: WasmMmap, + // Configuration of this memory + config: VMMemoryConfig, } -impl VMMemory { +unsafe impl Send for VMOwnedMemory {} +unsafe impl Sync for VMOwnedMemory {} + +impl VMOwnedMemory { /// Create a new linear memory instance with specified minimum and maximum number of wasm pages. /// /// This creates a `Memory` with owned metadata: this can be used to create a memory @@ -154,18 +222,11 @@ impl VMMemory { let mapped_pages = memory.minimum; let mapped_bytes = mapped_pages.bytes(); - let mut mmap = WasmMmap { - alloc: Mmap::accessible_reserved(mapped_bytes.0, request_bytes) - .map_err(MemoryError::Region)?, - size: memory.minimum, - }; - - let base_ptr = mmap.alloc.as_mut_ptr(); + let mut alloc = Mmap::accessible_reserved(mapped_bytes.0, request_bytes) + .map_err(MemoryError::Region)?; + let base_ptr = alloc.as_mut_ptr(); let mem_length = memory.minimum.bytes().0; - Ok(Self { - mmap, - maximum: memory.maximum, - offset_guard_size: offset_guard_bytes, + let mmap = WasmMmap { vm_memory_definition: if let Some(mem_loc) = vm_memory_location { { let mut ptr = mem_loc; @@ -180,127 +241,173 @@ impl VMMemory { current_length: mem_length, }))) }, - memory: *memory, - style: style.clone(), - }) - } + alloc, + size: memory.minimum, + }; - /// Get the `VMMemoryDefinition`. - fn get_vm_memory_definition(&self) -> NonNull { - self.vm_memory_definition.as_ptr() + Ok(Self { + mmap, + config: VMMemoryConfig { + maximum: memory.maximum, + offset_guard_size: offset_guard_bytes, + memory: *memory, + style: *style, + }, + }) } +} +impl LinearMemory for VMOwnedMemory { /// Returns the type for this memory. - pub fn ty(&self) -> MemoryType { - let minimum = self.size(); - let mut out = self.memory; - out.minimum = minimum; - - out + fn ty(&self) -> MemoryType { + let minimum = self.mmap.size(); + self.config.ty(minimum) } - /// Returns the memory style for this memory. - pub fn style(&self) -> &MemoryStyle { - &self.style + /// Returns the size of hte memory in pages + fn size(&self) -> Pages { + self.mmap.size() } - /// Returns the number of allocated wasm pages. - pub fn size(&self) -> Pages { - // TODO: investigate this function for race conditions - unsafe { - let md_ptr = self.get_vm_memory_definition(); - let md = md_ptr.as_ref(); - Bytes::from(md.current_length).try_into().unwrap() - } + /// Returns the memory style for this memory. + fn style(&self) -> MemoryStyle { + self.config.style() } /// Grow memory by the specified amount of wasm pages. /// /// Returns `None` if memory can't be grown by the specified amount /// of wasm pages. - pub fn grow(&mut self, delta: Pages) -> Result { - // Optimization of memory.grow 0 calls. - if delta.0 == 0 { - return Ok(self.mmap.size); - } + fn grow(&mut self, delta: Pages) -> Result { + self.mmap.grow(delta, self.config.clone()) + } - let new_pages = self - .mmap - .size - .checked_add(delta) - .ok_or(MemoryError::CouldNotGrow { - current: self.mmap.size, - attempted_delta: delta, - })?; - let prev_pages = self.mmap.size; + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. + fn vmmemory(&self) -> NonNull { + self.mmap.vm_memory_definition.as_ptr() + } - if let Some(maximum) = self.maximum { - if new_pages > maximum { - return Err(MemoryError::CouldNotGrow { - current: self.mmap.size, - attempted_delta: delta, - }); - } - } + /// Owned memory can not be cloned (this will always return None) + fn try_clone(&self) -> Option> { + None + } +} - // Wasm linear memories are never allowed to grow beyond what is - // indexable. If the memory has no maximum, enforce the greatest - // limit here. - if new_pages >= Pages::max_value() { - // Linear memory size would exceed the index range. - return Err(MemoryError::CouldNotGrow { - current: self.mmap.size, - attempted_delta: delta, - }); - } +impl From for VMMemory { + fn from(mem: VMOwnedMemory) -> Self { + Self(Box::new(mem)) + } +} - let delta_bytes = delta.bytes().0; - let prev_bytes = prev_pages.bytes().0; - let new_bytes = new_pages.bytes().0; +/// Represents linear memory that can be either owned or shared +#[derive(Debug)] +pub struct VMMemory(pub Box); - if new_bytes > self.mmap.alloc.len() - self.offset_guard_size { - // If the new size is within the declared maximum, but needs more memory than we - // have on hand, it's a dynamic heap and it can move. - let guard_bytes = self.offset_guard_size; - let request_bytes = - new_bytes - .checked_add(guard_bytes) - .ok_or_else(|| MemoryError::CouldNotGrow { - current: new_pages, - attempted_delta: Bytes(guard_bytes).try_into().unwrap(), - })?; +impl From> for VMMemory { + fn from(mem: Box) -> Self { + Self(mem) + } +} - let mut new_mmap = - Mmap::accessible_reserved(new_bytes, request_bytes).map_err(MemoryError::Region)?; +impl LinearMemory for VMMemory { + /// Returns the type for this memory. + fn ty(&self) -> MemoryType { + self.0.ty() + } - let copy_len = self.mmap.alloc.len() - self.offset_guard_size; - new_mmap.as_mut_slice()[..copy_len] - .copy_from_slice(&self.mmap.alloc.as_slice()[..copy_len]); + /// Returns the size of hte memory in pages + fn size(&self) -> Pages { + self.0.size() + } - self.mmap.alloc = new_mmap; - } else if delta_bytes > 0 { - // Make the newly allocated pages accessible. - self.mmap - .alloc - .make_accessible(prev_bytes, delta_bytes) - .map_err(MemoryError::Region)?; - } + /// Grow memory by the specified amount of wasm pages. + /// + /// Returns `None` if memory can't be grown by the specified amount + /// of wasm pages. + fn grow(&mut self, delta: Pages) -> Result { + self.0.grow(delta) + } - self.mmap.size = new_pages; + /// Returns the memory style for this memory. + fn style(&self) -> MemoryStyle { + self.0.style() + } - // update memory definition - unsafe { - let mut md_ptr = self.get_vm_memory_definition(); - let md = md_ptr.as_mut(); - md.current_length = new_pages.bytes().0; - md.base = self.mmap.alloc.as_mut_ptr() as _; - } + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. + fn vmmemory(&self) -> NonNull { + self.0.vmmemory() + } - Ok(prev_pages) + /// Attempts to clone this memory (if its clonable) + fn try_clone(&self) -> Option> { + self.0.try_clone() } +} - /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. - pub fn vmmemory(&self) -> NonNull { - self.get_vm_memory_definition() +impl VMMemory { + /// Creates a new linear memory instance of the correct type with specified + /// minimum and maximum number of wasm pages. + /// + /// This creates a `Memory` with owned metadata: this can be used to create a memory + /// that will be imported into Wasm modules. + pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result { + Ok(Self(Box::new(VMOwnedMemory::new(memory, style)?))) + } + + /// Create a new linear memory instance with specified minimum and maximum number of wasm pages. + /// + /// This creates a `Memory` with metadata owned by a VM, pointed to by + /// `vm_memory_location`: this can be used to create a local memory. + /// + /// # Safety + /// - `vm_memory_location` must point to a valid location in VM memory. + pub unsafe fn from_definition( + memory: &MemoryType, + style: &MemoryStyle, + vm_memory_location: NonNull, + ) -> Result { + Ok(Self(Box::new(VMOwnedMemory::from_definition( + memory, + style, + vm_memory_location, + )?))) + } + + /// Creates VMMemory from a custom implementation - the following into implementations + /// are natively supported + /// - VMOwnedMemory -> VMMemory + /// - Box -> VMMemory + pub fn from_custom(memory: IntoVMMemory) -> VMMemory + where + IntoVMMemory: Into, + { + memory.into() } } + +/// Represents memory that is used by the WebAsssembly module +pub trait LinearMemory +where + Self: std::fmt::Debug + Send, +{ + /// Returns the type for this memory. + fn ty(&self) -> MemoryType; + + /// Returns the size of hte memory in pages + fn size(&self) -> Pages; + + /// Returns the memory style for this memory. + fn style(&self) -> MemoryStyle; + + /// Grow memory by the specified amount of wasm pages. + /// + /// Returns `None` if memory can't be grown by the specified amount + /// of wasm pages. + fn grow(&mut self, delta: Pages) -> Result; + + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. + fn vmmemory(&self) -> NonNull; + + /// Attempts to clone this memory (if its clonable) + fn try_clone(&self) -> Option>; +} diff --git a/lib/vm/src/store.rs b/lib/vm/src/store.rs index 4faee579a4d..57630f15c02 100644 --- a/lib/vm/src/store.rs +++ b/lib/vm/src/store.rs @@ -266,3 +266,23 @@ impl MaybeInstanceOwned { } } } + +impl std::fmt::Debug for MaybeInstanceOwned +where + T: std::fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MaybeInstanceOwned::Host(p) => { + write!(f, "host(")?; + p.as_ref().fmt(f)?; + write!(f, ")") + } + MaybeInstanceOwned::Instance(p) => { + write!(f, "instance(")?; + unsafe { p.as_ref().fmt(f)? }; + write!(f, ")") + } + } + } +} diff --git a/lib/vm/src/vmcontext.rs b/lib/vm/src/vmcontext.rs index bb1cb6cc9fc..766a8708d1d 100644 --- a/lib/vm/src/vmcontext.rs +++ b/lib/vm/src/vmcontext.rs @@ -303,120 +303,77 @@ mod test_vmglobal_import { } } -/// The fields compiled code needs to access to utilize a WebAssembly linear -/// memory defined within the instance, namely the start address and the -/// size in bytes. -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub struct VMMemoryDefinition { - /// The start address which is always valid, even if the memory grows. - pub base: *mut u8, - - /// The current logical size of this linear memory in bytes. - pub current_length: usize, -} - -/// # Safety -/// This data is safe to share between threads because it's plain data that -/// is the user's responsibility to synchronize. -unsafe impl Send for VMMemoryDefinition {} +/// Do an unsynchronized, non-atomic `memory.copy` for the memory. +/// +/// # Errors +/// +/// Returns a `Trap` error when the source or destination ranges are out of +/// bounds. +/// /// # Safety -/// This data is safe to share between threads because it's plain data that -/// is the user's responsibility to synchronize. And it's `Copy` so there's -/// really no difference between passing it by reference or by value as far as -/// correctness in a multi-threaded context is concerned. -unsafe impl Sync for VMMemoryDefinition {} - -impl VMMemoryDefinition { - /// Do an unsynchronized, non-atomic `memory.copy` for the memory. - /// - /// # Errors - /// - /// Returns a `Trap` error when the source or destination ranges are out of - /// bounds. - /// - /// # Safety - /// The memory is not copied atomically and is not synchronized: it's the - /// caller's responsibility to synchronize. - pub(crate) unsafe fn memory_copy(&self, dst: u32, src: u32, len: u32) -> Result<(), Trap> { - // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-memory-copy - if src +/// The memory is not copied atomically and is not synchronized: it's the +/// caller's responsibility to synchronize. +pub(crate) unsafe fn memory_copy( + mem: &VMMemoryDefinition, + dst: u32, + src: u32, + len: u32, +) -> Result<(), Trap> { + // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-memory-copy + if src + .checked_add(len) + .map_or(true, |n| usize::try_from(n).unwrap() > mem.current_length) + || dst .checked_add(len) - .map_or(true, |n| usize::try_from(n).unwrap() > self.current_length) - || dst - .checked_add(len) - .map_or(true, |m| usize::try_from(m).unwrap() > self.current_length) - { - return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds)); - } - - let dst = usize::try_from(dst).unwrap(); - let src = usize::try_from(src).unwrap(); - - // Bounds and casts are checked above, by this point we know that - // everything is safe. - let dst = self.base.add(dst); - let src = self.base.add(src); - ptr::copy(src, dst, len as usize); - - Ok(()) + .map_or(true, |m| usize::try_from(m).unwrap() > mem.current_length) + { + return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds)); } - /// Perform the `memory.fill` operation for the memory in an unsynchronized, - /// non-atomic way. - /// - /// # Errors - /// - /// Returns a `Trap` error if the memory range is out of bounds. - /// - /// # Safety - /// The memory is not filled atomically and is not synchronized: it's the - /// caller's responsibility to synchronize. - pub(crate) unsafe fn memory_fill(&self, dst: u32, val: u32, len: u32) -> Result<(), Trap> { - if dst - .checked_add(len) - .map_or(true, |m| usize::try_from(m).unwrap() > self.current_length) - { - return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds)); - } + let dst = usize::try_from(dst).unwrap(); + let src = usize::try_from(src).unwrap(); - let dst = isize::try_from(dst).unwrap(); - let val = val as u8; + // Bounds and casts are checked above, by this point we know that + // everything is safe. + let dst = mem.base.add(dst); + let src = mem.base.add(src); + ptr::copy(src, dst, len as usize); - // Bounds and casts are checked above, by this point we know that - // everything is safe. - let dst = self.base.offset(dst); - ptr::write_bytes(dst, val, len as usize); + Ok(()) +} - Ok(()) +/// Perform the `memory.fill` operation for the memory in an unsynchronized, +/// non-atomic way. +/// +/// # Errors +/// +/// Returns a `Trap` error if the memory range is out of bounds. +/// +/// # Safety +/// The memory is not filled atomically and is not synchronized: it's the +/// caller's responsibility to synchronize. +pub(crate) unsafe fn memory_fill( + mem: &VMMemoryDefinition, + dst: u32, + val: u32, + len: u32, +) -> Result<(), Trap> { + if dst + .checked_add(len) + .map_or(true, |m| usize::try_from(m).unwrap() > mem.current_length) + { + return Err(Trap::lib(TrapCode::HeapAccessOutOfBounds)); } -} -#[cfg(test)] -mod test_vmmemory_definition { - use super::VMMemoryDefinition; - use crate::VMOffsets; - use memoffset::offset_of; - use std::mem::size_of; - use wasmer_types::ModuleInfo; + let dst = isize::try_from(dst).unwrap(); + let val = val as u8; - #[test] - fn check_vmmemory_definition_offsets() { - let module = ModuleInfo::new(); - let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module); - assert_eq!( - size_of::(), - usize::from(offsets.size_of_vmmemory_definition()) - ); - assert_eq!( - offset_of!(VMMemoryDefinition, base), - usize::from(offsets.vmmemory_definition_base()) - ); - assert_eq!( - offset_of!(VMMemoryDefinition, current_length), - usize::from(offsets.vmmemory_definition_current_length()) - ); - } + // Bounds and casts are checked above, by this point we know that + // everything is safe. + let dst = mem.base.offset(dst); + ptr::write_bytes(dst, val, len as usize); + + Ok(()) } /// The fields compiled code needs to access to utilize a WebAssembly table @@ -720,3 +677,54 @@ pub type VMTrampoline = unsafe extern "C" fn( *const VMFunctionBody, // function we're actually calling *mut RawValue, // space for arguments and return values ); + +/// The fields compiled code needs to access to utilize a WebAssembly linear +/// memory defined within the instance, namely the start address and the +/// size in bytes. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct VMMemoryDefinition { + /// The start address which is always valid, even if the memory grows. + pub base: *mut u8, + + /// The current logical size of this linear memory in bytes. + pub current_length: usize, +} + +/// # Safety +/// This data is safe to share between threads because it's plain data that +/// is the user's responsibility to synchronize. +unsafe impl Send for VMMemoryDefinition {} +/// # Safety +/// This data is safe to share between threads because it's plain data that +/// is the user's responsibility to synchronize. And it's `Copy` so there's +/// really no difference between passing it by reference or by value as far as +/// correctness in a multi-threaded context is concerned. +unsafe impl Sync for VMMemoryDefinition {} + +#[cfg(test)] +mod test_vmmemory_definition { + use super::VMMemoryDefinition; + use crate::ModuleInfo; + use crate::VMOffsets; + use memoffset::offset_of; + use std::mem::size_of; + + #[test] + fn check_vmmemory_definition_offsets() { + let module = ModuleInfo::new(); + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module); + assert_eq!( + size_of::(), + usize::from(offsets.size_of_vmmemory_definition()) + ); + assert_eq!( + offset_of!(VMMemoryDefinition, base), + usize::from(offsets.vmmemory_definition_base()) + ); + assert_eq!( + offset_of!(VMMemoryDefinition, current_length), + usize::from(offsets.vmmemory_definition_current_length()) + ); + } +}