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}