Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreading, full networking and RPC for WebAssembly #3116

Merged
merged 4 commits into from
Aug 25, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implemented shared memory for Wasmer in preparation for multithreading
john-sharratt committed Aug 18, 2022
commit 301540c04271f8bc0458a095aaf7391517a0bdaf
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.

6 changes: 6 additions & 0 deletions lib/api/src/js/export.rs
Original file line number Diff line number Diff line change
@@ -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<VMMemory> {
Some(self.clone())
}
}

#[derive(Clone, Debug, PartialEq)]
35 changes: 18 additions & 17 deletions lib/api/src/js/externals/memory.rs
Original file line number Diff line number Diff line change
@@ -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<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
@@ -210,6 +205,12 @@ impl Memory {
}
}

/// Attempts to clone this memory (if its clonable)
pub fn try_clone(&self, store: &impl AsStoreRef) -> Option<VMMemory> {
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()
7 changes: 6 additions & 1 deletion lib/api/src/js/function_env.rs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could also go to 3.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is a easy one to bring in

T: Any + Send + 'static + Sized,
{
@@ -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()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that could go to 3.0 too, I suppose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah if we move the other ones we might as well

/// Borrows a new mutable reference
pub fn as_mut<'a>(&'a mut self) -> FunctionEnvMut<'a, T> {
FunctionEnvMut {
30 changes: 30 additions & 0 deletions lib/api/src/js/imports.rs
Original file line number Diff line number Diff line change
@@ -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 {
6 changes: 6 additions & 0 deletions lib/api/src/js/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
1 change: 0 additions & 1 deletion lib/api/src/js/native.rs
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 5 additions & 0 deletions lib/api/src/js/store.rs
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 15 additions & 1 deletion lib/api/src/sys/externals/memory.rs
Original file line number Diff line number Diff line change
@@ -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<VMMemory> {
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())
}
2 changes: 1 addition & 1 deletion lib/api/src/sys/externals/memory_view.rs
Original file line number Diff line number Diff line change
@@ -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;
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::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<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, memory.clone().into()));
Some(memory)
} else {
None
}
}

/// Returns the contents of a namespace as an `Exports`.
///
/// Returns `None` if the namespace doesn't exist.
2 changes: 1 addition & 1 deletion lib/api/src/sys/mod.rs
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ pub mod vm {

pub use wasmer_vm::{
MemoryError, MemoryStyle, TableStyle, VMExtern, VMMemory, VMMemoryDefinition, VMTable,
VMTableDefinition,
VMTableDefinition, VMOwnedMemory, VMSharedMemory
};
}

3 changes: 2 additions & 1 deletion lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
@@ -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());
31 changes: 25 additions & 6 deletions lib/compiler-cranelift/src/translator/code_translator.rs
Original file line number Diff line number Diff line change
@@ -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)?
2 changes: 1 addition & 1 deletion lib/compiler/src/engine/resolver.rs
Original file line number Diff line number Diff line change
@@ -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::{
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;
@@ -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(())
}
3 changes: 3 additions & 0 deletions lib/types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 = []
Loading