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(&self, key: NonZeroUsize, init: impl FnOnce() -> V) -> Ref<V> {
189        let val = unsafe { self.get(key) };
190        val.unwrap_or_else(|| {
191            let val = init();
192            // SAFETY: Ensured by caller
193            unsafe { self.insert(key, val) }
194        })
195    }
196
197    /// Removes the value still for `key`, if any. Panics if this thread has
198    /// any outstanding references for `key`.
199    ///
200    /// # Safety
201    ///
202    /// The value at `key`, if any, must have been inserted by the current thread.
203    pub unsafe fn remove(&self, key: NonZeroUsize) -> Option<V> {
204        let idx = self.idx(key)?;
205        assert_eq!(
206            self.refcounts[idx].get(),
207            0,
208            "Removed key while references still held: {key:?}"
209        );
210        let value = self.values[idx].get_mut().with(|value| {
211            let value = unsafe { &mut *value };
212            unsafe { value.assume_init_read() }
213        });
214
215        // Careful not to panic between `assume_init_read` above and the `store`
216        // below; doing so would cause `value` to be dropped twice.
217
218        // Syncs with `Acquire` on insertion
219        self.keys[idx].store(None, atomic::Ordering::Release);
220        Some(value)
221    }
222
223    /// Resets metadata in the map to mark all entries vacant, without dropping
224    /// the values.
225    ///
226    /// Intended for use after `fork`, after which entries belonging to other threads
227    /// are not guaranteed to be in any consistent state (so can't be dropped), but
228    /// the threads owning those entries no longer exist in the child, so they *can*
229    /// be safely overwritten.
230    ///
231    /// # Safety
232    ///
233    /// Any outstanding references from `self` (e.g. obtained via Self::get)
234    /// must not be accessed *or dropped* again. e.g. references held by other
235    /// threads before `fork` are OK, since those threads do not exist in the
236    /// current process, and so will not access the child's copy of this table.
237    /// References that have been forgotten via `core::mem::forget` are also ok.
238    pub unsafe fn forget_all(&self) {
239        for idx in 0..N {
240            self.refcounts[idx].set(0);
241            self.keys[idx].store(None, atomic::Ordering::Release);
242        }
243    }
244}
245
246impl<const N: usize, V, H> AtomicTlsMap<N, V, H>
247where
248    H: BuildHasher + Default,
249{
250    // This `inline` is important when allocating large instances, since
251    // otherwise the compiler can't avoid create a temporary copy on the stack,
252    // which might not fit.
253    //
254    // See https://stackoverflow.com/questions/25805174/creating-a-fixed-size-array-on-heap-in-rust/68122278#68122278
255    #[inline]
256    #[allow(clippy::new_without_default)]
257    pub fn new() -> Self {
258        Self::new_with_hasher(Default::default())
259    }
260}
261
262impl<const N: usize, V, H> Drop for AtomicTlsMap<N, V, H>
263where
264    H: BuildHasher,
265{
266    fn drop(&mut self) {
267        for idx in 0..N {
268            // No special synchronization requirements here since we have a
269            // `mut` reference to self. Even values that were inserted by other
270            // threads should now be safe to access; for us to have obtained a
271            // `mut` reference some external synchronization must have occurred,
272            // which should make the values safely accessible by this thread.
273            if self.keys[idx].load(atomic::Ordering::Relaxed).is_some() {
274                self.values[idx].get_mut().with(|value| {
275                    assert_eq!(self.refcounts[idx].get(), 0);
276                    // SAFETY: We have exclusive access to `self`.
277                    let value = unsafe { &mut *value };
278                    // SAFETY: We know the value is initialized.
279                    unsafe { value.assume_init_drop() }
280                })
281            }
282        }
283    }
284}
285
286pub struct Ref<'a, V> {
287    ptr: ConstPtr<MaybeUninit<V>>,
288    refcount: &'a Cell<usize>,
289    _phantom: PhantomData<&'a V>,
290}
291static_assertions::assert_not_impl_any!(Ref<'static, ()>: Send, Sync);
292
293impl<'a, V> Ref<'a, V> {
294    /// # Safety
295    ///
296    /// Current thread must be the only one to access `refcount`
297    unsafe fn new(ptr: ConstPtr<MaybeUninit<V>>, refcount: &'a Cell<usize>) -> Self {
298        refcount.set(refcount.get() + 1);
299        Self {
300            ptr,
301            refcount,
302            _phantom: PhantomData,
303        }
304    }
305}
306
307impl<V> Deref for Ref<'_, V> {
308    type Target = V;
309
310    fn deref(&self) -> &Self::Target {
311        // SAFETY: The table ensures no mutable accesses to this value as long
312        // as `Ref`s exist.
313        let val = unsafe { self.ptr.deref() };
314        // SAFETY: The table ensures that the value is initialized before
315        // constructing this `Ref`.
316        unsafe { val.assume_init_ref() }
317    }
318}
319
320impl<V> Drop for Ref<'_, V> {
321    fn drop(&mut self) {
322        self.refcount.set(self.refcount.get() - 1)
323    }
324}