Skip to content

Commit

Permalink
Implemented shared memory for Wasmer in preparation for multithreading
Browse files Browse the repository at this point in the history
  • Loading branch information
john-sharratt committed Aug 17, 2022
1 parent 544cbe0 commit d2909a9
Show file tree
Hide file tree
Showing 22 changed files with 692 additions and 319 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 12 additions & 17 deletions lib/api/src/js/externals/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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" {
Expand Down Expand Up @@ -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<Self, MemoryError> {
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
Expand Down
7 changes: 6 additions & 1 deletion lib/api/src/js/function_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<T> FunctionEnv<T> {
}

/// 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,
{
Expand Down Expand Up @@ -112,6 +112,11 @@ impl<T: Send + 'static> FunctionEnvMut<'_, T> {
self.func_env.as_mut(&mut self.store_mut)
}

/// Borrows a new immmutable reference
pub fn as_ref(&self) -> FunctionEnv<T> {
self.func_env.clone()
}

/// Borrows a new mutable reference
pub fn as_mut<'a>(&'a mut self) -> FunctionEnvMut<'a, T> {
FunctionEnvMut {
Expand Down
30 changes: 30 additions & 0 deletions lib/api/src/js/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::Item> {
self.iter
.next()
.map(|(k, v)| {
(k.0.as_str(), k.1.as_str(), v)
})
}
}

impl IntoIterator for &Imports {
Expand Down
5 changes: 5 additions & 0 deletions lib/api/src/js/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 8 additions & 1 deletion lib/api/src/sys/externals/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/api/src/sys/externals/memory_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 28 additions & 1 deletion lib/api/src/sys/imports.rs
Original file line number Diff line number Diff line change
@@ -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::{VMMemory, VMSharedMemory};

/// All of the import data used when instantiating.
///
Expand Down Expand Up @@ -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<VMSharedMemory> {
// 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, VMMemory::from_custom(memory.clone())));
Some(memory)
} else {
None
}
}

/// Returns the contents of a namespace as an `Exports`.
///
/// Returns `None` if the namespace doesn't exist.
Expand Down
3 changes: 2 additions & 1 deletion lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
31 changes: 25 additions & 6 deletions lib/compiler-cranelift/src/translator/code_translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,25 +1063,44 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
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);
let heap = state.get_heap(builder.func, memarg.memory, environ)?;
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)?
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/src/engine/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
8 changes: 1 addition & 7 deletions lib/compiler/src/translator/environ.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -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(())
}
Expand Down
3 changes: 3 additions & 0 deletions lib/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
48 changes: 47 additions & 1 deletion lib/types/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The WebAssembly possible errors
use crate::ExternType;
use crate::{ExternType, Pages};
use std::io;
use thiserror::Error;

Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
Loading

0 comments on commit d2909a9

Please sign in to comment.