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