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}