vasi_sync/atomic_tls_map.rs
1use crate::sync::{AtomicUsize, Cell, ConstPtr, UnsafeCell, atomic};
2
3use core::hash::{BuildHasher, Hasher};
4use core::marker::PhantomData;
5use core::mem::MaybeUninit;
6use core::num::NonZeroUsize;
7use core::ops::Deref;
8
9/// A lockless, no_std, no-alloc hash table.
10///
11/// Allows insertion and removal from an immutable reference, but does not
12/// support getting mutable references to internal values, and requires that a
13/// particular key is only ever accessed from the thread that inserted it, until
14/// that thread removes it.
15///
16/// Uses linear probing, and doesn't support resizing. Lookup is `Θ(1)`
17/// (average case) if the key is present and the key hasn't been forced far away
18/// from its "home" location, but is `O(N)` worst case. Lookup of a non-present
19/// key is always `O(N)`; we need to scan the whole table.
20///
21/// This is designed mostly for use by `shadow_shim::tls` to help implement
22/// thread-local storage.
23pub struct AtomicTlsMap<const N: usize, V, H = core::hash::BuildHasherDefault<rustc_hash::FxHasher>>
24where
25 H: BuildHasher,
26{
27 keys: [AtomicOptionNonZeroUsize; N],
28 values: [UnsafeCell<MaybeUninit<V>>; N],
29 // This lets us enforce that a still-referenced key is not removed.
30 // TODO: Consider storing `RefCell<V>` in `values` instead. That'd be a bit
31 // more idiomatic, and is probably a better layout for cache performance.
32 refcounts: [Cell<usize>; N],
33 build_hasher: H,
34}
35/// Override default of `UnsafeCell`, `Cell`, and `V` not being `Sync`. We
36/// synchronize access to these (if partly by requiring users to guarantee no
37/// parallel access to a given key from multiple threads).
38/// Likewise `V` only needs to be `Send`.
39unsafe impl<const N: usize, V, H> Sync for AtomicTlsMap<N, V, H>
40where
41 // Requires for the Drop implementation to be able to drop values that were
42 // inserted by a different thread. Also if we want to support values being
43 // accessed by multiple threads with some kind of external synchronization,
44 // but I don't think we do.
45 //
46 // Alternatively we could only have this bound on the `Drop` implemenation,
47 // and document that the final contents aren't dropped if `V` isn't send. Or
48 // just remove the `Drop` impementation altogether.
49 V: Send,
50 H: Sync + BuildHasher,
51{
52}
53
54/// Adapter for `Option<NonZeroUsize>` around `AtomicUsize`
55struct AtomicOptionNonZeroUsize(AtomicUsize);
56impl AtomicOptionNonZeroUsize {
57 fn to_usize(val: Option<NonZeroUsize>) -> usize {
58 val.map(NonZeroUsize::get).unwrap_or(0)
59 }
60
61 fn from_usize(val: usize) -> Option<NonZeroUsize> {
62 NonZeroUsize::new(val)
63 }
64
65 pub fn new(val: Option<NonZeroUsize>) -> Self {
66 Self(AtomicUsize::new(Self::to_usize(val)))
67 }
68
69 pub fn load(&self, order: atomic::Ordering) -> Option<NonZeroUsize> {
70 Self::from_usize(self.0.load(order))
71 }
72
73 pub fn store(&self, val: Option<NonZeroUsize>, order: atomic::Ordering) {
74 self.0.store(Self::to_usize(val), order)
75 }
76
77 pub fn compare_exchange(
78 &self,
79 current: Option<NonZeroUsize>,
80 new: Option<NonZeroUsize>,
81 success: atomic::Ordering,
82 failure: atomic::Ordering,
83 ) -> Result<Option<NonZeroUsize>, Option<NonZeroUsize>> {
84 self.0
85 .compare_exchange(
86 Self::to_usize(current),
87 Self::to_usize(new),
88 success,
89 failure,
90 )
91 .map(Self::from_usize)
92 .map_err(Self::from_usize)
93 }
94}
95
96impl<const N: usize, V, H> AtomicTlsMap<N, V, H>
97where
98 H: BuildHasher,
99{
100 pub fn new_with_hasher(build_hasher: H) -> Self {
101 Self {
102 keys: core::array::from_fn(|_| AtomicOptionNonZeroUsize::new(None)),
103 values: core::array::from_fn(|_| UnsafeCell::new(MaybeUninit::uninit())),
104 refcounts: core::array::from_fn(|_| Cell::new(0)),
105 build_hasher,
106 }
107 }
108
109 /// All indexes starting from the hash position of `key`.
110 fn indexes_from(&self, key: NonZeroUsize) -> impl Iterator<Item = usize> {
111 let mut hasher = self.build_hasher.build_hasher();
112 hasher.write_usize(key.get());
113 let hash = hasher.finish();
114 let start_idx = usize::try_from(hash).unwrap() % N;
115 (start_idx..N).chain(0..start_idx)
116 }
117
118 /// The index containing `key`, if any. No synchronization.
119 ///
120 /// TODO: Consider keeping track of whether/where we saw vacancies along the
121 /// way in linear search, and moving the value if its refcount is currently
122 /// 0.
123 fn idx(&self, key: NonZeroUsize) -> Option<usize> {
124 self.indexes_from(key).find(|idx| {
125 // Relaxed because of requirement that only one thread ever accesses
126 // a given key at once.
127 self.keys[*idx].load(atomic::Ordering::Relaxed) == Some(key)
128 })
129 }
130
131 /// # Safety
132 ///
133 /// The value at `key`, if any, must have been inserted by the current thread.
134 #[inline]
135 pub unsafe fn get(&self, key: NonZeroUsize) -> Option<Ref<'_, V>> {
136 // SAFETY: Ensured by caller
137 let idx = self.idx(key)?;
138 let ptr = self.values[idx].get();
139 Some(unsafe { Ref::new(ptr, &self.refcounts[idx]) })
140 }
141
142 /// Insert `(key, value)`.
143 ///
144 /// If `key` is already present in `self`, the previous value would shadow
145 /// the newly inserted value. We don't expose this function in the public
146 /// API since this behavior would be confusing.
147 ///
148 /// Returns a reference to the newly inserted value.
149 ///
150 /// Panics if the table is full.
151 ///
152 /// # Safety
153 ///
154 /// There must not be a value at `key` that was inserted by a different
155 /// thread.
156 unsafe fn insert(&self, key: NonZeroUsize, value: V) -> Ref<'_, V> {
157 let idx = self
158 .indexes_from(key)
159 .find(|idx| {
160 self.keys[*idx]
161 .compare_exchange(
162 None,
163 Some(key),
164 // Syncs with `Release` on removal
165 atomic::Ordering::Acquire,
166 atomic::Ordering::Relaxed,
167 )
168 .is_ok()
169 })
170 .unwrap();
171 self.values[idx].get_mut().with(|table_value| {
172 let table_value = unsafe { &mut *table_value };
173 table_value.write(value)
174 });
175 unsafe { Ref::new(self.values[idx].get(), &self.refcounts[idx]) }
176 }
177
178 /// Retrieve the value associated with `key`, initializing it with `init` if `key`
179 /// is not already present.
180 ///
181 /// Panics if the table is full and `key` is not already present.
182 ///
183 /// # Safety
184 ///
185 /// There must not be a value at `key` that was inserted by a different
186 /// thread.
187 #[inline]
188 pub unsafe fn get_or_insert_with(
189 &self,
190 key: NonZeroUsize,
191 init: impl FnOnce() -> V,
192 ) -> Ref<'_, V> {
193 let val = unsafe { self.get(key) };
194 val.unwrap_or_else(|| {
195 let val = init();
196 // SAFETY: Ensured by caller
197 unsafe { self.insert(key, val) }
198 })
199 }
200
201 /// Removes the value still for `key`, if any. Panics if this thread has
202 /// any outstanding references for `key`.
203 ///
204 /// # Safety
205 ///
206 /// The value at `key`, if any, must have been inserted by the current thread.
207 pub unsafe fn remove(&self, key: NonZeroUsize) -> Option<V> {
208 let idx = self.idx(key)?;
209 assert_eq!(
210 self.refcounts[idx].get(),
211 0,
212 "Removed key while references still held: {key:?}"
213 );
214 let value = self.values[idx].get_mut().with(|value| {
215 let value = unsafe { &mut *value };
216 unsafe { value.assume_init_read() }
217 });
218
219 // Careful not to panic between `assume_init_read` above and the `store`
220 // below; doing so would cause `value` to be dropped twice.
221
222 // Syncs with `Acquire` on insertion
223 self.keys[idx].store(None, atomic::Ordering::Release);
224 Some(value)
225 }
226
227 /// Resets metadata in the map to mark all entries vacant, without dropping
228 /// the values.
229 ///
230 /// Intended for use after `fork`, after which entries belonging to other threads
231 /// are not guaranteed to be in any consistent state (so can't be dropped), but
232 /// the threads owning those entries no longer exist in the child, so they *can*
233 /// be safely overwritten.
234 ///
235 /// # Safety
236 ///
237 /// Any outstanding references from `self` (e.g. obtained via Self::get)
238 /// must not be accessed *or dropped* again. e.g. references held by other
239 /// threads before `fork` are OK, since those threads do not exist in the
240 /// current process, and so will not access the child's copy of this table.
241 /// References that have been forgotten via `core::mem::forget` are also ok.
242 pub unsafe fn forget_all(&self) {
243 for idx in 0..N {
244 self.refcounts[idx].set(0);
245 self.keys[idx].store(None, atomic::Ordering::Release);
246 }
247 }
248}
249
250impl<const N: usize, V, H> AtomicTlsMap<N, V, H>
251where
252 H: BuildHasher + Default,
253{
254 // This `inline` is important when allocating large instances, since
255 // otherwise the compiler can't avoid create a temporary copy on the stack,
256 // which might not fit.
257 //
258 // See https://stackoverflow.com/questions/25805174/creating-a-fixed-size-array-on-heap-in-rust/68122278#68122278
259 #[inline]
260 #[allow(clippy::new_without_default)]
261 pub fn new() -> Self {
262 Self::new_with_hasher(Default::default())
263 }
264}
265
266impl<const N: usize, V, H> Drop for AtomicTlsMap<N, V, H>
267where
268 H: BuildHasher,
269{
270 fn drop(&mut self) {
271 for idx in 0..N {
272 // No special synchronization requirements here since we have a
273 // `mut` reference to self. Even values that were inserted by other
274 // threads should now be safe to access; for us to have obtained a
275 // `mut` reference some external synchronization must have occurred,
276 // which should make the values safely accessible by this thread.
277 if self.keys[idx].load(atomic::Ordering::Relaxed).is_some() {
278 self.values[idx].get_mut().with(|value| {
279 assert_eq!(self.refcounts[idx].get(), 0);
280 // SAFETY: We have exclusive access to `self`.
281 let value = unsafe { &mut *value };
282 // SAFETY: We know the value is initialized.
283 unsafe { value.assume_init_drop() }
284 })
285 }
286 }
287 }
288}
289
290pub struct Ref<'a, V> {
291 ptr: ConstPtr<MaybeUninit<V>>,
292 refcount: &'a Cell<usize>,
293 _phantom: PhantomData<&'a V>,
294}
295static_assertions::assert_not_impl_any!(Ref<'static, ()>: Send, Sync);
296
297impl<'a, V> Ref<'a, V> {
298 /// # Safety
299 ///
300 /// Current thread must be the only one to access `refcount`
301 unsafe fn new(ptr: ConstPtr<MaybeUninit<V>>, refcount: &'a Cell<usize>) -> Self {
302 refcount.set(refcount.get() + 1);
303 Self {
304 ptr,
305 refcount,
306 _phantom: PhantomData,
307 }
308 }
309}
310
311impl<V> Deref for Ref<'_, V> {
312 type Target = V;
313
314 fn deref(&self) -> &Self::Target {
315 // SAFETY: The table ensures no mutable accesses to this value as long
316 // as `Ref`s exist.
317 let val = unsafe { self.ptr.deref() };
318 // SAFETY: The table ensures that the value is initialized before
319 // constructing this `Ref`.
320 unsafe { val.assume_init_ref() }
321 }
322}
323
324impl<V> Drop for Ref<'_, V> {
325 fn drop(&mut self) {
326 self.refcount.set(self.refcount.get() - 1)
327 }
328}