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}