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