Skip to content

Commit

Permalink
Added helper functions for WaitNotify opcodes (for #3155)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptitSeb committed Sep 6, 2022
1 parent 667672e commit 3ec9996
Show file tree
Hide file tree
Showing 5 changed files with 470 additions and 6 deletions.
24 changes: 24 additions & 0 deletions lib/types/src/libcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ pub enum LibCall {
/// probe for stack overflow. These are emitted for functions which need
/// when the `enable_probestack` setting is true.
Probestack,

/// memory.atomic.wait32 for local memories
Memory32AtomicWait32,

/// memory.atomic.wait32 for imported memories
ImportedMemory32AtomicWait32,

/// memory.atomic.wait64 for local memories
Memory32AtomicWait64,

/// memory.atomic.wait64 for imported memories
ImportedMemory32AtomicWait64,

/// memory.atomic.notify for local memories
Memory32AtomicNotify,

/// memory.atomic.botify for imported memories
ImportedMemory32AtomicNotify,
}

impl LibCall {
Expand Down Expand Up @@ -157,6 +175,12 @@ impl LibCall {
Self::Probestack => "_wasmer_vm_probestack",
#[cfg(not(target_vendor = "apple"))]
Self::Probestack => "wasmer_vm_probestack",
Self::Memory32AtomicWait32 => "wasmer_vm_memory32_atomic_wait32",
Self::ImportedMemory32AtomicWait32 => "wasmer_vm_imported_memory32_atomic_wait32",
Self::Memory32AtomicWait64 => "wasmer_vm_memory32_atomic_wait64",
Self::ImportedMemory32AtomicWait64 => "wasmer_vm_imported_memory32_atomic_wait64",
Self::Memory32AtomicNotify => "wasmer_vm_memory32_atomic_notify",
Self::ImportedMemory32AtomicNotify => "wasmer_vm_imported_memory32_atomic_notify",
}
}
}
Expand Down
26 changes: 25 additions & 1 deletion lib/types/src/vmoffsets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,33 @@ impl VMBuiltinFunctionIndex {
pub const fn get_table_fill_index() -> Self {
Self(23)
}
/// Returns an index for wasm's local `memory.atomic.wait32` builtin function.
pub const fn get_memory_atomic_wait32_index() -> Self {
Self(24)
}
/// Returns an index for wasm's imported `memory.atomic.wait32` builtin function.
pub const fn get_imported_memory_atomic_wait32_index() -> Self {
Self(25)
}
/// Returns an index for wasm's local `memory.atomic.wait64` builtin function.
pub const fn get_memory_atomic_wait64_index() -> Self {
Self(26)
}
/// Returns an index for wasm's imported `memory.atomic.wait64` builtin function.
pub const fn get_imported_memory_atomic_wait64_index() -> Self {
Self(27)
}
/// Returns an index for wasm's local `memory.atomic.notify` builtin function.
pub const fn get_memory_atomic_notify_index() -> Self {
Self(28)
}
/// Returns an index for wasm's imported `memory.atomic.notify` builtin function.
pub const fn get_imported_memory_atomic_notify_index() -> Self {
Self(29)
}
/// Returns the total number of builtin functions.
pub const fn builtin_functions_total_number() -> u32 {
24
30
}

/// Return the index as an u32 number.
Expand Down
205 changes: 200 additions & 5 deletions lib/vm/src/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ use crate::store::{InternalStoreHandle, StoreObjects};
use crate::table::TableElement;
use crate::trap::{catch_traps, Trap, TrapCode};
use crate::vmcontext::{
memory_copy, memory_fill, VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext,
VMFunctionContext, VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport,
VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport,
VMTrampoline,
memory32_atomic_check32, memory32_atomic_check64, memory_copy, memory_fill,
VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionContext,
VMFunctionImport, VMFunctionKind, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition,
VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline,
};
use crate::LinearMemory;
use crate::{FunctionBodyPtr, MaybeInstanceOwned, TrapHandlerFn, VMFunctionBody};
Expand All @@ -33,7 +33,8 @@ use std::fmt;
use std::mem;
use std::ptr::{self, NonNull};
use std::slice;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::thread::{current, park, park_timeout, Thread};
use wasmer_types::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap};
use wasmer_types::{
DataIndex, DataInitializer, ElemIndex, ExportIndex, FunctionIndex, GlobalIndex, GlobalInit,
Expand All @@ -48,6 +49,7 @@ use wasmer_types::{
/// to ensure that the `vmctx` field is last. See the documentation of
/// the `vmctx` field to learn more.
#[repr(C)]
#[allow(clippy::type_complexity)]
pub(crate) struct Instance {
/// The `ModuleInfo` this `Instance` was instantiated from.
module: Arc<ModuleInfo>,
Expand Down Expand Up @@ -89,6 +91,9 @@ pub(crate) struct Instance {
/// will point to elements here for functions imported by this instance.
imported_funcrefs: BoxedSlice<FunctionIndex, NonNull<VMCallerCheckedAnyfunc>>,

/// The Hasmap with the Notify for the Notify/wait opcodes
conditions: Arc<Mutex<HashMap<(u32, u32), Vec<(Thread, bool)>>>>,

/// Additional context used by compiled WebAssembly code. This
/// field is last, and represents a dynamically-sized array that
/// extends beyond the nominal end of the struct (similar to a
Expand Down Expand Up @@ -768,6 +773,195 @@ impl Instance {
self.imported_table(table_index).handle
}
}

// To implement Wait / Notify, a HasMap, behind a mutex, will be used
// to track the address of waiter. The key of the hashmap is based on the memory
// and waiter threads are "park"'d (with or without timeout)
// Notify will wake the waiters by simply "unpark" the thread
// as the Thread info is stored on the HashMap
// once unparked, the waiter thread will remove it's mark on the HashMap
// timeout / awake is tracked with a boolean in the HashMap
// because `park_timeout` doesn't gives any information on why it returns
fn do_wait(&mut self, index: u32, dst: u32, timeout: i64) -> u32 {
// fetch the notifier
let key = (index, dst);
let mut conds = self.conditions.lock().unwrap();
conds.entry(key).or_insert_with(Vec::new);
let v = conds.get_mut(&key).unwrap();
v.push((current(), false));
drop(conds);
if timeout < 0 {
park();
} else {
park_timeout(std::time::Duration::from_nanos(timeout as u64));
}
let mut conds = self.conditions.lock().unwrap();
let v = conds.get_mut(&key).unwrap();
let id = current().id();
let mut ret = 0;
v.retain(|cond| {
if cond.0.id() == id {
ret = if cond.1 { 0 } else { 2 };
false
} else {
true
}
});
if v.is_empty() {
conds.remove(&key);
}
ret
}

/// Perform an Atomic.Wait32
pub(crate) fn local_memory_wait32(
&mut self,
memory_index: LocalMemoryIndex,
dst: u32,
val: u32,
timeout: i64,
) -> Result<u32, Trap> {
let memory = self.memory(memory_index);
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

let ret = unsafe { memory32_atomic_check32(&memory, dst, val) };

if let Ok(mut ret) = ret {
if ret == 0 {
ret = self.do_wait(memory_index.as_u32(), dst, timeout);
}
Ok(ret)
} else {
ret
}
}

/// Perform an Atomic.Wait32
pub(crate) fn imported_memory_wait32(
&mut self,
memory_index: MemoryIndex,
dst: u32,
val: u32,
timeout: i64,
) -> Result<u32, Trap> {
let import = self.imported_memory(memory_index);
let memory = unsafe { import.definition.as_ref() };
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

let ret = unsafe { memory32_atomic_check32(memory, dst, val) };

if let Ok(mut ret) = ret {
if ret == 0 {
ret = self.do_wait(memory_index.as_u32(), dst, timeout);
}
Ok(ret)
} else {
ret
}
}

/// Perform an Atomic.Wait64
pub(crate) fn local_memory_wait64(
&mut self,
memory_index: LocalMemoryIndex,
dst: u32,
val: u64,
timeout: i64,
) -> Result<u32, Trap> {
let memory = self.memory(memory_index);
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

let ret = unsafe { memory32_atomic_check64(&memory, dst, val) };

if let Ok(mut ret) = ret {
if ret == 0 {
ret = self.do_wait(memory_index.as_u32(), dst, timeout);
}
Ok(ret)
} else {
ret
}
}

/// Perform an Atomic.Wait64
pub(crate) fn imported_memory_wait64(
&mut self,
memory_index: MemoryIndex,
dst: u32,
val: u64,
timeout: i64,
) -> Result<u32, Trap> {
let import = self.imported_memory(memory_index);
let memory = unsafe { import.definition.as_ref() };
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

let ret = unsafe { memory32_atomic_check64(memory, dst, val) };

if let Ok(mut ret) = ret {
if ret == 0 {
ret = self.do_wait(memory_index.as_u32(), dst, timeout);
}
Ok(ret)
} else {
ret
}
}

/// Perform an Atomic.Notify
pub(crate) fn local_memory_notify(
&mut self,
memory_index: LocalMemoryIndex,
dst: u32,
) -> Result<(), Trap> {
//let memory = self.memory(memory_index);
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

// fetch the notifier
let key = (memory_index.as_u32(), dst);
let mut conds = self.conditions.lock().unwrap();
if conds.contains_key(&key) {
let v = conds.get_mut(&key).unwrap();
for (t, b) in v {
*b = true; // mark as was waiked up
t.unpark(); // wakeup!
}
}
Ok(())
}
/// Perform an Atomic.Notify
pub(crate) fn imported_memory_notify(
&mut self,
memory_index: MemoryIndex,
dst: u32,
) -> Result<(), Trap> {
//let import = self.imported_memory(memory_index);
//let memory = unsafe { import.definition.as_ref() };
//if ! memory.shared {
// We should trap according to spec, but official test rely on not trapping...
//}

// fetch the notifier
let key = (memory_index.as_u32(), dst);
let mut conds = self.conditions.lock().unwrap();
if conds.contains_key(&key) {
let v = conds.get_mut(&key).unwrap();
for (t, b) in v {
*b = true; // mark as was waiked up
t.unpark(); // wakeup!
}
}
Ok(())
}
}

/// A handle holding an `Instance` of a WebAssembly module.
Expand Down Expand Up @@ -860,6 +1054,7 @@ impl InstanceHandle {
funcrefs,
imported_funcrefs,
vmctx: VMContext {},
conditions: Arc::new(Mutex::new(HashMap::new())),
};

let mut instance_handle = allocator.write_instance(instance);
Expand Down
Loading

0 comments on commit 3ec9996

Please sign in to comment.