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