mycelium_util/sync/
cell.rs

1//! A variant of [`core::cell::UnsafeCell`] specialized for use in
2//! implementations of synchronization primitives.
3//!
4//! When the `cfg(loom)` flag is enabled, the [`UnsafeCell`] and [`Cell`] types
5//! in this module are re-exports of [`loom`]'s [checked
6//! `UnsafeCell`][loom-unsafecell] and [checked `Cell`][loom-cell] types. When
7//! Loom is not enabled, [`UnsafeCell`] is a wrapper around
8//! [`core::cell::UnsafeCell`] that implements the [`loom::cell::UnsafeCell`
9//! interface][loom-unsafecell], and `Cell` is a re-export of [`core::cell::Cell`].
10//!
11//! [`loom`]: https:://crates.io/crates/loom
12//! [loom-unsafecell]: https://docs.rs/loom/latest/loom/cell/struct.UnsafeCell.html
13
14pub use self::unsafe_cell::*;
15
16#[cfg(loom)]
17pub use loom::cell::Cell;
18
19#[cfg(not(loom))]
20pub use core::cell::Cell;
21
22#[cfg(loom)]
23mod unsafe_cell {
24    pub use loom::cell::{ConstPtr, MutPtr, UnsafeCell};
25}
26
27#[cfg(not(loom))]
28mod unsafe_cell {
29    #![allow(dead_code)]
30    use core::cell;
31
32    /// A variant of [`core::cell::UnsafeCell`] that may be checked when
33    /// [Loom] model checking is enabled.
34    ///
35    /// This type is similar to [`core::cell::UnsafeCell`], except when the
36    /// `cfg(loom)` cfg flag is enabled, it is replaced with a variant that
37    /// participates in [Loom] model checking. See [`loom::cell::UnsafeCell`]
38    /// for details on this.
39    ///
40    /// When `cfg(loom)` is *not* set, this type is essentially a
41    /// [`core::cell::UnsafeCell`], but with an API that matches that of the
42    /// checked Loom cell.
43    ///
44    /// Instead of providing a `get()` API, this version of `UnsafeCell` provides
45    /// [`with`] and [`with_mut`]. Both functions take a closure in order to track the
46    /// start and end of the access to the underlying cell.
47    ///
48    /// [Loom]: https:://crates.io/crates/loom
49    /// [`loom::cell::UnsafeCell`]: https://docs.rs/loom/latest/loom/cell/struct.UnsafeCell.html
50    /// [`with`]: Self::with
51    /// [`with_mut`]: Self::with_mut
52    #[derive(Debug)]
53    pub struct UnsafeCell<T> {
54        data: cell::UnsafeCell<T>,
55    }
56
57    /// An immutable raw pointer to an [`UnsafeCell`] that may be checked when
58    /// [Loom] model checking is enabled.
59    ///
60    /// This type is essentially a [`*const T`], but with the added ability to
61    /// participate in Loom's [`UnsafeCell`] access tracking when the
62    /// `cfg(loom)` cfg flag is set. While a `ConstPtr` to a given
63    /// [`UnsafeCell`] exists, Loom will track that the [`UnsafeCell`] is
64    /// being accessed immutably.
65    ///
66    /// When `cfg(loom)` is *not* set, this type is equivalent to a normal
67    /// `*const T`.
68    ///
69    /// [`ConstPtr`]s are produced by the [`UnsafeCell::get`] method. The pointed
70    /// value can be accessed using [`ConstPtr::deref`].
71    ///
72    /// Any number of [`ConstPtr`]s may concurrently access a given [`UnsafeCell`].
73    /// However, if the [`UnsafeCell`] is accessed mutably (by
74    /// [`UnsafeCell::with_mut`] or [`UnsafeCell::get_mut`]) while a [`ConstPtr`]
75    /// exists, Loom will detect the concurrent mutable and immutable accesses and
76    /// panic.
77    ///
78    /// Note that the cell is considered to be immutably accessed for *the entire
79    /// lifespan of the `ConstPtr`*, not just when the `ConstPtr` is actively
80    /// dereferenced.
81    ///
82    /// # Safety
83    ///
84    /// Although the `ConstPtr` type is checked for concurrent access violations, it
85    /// is **still a raw pointer**. A `ConstPtr` is not bound to the lifetime of the
86    /// [`UnsafeCell`] from which it was produced, and may outlive the cell. Loom
87    /// does *not* currently check for dangling pointers. Therefore, the user is
88    /// responsible for ensuring that a `ConstPtr` does not dangle. However, unlike
89    /// a normal `*const T`, `ConstPtr`s may only be produced from a valid
90    /// [`UnsafeCell`], and therefore can be assumed to never be null.
91    ///
92    /// Additionally, it is possible to write code in which raw pointers to an
93    /// [`UnsafeCell`] are constructed that are *not* checked by Loom. If a raw
94    /// pointer "escapes" Loom's tracking, invalid accesses may not be detected,
95    /// resulting in tests passing when they should have failed. See [here] for
96    /// details on how to avoid accidentally escaping the model.
97    ///
98    /// [`*const T`]: https://doc.rust-lang.org/stable/std/primitive.pointer.html
99    /// [here]: #correct-usage
100    /// [Loom]: https:://crates.io/crates/loom
101    #[derive(Debug)]
102    #[repr(transparent)]
103    pub struct ConstPtr<T: ?Sized>(*const T);
104
105    /// A mutable raw pointer to an [`UnsafeCell`] that may be checked when
106    /// [Loom] model checking is enabled.
107    ///
108    /// This type is essentially a [`*mut T`], but with the added ability to
109    /// participate in Loom's [`UnsafeCell`] access tracking when the
110    /// `cfg(loom)` cfg flag is set. While a `MutPtr` to a given [`UnsafeCell`]
111    /// exists, Loom will track that the [`UnsafeCell`] is
112    /// being accessed mutably.
113    ///
114    /// When `cfg(loom)` is *not* set, this type is equivalent to a normal
115    /// `*mut T`.
116    ///
117    /// [`MutPtr`]s are produced by the [`UnsafeCell::get_mut`] method. The pointed
118    /// value can be accessed using [`MutPtr::deref`].
119    ///
120    /// If an [`UnsafeCell`] is accessed mutably (by [`UnsafeCell::with_mut`] or
121    /// [`UnsafeCell::get_mut`]) or immutably (by [`UnsafeCell::with`] or
122    /// [`UnsafeCell::get`]) while a [`MutPtr`] to that cell exists, Loom will
123    /// detect the invalid accesses and panic.
124    ///
125    /// Note that the cell is considered to be mutably accessed for *the entire
126    /// lifespan of the `MutPtr`*, not just when the `MutPtr` is actively
127    /// dereferenced.
128    ///
129    /// # Safety
130    ///
131    /// Although the `MutPtr` type is checked for concurrent access violations, it
132    /// is **still a raw pointer**. A `MutPtr` is not bound to the lifetime of the
133    /// [`UnsafeCell`] from which it was produced, and may outlive the cell. Loom
134    /// does *not* currently check for dangling pointers. Therefore, the user is
135    /// responsible for ensuring that a `MutPtr` does not dangle. However, unlike
136    /// a normal `*mut T`, `MutPtr`s may only be produced from a valid
137    /// [`UnsafeCell`], and therefore can be assumed to never be null.
138    ///
139    /// Additionally, it is possible to write code in which raw pointers to an
140    /// [`UnsafeCell`] are constructed that are *not* checked by Loom. If a raw
141    /// pointer "escapes" Loom's tracking, invalid accesses may not be detected,
142    /// resulting in tests passing when they should have failed. See [here] for
143    /// details on how to avoid accidentally escaping the model.
144    ///
145    /// [`*mut T`]: https://doc.rust-lang.org/stable/std/primitive.pointer.html
146    /// [here]: #correct-usage
147    /// [Loom]: https:://crates.io/crates/loom
148    #[derive(Debug)]
149    #[repr(transparent)]
150    pub struct MutPtr<T: ?Sized>(*mut T);
151
152    impl<T> UnsafeCell<T> {
153        /// Construct a new instance of `UnsafeCell` which will wrap the specified
154        /// value.
155        pub const fn new(data: T) -> Self {
156            Self {
157                data: cell::UnsafeCell::new(data),
158            }
159        }
160
161        /// Get an immutable pointer to the wrapped value.
162        ///
163        /// # Panics
164        ///
165        /// When running under `loom`, this function will panic if the access is
166        /// not valid under the Rust memory model.
167        pub fn with<F, R>(&self, f: F) -> R
168        where
169            F: FnOnce(*const T) -> R,
170        {
171            f(self.data.get() as *const _)
172        }
173
174        /// Get a mutable pointer to the wrapped value.
175        ///
176        /// # Panics
177        ///
178        /// When running under `loom`, this function will panic if the access is
179        /// not valid under the Rust memory model.
180        pub fn with_mut<F, R>(&self, f: F) -> R
181        where
182            F: FnOnce(*mut T) -> R,
183        {
184            f(self.data.get())
185        }
186
187        /// Get an immutable pointer to the wrapped value.
188        pub(crate) fn with_unchecked<F, R>(&self, f: F) -> R
189        where
190            F: FnOnce(*const T) -> R,
191        {
192            f(self.data.get())
193        }
194
195        /// Get a mutable pointer to the wrapped value.
196        pub(crate) fn with_mut_unchecked<F, R>(&self, f: F) -> R
197        where
198            F: FnOnce(*mut T) -> R,
199        {
200            f(self.data.get())
201        }
202
203        /// Get an immutable pointer to the wrapped value.
204        ///
205        /// This function returns a [`ConstPtr`] guard, which is analogous to a
206        /// `*const T`, but tracked by Loom when the `cfg(loom)` cfg flag is
207        /// enabled. As long as the returned `ConstPtr` exists, Loom will
208        /// consider the cell to be accessed immutably.
209        ///
210        /// This means that any mutable accesses (e.g. calls to [`with_mut`] or
211        /// [`get_mut`]) while the returned guard is live will result in a panic.
212        ///
213        /// # Panics
214        ///
215        /// This function will panic if the access is not valid under the Rust memory
216        /// model, if `cfg(loom)` is enabled.
217        ///
218        /// [`with_mut`]: UnsafeCell::with_mut
219        /// [`get_mut`]: UnsafeCell::get_mut
220        #[inline(always)]
221        #[must_use]
222        pub fn get(&self) -> ConstPtr<T> {
223            ConstPtr(self.data.get())
224        }
225
226        /// Get a mutable pointer to the wrapped value.
227        ///
228        /// This function returns a [`MutPtr`] guard, which is analogous to a
229        /// `*mut T`, but tracked by Loom when the `cfg(loom)` cfg flag is
230        /// enabled. As long as the returned `MutPtr`  exists, Loom will
231        /// consider the cell to be accessed mutably.
232        ///
233        /// This means that any concurrent mutable or immutable accesses (e.g. calls
234        /// to [`with`], [`with_mut`], [`get`], or [`get_mut`]) while the returned
235        /// guard is live will result in a panic.
236        ///
237        /// # Panics
238        ///
239        /// This function will panic if the access is not valid under the Rust memory
240        /// model, if `cfg(loom)` is enabled.
241        ///
242        /// [`with`]: UnsafeCell::with
243        /// [`with_mut`]: UnsafeCell::with_mut
244        /// [`get`]: UnsafeCell::get
245        /// [`get_mut`]: UnsafeCell::get_mut
246        #[inline(always)]
247        #[must_use]
248        pub fn get_mut(&self) -> MutPtr<T> {
249            MutPtr(self.data.get())
250        }
251    }
252
253    // === impl MutPtr ===
254
255    impl<T: ?Sized> MutPtr<T> {
256        /// Dereference the raw pointer.
257        ///
258        /// # Safety
259        ///
260        /// This is equivalent to dereferencing a `*mut T` pointer, so all the same
261        /// safety considerations apply here.
262        ///
263        /// Because the `MutPtr` type can only be created by calling
264        /// [`UnsafeCell::get_mut`] on a valid `UnsafeCell`, we know the pointer
265        /// will never be null.
266        ///
267        /// Loom tracks whether the value contained in the [`UnsafeCell`] from which
268        /// this pointer originated is being concurrently accessed, and will panic
269        /// if a data race could occur. However, `loom` does _not_ track liveness
270        /// --- the [`UnsafeCell`] this pointer points to may have been dropped.
271        /// Therefore, the caller is responsible for ensuring this pointer is not
272        /// dangling.
273        ///
274        // Clippy knows that it's Bad and Wrong to construct a mutable reference
275        // from an immutable one...but this function is intended to simulate a raw
276        // pointer, so we have to do that here.
277        #[allow(clippy::mut_from_ref)]
278        #[inline(always)]
279        #[must_use]
280        pub unsafe fn deref(&self) -> &mut T {
281            &mut *self.0
282        }
283
284        /// Perform an operation with the actual value of the raw pointer.
285        ///
286        /// This may be used to call functions like [`ptr::write`], [`ptr::read]`,
287        /// and [`ptr::eq`], which are not exposed by the `MutPtr` type, cast the
288        /// pointer to an integer, et cetera.
289        ///
290        /// # Correct Usage
291        ///
292        /// Note that the raw pointer passed into the closure *must not* be moved
293        /// out of the closure, as doing so will allow it to "escape" Loom's ability
294        /// to track accesses.
295        ///
296        /// Loom considers the [`UnsafeCell`] from which this pointer originated to
297        /// be "accessed mutably" as long as the [`MutPtr`] guard exists. When the
298        /// guard is dropped, Loom considers the mutable access to have ended. This
299        /// means that if the `*mut T` passed to a `with` closure is moved _out_ of
300        /// that closure, it may outlive the guard, and thus exist past the end of
301        /// the mutable access (as understood by Loom).
302        ///
303        /// For example, code like this is incorrect:
304        ///
305        /// ```rust
306        /// use mycelium_util::sync::cell::UnsafeCell;
307        /// let cell = UnsafeCell::new(1);
308        ///
309        /// let ptr = {
310        ///     let tracked_ptr = cell.get_mut(); // tracked mutable access begins here
311        ///
312        ///      // move the real pointer out of the simulated pointer
313        ///     tracked_ptr.with(|real_ptr| real_ptr)
314        ///
315        /// }; // tracked mutable access *ends here* (when the tracked pointer is dropped)
316        ///
317        /// // now, we can mutate the value *without* loom knowing it is being mutably
318        /// // accessed! this is BAD NEWS --- if the cell was being accessed concurrently,
319        /// // loom would have failed to detect the error!
320        /// unsafe { (*ptr) = 2 }
321        /// ```
322        ///
323        /// More subtly, if a *new* pointer is constructed from the original
324        /// pointer, that pointer is not tracked by Loom, either. This might occur
325        /// when constructing a pointer to a struct field or array index. For
326        /// example, this is incorrect:
327        ///
328        /// ```rust
329        /// use mycelium_util::sync::cell::UnsafeCell;
330        ///
331        /// struct MyStruct {
332        ///     foo: usize,
333        ///     bar: usize,
334        /// }
335        ///
336        /// let my_struct = UnsafeCell::new(MyStruct { foo: 1, bar: 1});
337        ///
338        /// fn get_bar(cell: &UnsafeCell<MyStruct>) -> *mut usize {
339        ///     let tracked_ptr = cell.get_mut(); // tracked mutable access begins here
340        ///
341        ///     tracked_ptr.with(|ptr| unsafe {
342        ///         &mut (*ptr).bar as *mut usize
343        ///     })
344        /// } // tracked mutable access ends here, when `tracked_ptr` is dropped
345        ///
346        ///
347        /// // now, a pointer to `mystruct.bar` exists that Loom is not aware of!
348        /// // if we were to mutate `mystruct.bar` through this pointer while another
349        /// // thread was accessing `mystruct` concurrently, Loom would fail to detect
350        /// /// this.
351        /// let ptr_to_bar = get_bar(&my_struct);
352        /// ```
353        ///
354        /// Similarly, constructing pointers via pointer math (such as [`offset`])
355        /// may also escape Loom's ability to track accesses.
356        ///
357        /// Finally, the raw pointer passed to the `with` closure may only be passed
358        /// into function calls that don't take ownership of that pointer past the
359        /// end of the function call. Therefore, code like this is okay:
360        ///
361        /// ```rust
362        /// use mycelium_util::sync::cell::UnsafeCell;
363        ///
364        /// let cell = UnsafeCell::new(1);
365        ///
366        /// let ptr = cell.get_mut();
367        /// let value_in_cell = ptr.with(|ptr| unsafe {
368        ///     // This is fine, because `ptr::write` does not retain ownership of
369        ///     // the pointer after when the function call returns.
370        ///     core::ptr::write(ptr, 2)
371        /// });
372        /// ```
373        ///
374        /// But code like *this* is not okay:
375        ///
376        /// ```rust
377        /// use mycelium_util::sync::cell::UnsafeCell;
378        /// use core::sync::atomic::{AtomicPtr, Ordering};
379        ///
380        /// static SOME_IMPORTANT_POINTER: AtomicPtr<usize> = AtomicPtr::new(core::ptr::null_mut());
381        ///
382        /// fn mess_with_important_pointer(cell: &UnsafeCell<usize>) {
383        ///     cell.get_mut() // mutable access begins here
384        ///        .with(|ptr| {
385        ///             SOME_IMPORTANT_POINTER.store(ptr, Ordering::SeqCst);
386        ///         })
387        /// } // mutable access ends here
388        ///
389        /// // loom doesn't know that the cell can still be accessed via the `AtomicPtr`!
390        /// ```
391        ///
392        /// [`ptr::write`]: core::ptr::write
393        /// [`ptr::read`]: core::ptr::read
394        /// [`ptr::eq`]: core::ptr::eq
395        /// [`offset`]: https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.offset
396        #[inline(always)]
397        pub fn with<F, R>(&self, f: F) -> R
398        where
399            F: FnOnce(*mut T) -> R,
400        {
401            f(self.0)
402        }
403    }
404
405    // === impl ConstPtr ===
406
407    impl<T: ?Sized> ConstPtr<T> {
408        /// Dereference the raw pointer.
409        ///
410        /// # Safety
411        ///
412        /// This is equivalent to dereferencing a `*const T` pointer, so all the
413        /// same safety considerations apply here.
414        ///
415        ///
416        /// Because the `ConstPtr` type can only be created by calling
417        /// [`UnsafeCell::get_mut`] on a valid `UnsafeCell`, we know the pointer
418        /// will never be null.
419        ///
420        /// Loom tracks whether the value contained in the [`UnsafeCell`] from which
421        /// this pointer originated is being concurrently accessed, and will panic
422        /// if a data race could occur. However, `loom` does _not_ track liveness
423        /// --- the [`UnsafeCell`] this pointer points to may have been dropped.
424        /// Therefore, the caller is responsible for ensuring this pointer is not
425        /// dangling.
426        ///
427        #[inline(always)]
428        #[must_use]
429        pub unsafe fn deref(&self) -> &T {
430            &*self.0
431        }
432
433        /// Perform an operation with the actual value of the raw pointer.
434        ///
435        /// This may be used to call functions like [`ptr::read]` and [`ptr::eq`],
436        /// which are not exposed by the `ConstPtr` type, cast the pointer to an
437        /// integer, et cetera.
438        ///
439        /// # Correct Usage
440        ///
441        /// Note that the raw pointer passed into the closure *must not* be moved
442        /// out of the closure, as doing so will allow it to "escape" Loom's ability
443        /// to track accesses.
444        ///
445        /// Loom considers the [`UnsafeCell`] from which this pointer originated to
446        /// be "accessed immutably" as long as the [`ConstPtr`] guard exists. When the
447        /// guard is dropped, Loom considers the immutable access to have ended. This
448        /// means that if the `*const T` passed to a `with` closure is moved _out_ of
449        /// that closure, it may outlive the guard, and thus exist past the end of
450        /// the access (as understood by Loom).
451        ///
452        /// For example, code like this is incorrect:
453        ///
454        /// ```rust
455        /// use mycelium_util::sync::cell::UnsafeCell;
456        /// let cell = UnsafeCell::new(1);
457        ///
458        /// let ptr = {
459        ///     let tracked_ptr = cell.get(); // tracked immutable access begins here
460        ///
461        ///      // move the real pointer out of the simulated pointer
462        ///     tracked_ptr.with(|real_ptr| real_ptr)
463        ///
464        /// }; // tracked immutable access *ends here* (when the tracked pointer is dropped)
465        ///
466        /// // now, another thread can mutate the value *without* loom knowing it is being
467        /// // accessed concurrently by this thread! this is BAD NEWS ---  loom would have
468        /// // failed to detect a potential data race!
469        /// unsafe { println!("{}", (*ptr)) }
470        /// ```
471        ///
472        /// More subtly, if a *new* pointer is constructed from the original
473        /// pointer, that pointer is not tracked by Loom, either. This might occur
474        /// when constructing a pointer to a struct field or array index. For
475        /// example, this is incorrect:
476        ///
477        /// ```rust
478        /// use mycelium_util::sync::cell::UnsafeCell;
479        ///
480        /// struct MyStruct {
481        ///     foo: usize,
482        ///     bar: usize,
483        /// }
484        ///
485        /// let my_struct = UnsafeCell::new(MyStruct { foo: 1, bar: 1});
486        ///
487        /// fn get_bar(cell: &UnsafeCell<MyStruct>) -> *const usize {
488        ///     let tracked_ptr = cell.get(); // tracked immutable access begins here
489        ///
490        ///     tracked_ptr.with(|ptr| unsafe {
491        ///         &(*ptr).bar as *const usize
492        ///     })
493        /// } // tracked access ends here, when `tracked_ptr` is dropped
494        ///
495        ///
496        /// // now, a pointer to `mystruct.bar` exists that Loom is not aware of!
497        /// // if another thread were to mutate `mystruct.bar` while we are holding this
498        /// // pointer, Loom would fail to detect the data race!
499        /// let ptr_to_bar = get_bar(&my_struct);
500        /// ```
501        ///
502        /// Similarly, constructing pointers via pointer math (such as [`offset`])
503        /// may also escape Loom's ability to track accesses.
504        ///
505        /// Furthermore, the raw pointer passed to the `with` closure may only be passed
506        /// into function calls that don't take ownership of that pointer past the
507        /// end of the function call. Therefore, code like this is okay:
508        ///
509        /// ```rust
510        /// use mycelium_util::sync::cell::UnsafeCell;
511        ///
512        /// let cell = UnsafeCell::new(1);
513        ///
514        /// let ptr = cell.get();
515        /// let value_in_cell = ptr.with(|ptr| unsafe {
516        ///     // This is fine, because `ptr::read` does not retain ownership of
517        ///     // the pointer after when the function call returns.
518        ///     core::ptr::read(ptr)
519        /// });
520        /// ```
521        ///
522        /// But code like *this* is not okay:
523        ///
524        /// ```rust
525        /// use mycelium_util::sync::cell::UnsafeCell;
526        /// use core::ptr;
527        ///
528        /// struct ListNode<T> {
529        ///    value: *const T,
530        ///    next: *const ListNode<T>,
531        /// }
532        ///
533        /// impl<T> ListNode<T> {
534        ///     fn new(value: *const T) -> Box<Self> {
535        ///         Box::new(ListNode {
536        ///             value, // the pointer is moved into the `ListNode`, which will outlive this function!
537        ///             next: ptr::null::<ListNode<T>>(),
538        ///         })
539        ///     }
540        /// }
541        ///
542        /// let cell = UnsafeCell::new(1);
543        ///
544        /// let ptr = cell.get(); // immutable access begins here
545        ///
546        /// // the pointer passed into `ListNode::new` will outlive the function call
547        /// let node = ptr.with(|ptr| ListNode::new(ptr));
548        ///
549        /// drop(ptr); // immutable access ends here
550        ///
551        /// // loom doesn't know that the cell can still be accessed via the `ListNode`!
552        /// ```
553        ///
554        /// Finally, the `*const T` passed to `with` should *not* be cast to an
555        /// `*mut T`. This will permit untracked mutable access, as Loom only tracks
556        /// the existence of a `ConstPtr` as representing an immutable access.
557        ///
558        /// [`ptr::read`]: core::ptr::read
559        /// [`ptr::eq`]: core::ptr::eq
560        /// [`offset`]: https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.offset
561        #[inline(always)]
562        pub fn with<F, R>(&self, f: F) -> R
563        where
564            F: FnOnce(*const T) -> R,
565        {
566            f(self.0)
567        }
568    }
569}