diff --git a/Cargo.lock b/Cargo.lock index 9328944bd83..04df81af3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3194,6 +3194,7 @@ dependencies = [ "enum-iterator", "enumset", "indexmap", + "memoffset", "more-asserts", "rkyv", "serde", diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index 6c5229ce92f..a1f4b8aa878 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" { @@ -116,6 +100,17 @@ impl Memory { Ok(Self::from_vm_export(store, vm_memory)) } + /// 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); + Ok(Self::from_vm_export(store, vm_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 { + Self::from_vm_export(new_store, memory) + } + /// Returns the [`MemoryType`] of the `Memory`. /// /// # Example 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..d3b2c42efa5 100644 --- a/lib/api/src/js/imports.rs +++ b/lib/api/src/js/imports.rs @@ -174,6 +174,36 @@ 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/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..64ab9a136e2 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -10,7 +10,7 @@ use std::mem::MaybeUninit; use std::slice; #[cfg(feature = "tracing")] use tracing::warn; -use wasmer_types::Pages; +use wasmer_types::{Pages, LinearMemory}; use wasmer_vm::{InternalStoreHandle, MemoryError, StoreHandle, VMExtern, VMMemory}; use super::MemoryView; @@ -60,6 +60,13 @@ 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 { + Self { + handle: StoreHandle::new(new_store.objects_mut(), memory) + } + } + /// Returns the [`MemoryType`] of the `Memory`. /// /// # Example @@ -142,6 +149,13 @@ 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..a638acb6b97 100644 --- a/lib/api/src/sys/externals/memory_view.rs +++ b/lib/api/src/sys/externals/memory_view.rs @@ -4,7 +4,7 @@ use std::convert::TryInto; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::slice; -use wasmer_types::Pages; +use wasmer_types::{Pages, LinearMemory}; use super::memory::MemoryBuffer; use super::Memory; diff --git a/lib/api/src/sys/imports.rs b/lib/api/src/sys/imports.rs index c9a9f22b917..80e51f7367d 100644 --- a/lib/api/src/sys/imports.rs +++ b/lib/api/src/sys/imports.rs @@ -1,11 +1,12 @@ //! The import module contains the implementation data structures and helper functions used to //! manipulate and access a wasm module's imports including memories, tables, globals, and //! functions. -use crate::{Exports, Extern, Module}; +use crate::{Exports, Extern, Module, AsStoreMut, Memory}; use std::collections::HashMap; use std::fmt; use wasmer_compiler::LinkError; use wasmer_types::ImportError; +use wasmer_vm::VMSharedMemory; /// All of the import data used when instantiating. /// @@ -111,6 +112,32 @@ impl Imports { .insert((ns.to_string(), name.to_string()), val.into()); } + /// Imports (any) shared memory into the imports. + /// (if the module does not import memory then this function is ignored) + pub fn import_shared_memory(&mut self, module: &Module, store: &mut impl AsStoreMut) -> Option { + // Determine if shared memory needs to be created and imported + let shared_memory = module + .imports() + .memories() + .next() + .map(|a| *a.ty()) + .map(|ty| { + let style = store + .as_store_ref() + .tunables() + .memory_style(&ty); + VMSharedMemory::new(&ty, &style) + .unwrap() + }); + + if let Some(memory) = shared_memory { + self.define("env", "memory", Memory::new_from_existing(store, memory.clone().into())); + Some(memory) + } else { + None + } + } + /// Returns the contents of a namespace as an `Exports`. /// /// Returns `None` if the namespace doesn't exist. diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 2fd31666bed..af971543696 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -96,7 +96,8 @@ impl Wasi { is_wasix_module(module), std::sync::atomic::Ordering::Release, ); - let import_object = import_object_for_all_wasi_versions(store, &wasi_env.env); + let mut import_object = import_object_for_all_wasi_versions(store, &wasi_env.env); + import_object.import_shared_memory(module, store); let instance = Instance::new(store, module, &import_object)?; let memory = instance.exports.get_memory("memory")?; wasi_env.data_mut(store).set_memory(memory.clone()); diff --git a/lib/compiler-cranelift/src/translator/code_translator.rs b/lib/compiler-cranelift/src/translator/code_translator.rs index c750d6c3231..d432f074361 100644 --- a/lib/compiler-cranelift/src/translator/code_translator.rs +++ b/lib/compiler-cranelift/src/translator/code_translator.rs @@ -1063,15 +1063,24 @@ pub fn translate_operator( assert!(builder.func.dfg.value_type(expected) == implied_ty); // `fn translate_atomic_wait` can inspect the type of `expected` to figure out what // code it needs to generate, if it wants. - let res = environ.translate_atomic_wait( + match environ.translate_atomic_wait( builder.cursor(), heap_index, heap, addr, expected, timeout, - )?; - state.push1(res); + ) { + Ok(res) => { + state.push1(res); + }, + Err(wasmer_types::WasmError::Unsupported(_err)) => { + // If multiple threads hit a mutex then the function will fail + builder.ins().trap(ir::TrapCode::UnreachableCodeReached); + state.reachable = false; + }, + Err(err) => { return Err(err); } + }; } Operator::MemoryAtomicNotify { memarg } => { let heap_index = MemoryIndex::from_u32(memarg.memory); @@ -1079,9 +1088,19 @@ pub fn translate_operator( let count = state.pop1(); // 32 (fixed) let addr = state.pop1(); // 32 (fixed) let addr = fold_atomic_mem_addr(addr, memarg, I32, builder); - let res = - environ.translate_atomic_notify(builder.cursor(), heap_index, heap, addr, count)?; - state.push1(res); + match environ.translate_atomic_notify(builder.cursor(), heap_index, heap, addr, count) + { + Ok(res) => { + state.push1(res); + }, + Err(wasmer_types::WasmError::Unsupported(_err)) => { + // Simple return a zero as this function is needed for the __wasi_init_memory function + // but the equivalent notify.wait will not be called (as only one thread calls __start) + // hence these atomic operations are not needed + state.push1(builder.ins().iconst(I32, i64::from(0))); + }, + Err(err) => { return Err(err); } + }; } Operator::I32AtomicLoad { memarg } => { translate_atomic_load(I32, I32, memarg, builder, state, environ)? diff --git a/lib/compiler/src/engine/resolver.rs b/lib/compiler/src/engine/resolver.rs index 207380bb819..94d40a50229 100644 --- a/lib/compiler/src/engine/resolver.rs +++ b/lib/compiler/src/engine/resolver.rs @@ -4,7 +4,7 @@ use crate::LinkError; use more_asserts::assert_ge; use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap}; use wasmer_types::{ - ExternType, FunctionIndex, ImportError, ImportIndex, MemoryIndex, ModuleInfo, TableIndex, + ExternType, FunctionIndex, ImportError, ImportIndex, MemoryIndex, ModuleInfo, TableIndex, LinearMemory, }; use wasmer_vm::{ diff --git a/lib/compiler/src/translator/environ.rs b/lib/compiler/src/translator/environ.rs index e172d92b063..1615b87bcf8 100644 --- a/lib/compiler/src/translator/environ.rs +++ b/lib/compiler/src/translator/environ.rs @@ -1,7 +1,6 @@ // This file contains code from external sources. // Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md use super::state::ModuleTranslationState; -use crate::lib::std::borrow::ToOwned; use crate::lib::std::string::ToString; use crate::lib::std::{boxed::Box, string::String, vec::Vec}; use crate::translate_module; @@ -15,7 +14,7 @@ use wasmer_types::{ LocalFunctionIndex, MemoryIndex, MemoryType, ModuleInfo, SignatureIndex, TableIndex, TableInitializer, TableType, }; -use wasmer_types::{WasmError, WasmResult}; +use wasmer_types::WasmResult; /// Contains function data: bytecode and its offset in the module. #[derive(Hash)] @@ -254,11 +253,6 @@ impl<'data> ModuleEnvironment<'data> { } pub(crate) fn declare_memory(&mut self, memory: MemoryType) -> WasmResult<()> { - if memory.shared { - return Err(WasmError::Unsupported( - "shared memories are not supported yet".to_owned(), - )); - } self.module.memories.push(memory); Ok(()) } 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..3f71079c94c 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -77,7 +77,7 @@ pub use crate::compilation::target::{ pub use crate::serialize::{MetadataHeader, SerializableCompilation, SerializableModule}; pub use error::{ CompileError, DeserializeError, ImportError, MiddlewareError, ParseCpuFeatureError, - PreInstantiationError, SerializeError, WasmError, WasmResult, + PreInstantiationError, SerializeError, WasmError, WasmResult, MemoryError, }; /// The entity module, with common helpers for Rust structures @@ -103,7 +103,9 @@ pub use types::{ pub use value::{RawValue, ValueType}; pub use crate::libcalls::LibCall; -pub use crate::memory::MemoryStyle; +pub use crate::memory::{ + MemoryStyle, LinearMemory, VMMemoryDefinition +}; pub use crate::table::TableStyle; pub use crate::trapcode::TrapCode; pub use crate::vmoffsets::{TargetSharedSignatureIndex, VMBuiltinFunctionIndex, VMOffsets}; diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index fafcb955a8a..6ebac15e7e8 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -2,12 +2,16 @@ use crate::{Pages, ValueType}; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; +use core::ptr::NonNull; use std::convert::{TryFrom, TryInto}; use std::iter::Sum; use std::ops::{Add, AddAssign}; +use super::MemoryType; +use super::MemoryError; + /// 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 +88,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 +105,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 +121,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 } @@ -120,3 +129,80 @@ unsafe impl MemorySize for Memory64 { native as Self::Offset } } + +/// 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>; +} + +/// 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::VMOffsets; + use memoffset::offset_of; + use std::mem::size_of; + use crate::ModuleInfo; + + #[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()) + ); + } +} diff --git a/lib/vm/src/instance/allocator.rs b/lib/vm/src/instance/allocator.rs index 29804c7460e..e00625f5e8c 100644 --- a/lib/vm/src/instance/allocator.rs +++ b/lib/vm/src/instance/allocator.rs @@ -1,11 +1,11 @@ use super::{Instance, InstanceHandle}; -use crate::vmcontext::{VMMemoryDefinition, VMTableDefinition}; +use crate::vmcontext::VMTableDefinition; use std::alloc::{self, Layout}; use std::convert::TryFrom; use std::mem; use std::ptr::{self, NonNull}; use wasmer_types::entity::EntityRef; -use wasmer_types::VMOffsets; +use wasmer_types::{VMOffsets, VMMemoryDefinition}; use wasmer_types::{LocalMemoryIndex, LocalTableIndex, ModuleInfo}; /// This is an intermediate type that manages the raw allocation and diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index bf0be8a295b..ab11b2b5501 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -10,14 +10,13 @@ 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, - VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, + VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, + VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, memory_copy, memory_fill, }; use crate::{FunctionBodyPtr, MaybeInstanceOwned, TrapHandlerFn, VMFunctionBody}; use crate::{VMFuncRef, VMFunction, VMGlobal, VMMemory, VMTable}; @@ -37,7 +36,8 @@ use wasmer_types::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, use wasmer_types::{ DataIndex, DataInitializer, ElemIndex, ExportIndex, FunctionIndex, GlobalIndex, GlobalInit, LocalFunctionIndex, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, - ModuleInfo, Pages, SignatureIndex, TableIndex, TableInitializer, VMOffsets, + ModuleInfo, Pages, SignatureIndex, TableIndex, TableInitializer, VMOffsets, LinearMemory, + MemoryError, VMMemoryDefinition }; /// 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..9b9903c0e80 100644 --- a/lib/vm/src/lib.rs +++ b/lib/vm/src/lib.rs @@ -45,7 +45,8 @@ 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::{VMMemory, VMOwnedMemory, VMSharedMemory}; +pub use wasmer_types::MemoryError; pub use crate::mmap::Mmap; pub use crate::probestack::PROBESTACK; pub use crate::sig_registry::SignatureRegistry; @@ -56,11 +57,11 @@ pub use crate::table::{TableElement, VMTable}; pub use crate::trap::*; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, VMFunctionContext, - VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, + VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, }; pub use wasmer_types::LibCall; -pub use wasmer_types::MemoryStyle; +pub use wasmer_types::{MemoryStyle, VMMemoryDefinition}; use wasmer_types::RawValue; pub use wasmer_types::TableStyle; pub use wasmer_types::{TargetSharedSignatureIndex, VMBuiltinFunctionIndex, VMOffsets}; diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index 787d5f390b5..d67ec52c4f4 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -5,88 +5,173 @@ //! //! `Memory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. -use crate::vmcontext::VMMemoryDefinition; use crate::{mmap::Mmap, store::MaybeInstanceOwned}; 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 std::sync::{RwLock, Arc}; +use wasmer_types::{Bytes, MemoryStyle, MemoryType, Pages, MemoryError, LinearMemory, VMMemoryDefinition}; + +// 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 { } + +/// A shared linear memory instance. +#[derive(Debug, Clone)] +pub struct VMSharedMemory { + // The underlying allocation. + mmap: Arc>, + // Configuration of this memory + config: VMMemoryConfig, +} + +unsafe impl Send for VMSharedMemory { } +unsafe impl Sync for VMSharedMemory { } + +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 +239,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 +258,257 @@ impl VMMemory { current_length: mem_length, }))) }, - memory: *memory, - style: style.clone(), + alloc, + size: memory.minimum, + }; + + Ok(Self { + mmap: mmap, + config: VMMemoryConfig { + maximum: memory.maximum, + offset_guard_size: offset_guard_bytes, + memory: *memory, + style: style.clone(), + } }) } +} - /// Get the `VMMemoryDefinition`. - fn get_vm_memory_definition(&self) -> NonNull { - self.vm_memory_definition.as_ptr() +impl VMOwnedMemory +{ + /// Converts this owned memory into shared memory + pub fn to_shared(self) -> VMSharedMemory + { + VMSharedMemory { + mmap: Arc::new(RwLock::new(self.mmap)), + config: self.config + } } +} +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; + fn ty(&self) -> MemoryType { + let minimum = self.mmap.size(); + self.config.ty(minimum) + } - out + /// Returns the size of hte memory in pages + fn size(&self) -> Pages { + self.mmap.size() } /// Returns the memory style for this memory. - pub fn style(&self) -> &MemoryStyle { - &self.style + fn style(&self) -> MemoryStyle { + self.config.style() } - /// 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() - } + /// 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.mmap.grow(delta, self.config.clone()) + } + + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. + fn vmmemory(&self) -> NonNull { + self.mmap.vm_memory_definition.as_ptr() + } + + /// Owned memory can not be cloned (this will always return None) + fn try_clone(&self) -> Option> { + None + } +} + +impl Into +for VMOwnedMemory +{ + fn into(self) -> VMMemory { + VMMemory(Box::new(self)) + } +} + +impl VMSharedMemory +{ + /// 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 + /// that will be imported into Wasm modules. + pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result { + Ok( + VMOwnedMemory::new(memory, style)?.to_shared() + ) + } + + /// 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( + VMOwnedMemory::from_definition(memory, style, vm_memory_location)?.to_shared() + ) + } +} + +impl LinearMemory +for VMSharedMemory +{ + /// Returns the type for this memory. + fn ty(&self) -> MemoryType { + let minimum = { + let guard = self.mmap.read().unwrap(); + guard.size() + }; + self.config.ty(minimum) + } + + /// Returns the size of hte memory in pages + fn size(&self) -> Pages { + let guard = self.mmap.read().unwrap(); + guard.size() + } + + /// 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 { + let mut guard = self.mmap.write().unwrap(); + guard.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 { + let guard = self.mmap.read().unwrap(); + guard.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, - }); - } - } + /// Shared memory can always be cloned + fn try_clone(&self) -> Option> { + Some(Box::new(self.clone())) + } +} - // 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 Into +for VMSharedMemory +{ + fn into(self) -> VMMemory { + VMMemory(Box::new(self)) + } +} - 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(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 Into +for Box +{ + fn into(self) -> VMMemory { + VMMemory(self) + } +} - 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( + if memory.shared { + Self(Box::new(VMSharedMemory::new(memory, style)?)) + } else { + 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( + if memory.shared { + Self(Box::new(VMSharedMemory::from_definition(memory, style, vm_memory_location)?)) + } else { + 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 + /// - VMSharedMemory -> VMMemory + /// - Box -> VMMemory + pub fn from_custom(memory: IntoVMMemory) -> VMMemory + where IntoVMMemory: Into + { + memory.into() } } diff --git a/lib/vm/src/store.rs b/lib/vm/src/store.rs index 4faee579a4d..93ae12e5511 100644 --- a/lib/vm/src/store.rs +++ b/lib/vm/src/store.rs @@ -78,6 +78,11 @@ impl StoreObjects { 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. @@ -266,3 +271,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..6940f43a265 100644 --- a/lib/vm/src/vmcontext.rs +++ b/lib/vm/src/vmcontext.rs @@ -15,7 +15,7 @@ use crate::{VMBuiltinFunctionIndex, VMFunction}; use std::convert::TryFrom; use std::ptr::{self, NonNull}; use std::u32; -use wasmer_types::RawValue; +use wasmer_types::{RawValue, VMMemoryDefinition}; /// Union representing the first parameter passed when calling a function. /// @@ -303,120 +303,67 @@ 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