10a55f804SAlex Crichton use crate::Engine; 2d3132c9dSAlex Crichton use crate::prelude::*; 390ac295eSAlex Crichton use crate::runtime::vm::memory::{LocalMemory, MmapMemory, validate_atomic_addr}; 4d3132c9dSAlex Crichton use crate::runtime::vm::parking_spot::{ParkingSpot, Waiter}; 5f881ab24SAlex Crichton use crate::runtime::vm::{self, Memory, VMMemoryDefinition, WaitResult}; 6d3132c9dSAlex Crichton use std::cell::RefCell; 7d3132c9dSAlex Crichton use std::ops::Range; 8b86b9682SAlex Crichton use std::ptr::NonNull; 9d3132c9dSAlex Crichton use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; 10d3132c9dSAlex Crichton use std::sync::{Arc, RwLock}; 11d3132c9dSAlex Crichton use std::time::{Duration, Instant}; 120a55f804SAlex Crichton use wasmtime_environ::Trap; 13d3132c9dSAlex Crichton 14d3132c9dSAlex Crichton /// For shared memory (and only for shared memory), this lock-version restricts 15d3132c9dSAlex Crichton /// access when growing the memory or checking its size. This is to conform with 16d3132c9dSAlex Crichton /// the [thread proposal]: "When `IsSharedArrayBuffer(...)` is true, the return 17d3132c9dSAlex Crichton /// value should be the result of an atomic read-modify-write of the new size to 18d3132c9dSAlex Crichton /// the internal `length` slot." 19d3132c9dSAlex Crichton /// 20d3132c9dSAlex Crichton /// [thread proposal]: 21d3132c9dSAlex Crichton /// https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md#webassemblymemoryprototypegrow 22d3132c9dSAlex Crichton #[derive(Clone)] 23d3132c9dSAlex Crichton pub struct SharedMemory(Arc<SharedMemoryInner>); 24d3132c9dSAlex Crichton 25d3132c9dSAlex Crichton struct SharedMemoryInner { 26d3132c9dSAlex Crichton memory: RwLock<LocalMemory>, 27d3132c9dSAlex Crichton spot: ParkingSpot, 28d3132c9dSAlex Crichton ty: wasmtime_environ::Memory, 29d3132c9dSAlex Crichton def: LongTermVMMemoryDefinition, 30d3132c9dSAlex Crichton } 31d3132c9dSAlex Crichton 32d3132c9dSAlex Crichton impl SharedMemory { 33d3132c9dSAlex Crichton /// Construct a new [`SharedMemory`]. new(engine: &Engine, ty: &wasmtime_environ::Memory) -> Result<Self>340a55f804SAlex Crichton pub fn new(engine: &Engine, ty: &wasmtime_environ::Memory) -> Result<Self> { 350a55f804SAlex Crichton let tunables = engine.tunables(); 36e1f50aadSAlex Crichton // Note that without a limiter being passed to `limit_new` this 37e1f50aadSAlex Crichton // `assert_ready` should never panic. 38e1f50aadSAlex Crichton let (minimum_bytes, maximum_bytes) = vm::assert_ready(Memory::limit_new(ty, None))?; 39d3132c9dSAlex Crichton let mmap_memory = MmapMemory::new(ty, tunables, minimum_bytes, maximum_bytes)?; 40*439de7fbSNick Fitzgerald let boxed: Box<dyn crate::runtime::vm::RuntimeLinearMemory> = 41*439de7fbSNick Fitzgerald try_new::<Box<_>>(mmap_memory)?; 42*439de7fbSNick Fitzgerald Self::wrap(engine, ty, LocalMemory::new(ty, tunables, boxed, None)?) 43d3132c9dSAlex Crichton } 44d3132c9dSAlex Crichton 45d3132c9dSAlex Crichton /// Wrap an existing [Memory] with the locking provided by a [SharedMemory]. wrap( engine: &Engine, ty: &wasmtime_environ::Memory, memory: LocalMemory, ) -> Result<Self>460a55f804SAlex Crichton pub fn wrap( 470a55f804SAlex Crichton engine: &Engine, 480a55f804SAlex Crichton ty: &wasmtime_environ::Memory, 490a55f804SAlex Crichton memory: LocalMemory, 500a55f804SAlex Crichton ) -> Result<Self> { 510a55f804SAlex Crichton if !engine.config().shared_memory { 520a55f804SAlex Crichton bail!( 530a55f804SAlex Crichton "shared memory support is disabled for this engine -- see `Config::shared_memory`" 540a55f804SAlex Crichton ); 550a55f804SAlex Crichton } 56d3132c9dSAlex Crichton if !ty.shared { 57d3132c9dSAlex Crichton bail!("shared memory must have a `shared` memory type"); 58d3132c9dSAlex Crichton } 59*439de7fbSNick Fitzgerald Ok(Self(try_new::<Arc<_>>(SharedMemoryInner { 60d3132c9dSAlex Crichton ty: *ty, 61d3132c9dSAlex Crichton spot: ParkingSpot::default(), 62d3132c9dSAlex Crichton def: LongTermVMMemoryDefinition(memory.vmmemory()), 63d3132c9dSAlex Crichton memory: RwLock::new(memory), 64*439de7fbSNick Fitzgerald })?)) 65d3132c9dSAlex Crichton } 66d3132c9dSAlex Crichton 67d3132c9dSAlex Crichton /// Return the memory type for this [`SharedMemory`]. ty(&self) -> &wasmtime_environ::Memory68b860c2c6SAlex Crichton pub fn ty(&self) -> &wasmtime_environ::Memory { 69b860c2c6SAlex Crichton &self.0.ty 70d3132c9dSAlex Crichton } 71d3132c9dSAlex Crichton 72d3132c9dSAlex Crichton /// Convert this shared memory into a [`Memory`]. as_memory(self) -> Memory73d3132c9dSAlex Crichton pub fn as_memory(self) -> Memory { 74d3132c9dSAlex Crichton Memory::Shared(self) 75d3132c9dSAlex Crichton } 76d3132c9dSAlex Crichton 77d3132c9dSAlex Crichton /// Return a pointer to the shared memory's [VMMemoryDefinition]. vmmemory_ptr(&self) -> NonNull<VMMemoryDefinition>78b86b9682SAlex Crichton pub fn vmmemory_ptr(&self) -> NonNull<VMMemoryDefinition> { 79b86b9682SAlex Crichton NonNull::from(&self.0.def.0) 80d3132c9dSAlex Crichton } 81d3132c9dSAlex Crichton 82d3132c9dSAlex Crichton /// Same as `RuntimeLinearMemory::grow`, except with `&self`. grow(&self, delta_pages: u64) -> Result<Option<(usize, usize)>, Error>83f881ab24SAlex Crichton pub fn grow(&self, delta_pages: u64) -> Result<Option<(usize, usize)>, Error> { 84d3132c9dSAlex Crichton let mut memory = self.0.memory.write().unwrap(); 85f881ab24SAlex Crichton // Without a limiter being passed in this shouldn't have an await point, 86f881ab24SAlex Crichton // so it should be safe to assert that it's ready. 87f881ab24SAlex Crichton let result = vm::assert_ready(memory.grow(delta_pages, None))?; 88d3132c9dSAlex Crichton if let Some((_old_size_in_bytes, new_size_in_bytes)) = result { 89d3132c9dSAlex Crichton // Store the new size to the `VMMemoryDefinition` for JIT-generated 90d3132c9dSAlex Crichton // code (and runtime functions) to access. No other code can be 91d3132c9dSAlex Crichton // growing this memory due to the write lock, but code in other 92d3132c9dSAlex Crichton // threads could have access to this shared memory and we want them 93d3132c9dSAlex Crichton // to see the most consistent version of the `current_length`; a 94d3132c9dSAlex Crichton // weaker consistency is possible if we accept them seeing an older, 95d3132c9dSAlex Crichton // smaller memory size (assumption: memory only grows) but presently 96d3132c9dSAlex Crichton // we are aiming for accuracy. 97d3132c9dSAlex Crichton // 98d3132c9dSAlex Crichton // Note that it could be possible to access a memory address that is 99d3132c9dSAlex Crichton // now-valid due to changes to the page flags in `grow` above but 100d3132c9dSAlex Crichton // beyond the `memory.size` that we are about to assign to. In these 101d3132c9dSAlex Crichton // and similar cases, discussion in the thread proposal concluded 102d3132c9dSAlex Crichton // that: "multiple accesses in one thread racing with another 103d3132c9dSAlex Crichton // thread's `memory.grow` that are in-bounds only after the grow 104d3132c9dSAlex Crichton // commits may independently succeed or trap" (see 105d3132c9dSAlex Crichton // https://github.com/WebAssembly/threads/issues/26#issuecomment-433930711). 106d3132c9dSAlex Crichton // In other words, some non-determinism is acceptable when using 107d3132c9dSAlex Crichton // `memory.size` on work being done by `memory.grow`. 108d3132c9dSAlex Crichton self.0 109d3132c9dSAlex Crichton .def 110d3132c9dSAlex Crichton .0 111d3132c9dSAlex Crichton .current_length 112d3132c9dSAlex Crichton .store(new_size_in_bytes, Ordering::SeqCst); 113d3132c9dSAlex Crichton } 114d3132c9dSAlex Crichton Ok(result) 115d3132c9dSAlex Crichton } 116d3132c9dSAlex Crichton 117d3132c9dSAlex Crichton /// Implementation of `memory.atomic.notify` for this shared memory. atomic_notify(&self, addr_index: u64, count: u32) -> Result<u32, Trap>118d3132c9dSAlex Crichton pub fn atomic_notify(&self, addr_index: u64, count: u32) -> Result<u32, Trap> { 119d3132c9dSAlex Crichton let ptr = validate_atomic_addr(&self.0.def.0, addr_index, 4, 4)?; 120d3132c9dSAlex Crichton log::trace!("memory.atomic.notify(addr={addr_index:#x}, count={count})"); 121d3132c9dSAlex Crichton let ptr = unsafe { &*ptr }; 122d3132c9dSAlex Crichton Ok(self.0.spot.notify(ptr, count)) 123d3132c9dSAlex Crichton } 124d3132c9dSAlex Crichton 125d3132c9dSAlex Crichton /// Implementation of `memory.atomic.wait32` for this shared memory. atomic_wait32( &self, addr_index: u64, expected: u32, timeout: Option<Duration>, ) -> Result<WaitResult, Trap>126d3132c9dSAlex Crichton pub fn atomic_wait32( 127d3132c9dSAlex Crichton &self, 128d3132c9dSAlex Crichton addr_index: u64, 129d3132c9dSAlex Crichton expected: u32, 130d3132c9dSAlex Crichton timeout: Option<Duration>, 131d3132c9dSAlex Crichton ) -> Result<WaitResult, Trap> { 132d3132c9dSAlex Crichton let addr = validate_atomic_addr(&self.0.def.0, addr_index, 4, 4)?; 133d3132c9dSAlex Crichton log::trace!( 134d3132c9dSAlex Crichton "memory.atomic.wait32(addr={addr_index:#x}, expected={expected}, timeout={timeout:?})" 135d3132c9dSAlex Crichton ); 136d3132c9dSAlex Crichton 137d3132c9dSAlex Crichton // SAFETY: `addr_index` was validated by `validate_atomic_addr` above. 138d3132c9dSAlex Crichton assert!(std::mem::size_of::<AtomicU32>() == 4); 139d3132c9dSAlex Crichton assert!(std::mem::align_of::<AtomicU32>() <= 4); 140d3132c9dSAlex Crichton let atomic = unsafe { AtomicU32::from_ptr(addr.cast()) }; 141d3132c9dSAlex Crichton let deadline = timeout.map(|d| Instant::now() + d); 142d3132c9dSAlex Crichton 143d3132c9dSAlex Crichton WAITER.with(|waiter| { 144d3132c9dSAlex Crichton let mut waiter = waiter.borrow_mut(); 145d3132c9dSAlex Crichton Ok(self.0.spot.wait32(atomic, expected, deadline, &mut waiter)) 146d3132c9dSAlex Crichton }) 147d3132c9dSAlex Crichton } 148d3132c9dSAlex Crichton 149d3132c9dSAlex Crichton /// Implementation of `memory.atomic.wait64` for this shared memory. atomic_wait64( &self, addr_index: u64, expected: u64, timeout: Option<Duration>, ) -> Result<WaitResult, Trap>150d3132c9dSAlex Crichton pub fn atomic_wait64( 151d3132c9dSAlex Crichton &self, 152d3132c9dSAlex Crichton addr_index: u64, 153d3132c9dSAlex Crichton expected: u64, 154d3132c9dSAlex Crichton timeout: Option<Duration>, 155d3132c9dSAlex Crichton ) -> Result<WaitResult, Trap> { 156d3132c9dSAlex Crichton let addr = validate_atomic_addr(&self.0.def.0, addr_index, 8, 8)?; 157d3132c9dSAlex Crichton log::trace!( 158d3132c9dSAlex Crichton "memory.atomic.wait64(addr={addr_index:#x}, expected={expected}, timeout={timeout:?})" 159d3132c9dSAlex Crichton ); 160d3132c9dSAlex Crichton 161d3132c9dSAlex Crichton // SAFETY: `addr_index` was validated by `validate_atomic_addr` above. 162d3132c9dSAlex Crichton assert!(std::mem::size_of::<AtomicU64>() == 8); 163d3132c9dSAlex Crichton assert!(std::mem::align_of::<AtomicU64>() <= 8); 164d3132c9dSAlex Crichton let atomic = unsafe { AtomicU64::from_ptr(addr.cast()) }; 165d3132c9dSAlex Crichton let deadline = timeout.map(|d| Instant::now() + d); 166d3132c9dSAlex Crichton 167d3132c9dSAlex Crichton WAITER.with(|waiter| { 168d3132c9dSAlex Crichton let mut waiter = waiter.borrow_mut(); 169d3132c9dSAlex Crichton Ok(self.0.spot.wait64(atomic, expected, deadline, &mut waiter)) 170d3132c9dSAlex Crichton }) 171d3132c9dSAlex Crichton } 172d3132c9dSAlex Crichton byte_size(&self) -> usize173d3132c9dSAlex Crichton pub(crate) fn byte_size(&self) -> usize { 174d3132c9dSAlex Crichton self.0.memory.read().unwrap().byte_size() 175d3132c9dSAlex Crichton } 176d3132c9dSAlex Crichton needs_init(&self) -> bool177d3132c9dSAlex Crichton pub(crate) fn needs_init(&self) -> bool { 178d3132c9dSAlex Crichton self.0.memory.read().unwrap().needs_init() 179d3132c9dSAlex Crichton } 180d3132c9dSAlex Crichton wasm_accessible(&self) -> Range<usize>181d3132c9dSAlex Crichton pub(crate) fn wasm_accessible(&self) -> Range<usize> { 182d3132c9dSAlex Crichton self.0.memory.read().unwrap().wasm_accessible() 183d3132c9dSAlex Crichton } 184d3132c9dSAlex Crichton } 185d3132c9dSAlex Crichton 186d3132c9dSAlex Crichton thread_local! { 187d3132c9dSAlex Crichton /// Structure used in conjunction with `ParkingSpot` to block the current 188d3132c9dSAlex Crichton /// thread if necessary. Note that this is lazily initialized. 189d3132c9dSAlex Crichton static WAITER: RefCell<Waiter> = const { RefCell::new(Waiter::new()) }; 190d3132c9dSAlex Crichton } 191d3132c9dSAlex Crichton 192d3132c9dSAlex Crichton /// Shared memory needs some representation of a `VMMemoryDefinition` for 193d3132c9dSAlex Crichton /// JIT-generated code to access. This structure owns the base pointer and 194d3132c9dSAlex Crichton /// length to the actual memory and we share this definition across threads by: 195d3132c9dSAlex Crichton /// - never changing the base pointer; according to the specification, shared 196d3132c9dSAlex Crichton /// memory must be created with a known maximum size so it can be allocated 197d3132c9dSAlex Crichton /// once and never moved 198d3132c9dSAlex Crichton /// - carefully changing the length, using atomic accesses in both the runtime 199d3132c9dSAlex Crichton /// and JIT-generated code. 200d3132c9dSAlex Crichton struct LongTermVMMemoryDefinition(VMMemoryDefinition); 201d3132c9dSAlex Crichton unsafe impl Send for LongTermVMMemoryDefinition {} 202d3132c9dSAlex Crichton unsafe impl Sync for LongTermVMMemoryDefinition {} 203