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