safeatomic/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![expect(missing_docs)]
5#![no_std]
6// UNSAFETY: Manual pointer manipulation and transmutes to/from atomic types.
7#![expect(unsafe_code)]
8#![expect(clippy::undocumented_unsafe_blocks)]
9
10use core::mem;
11use core::sync::atomic;
12use core::sync::atomic::AtomicU8;
13use zerocopy::FromBytes;
14use zerocopy::Immutable;
15use zerocopy::IntoBytes;
16use zerocopy::KnownLayout;
17
18/// A helper trait for types that can be safely transmuted to and from byte
19/// slices.
20pub trait AsAtomicBytes: IntoBytes + FromBytes + Immutable + KnownLayout {
21    /// Casts the type to a slice of atomic bytes.
22    fn as_atomic_bytes(&mut self) -> &[AtomicU8] {
23        // SAFETY: IntoBytes guarantees that Self can be cast to a byte slice.
24        // And since we have exclusive ownership of self, it should be safe to
25        // cast to an atomic byte slice (which can then be used by multiple
26        // threads safely).
27        // FromBytes guarantees that any value then assigned to these bytes
28        // is still valid.
29        unsafe {
30            core::slice::from_raw_parts_mut(
31                core::ptr::from_mut(self).cast::<AtomicU8>(),
32                size_of_val(self),
33            )
34        }
35    }
36}
37
38impl<T> AsAtomicBytes for T where T: IntoBytes + FromBytes + ?Sized + Immutable + KnownLayout {}
39
40/// Marker trait for atomic primitives.
41///
42/// # Safety
43///
44/// Must only be implemented for types under [`core::sync::atomic`]
45pub unsafe trait Atomic {}
46
47// SAFETY: This type is under core::sync::atomic
48unsafe impl Atomic for AtomicU8 {}
49// SAFETY: This type is under core::sync::atomic
50unsafe impl Atomic for atomic::AtomicU16 {}
51// SAFETY: This type is under core::sync::atomic
52unsafe impl Atomic for atomic::AtomicU32 {}
53// SAFETY: This type is under core::sync::atomic
54unsafe impl Atomic for atomic::AtomicU64 {}
55// SAFETY: This type is under core::sync::atomic
56unsafe impl Atomic for atomic::AtomicI8 {}
57// SAFETY: This type is under core::sync::atomic
58unsafe impl Atomic for atomic::AtomicI16 {}
59// SAFETY: This type is under core::sync::atomic
60unsafe impl Atomic for atomic::AtomicI32 {}
61// SAFETY: This type is under core::sync::atomic
62unsafe impl Atomic for atomic::AtomicI64 {}
63
64pub trait AtomicSliceOps {
65    /// # Safety
66    /// The caller must ensure that `dest..dest+len` is a
67    /// [valid](core::ptr#safety) target for writes.
68    unsafe fn atomic_read_ptr(&self, dest: *mut u8, len: usize);
69
70    /// # Safety
71    /// The caller must ensure that `src..src+len` is a [valid](core::ptr#safety) source for reads.
72    unsafe fn atomic_write_ptr(&self, src: *const u8, len: usize);
73
74    /// Reads from the slice into `dest`.
75    ///
76    /// Panics if the slice is not the same size as `dest`.
77    fn atomic_read(&self, dest: &mut [u8]) {
78        // SAFETY: `dest` is a valid target for writes.
79        unsafe { self.atomic_read_ptr(dest.as_mut_ptr(), dest.len()) }
80    }
81
82    /// Reads an object from the slice.
83    ///
84    /// Panics if the slice is not the same size as `T`.
85    fn atomic_read_obj<T: FromBytes + Immutable + KnownLayout>(&self) -> T {
86        let mut obj = mem::MaybeUninit::<T>::uninit();
87        // SAFETY: `obj` is a valid target for writes, and will be initialized by
88        // `atomic_read_ptr`.
89        unsafe {
90            self.atomic_read_ptr(obj.as_mut_ptr().cast::<u8>(), size_of::<T>());
91            obj.assume_init()
92        }
93    }
94
95    /// Writes `src` to the slice.
96    ///
97    /// Panics if the slice is not the same size as `src`.
98    fn atomic_write(&self, src: &[u8]) {
99        // SAFETY: `src` is a valid source for reads.
100        unsafe { self.atomic_write_ptr(src.as_ptr(), src.len()) }
101    }
102
103    /// Writes an object to the slice.
104    ///
105    /// Panics if the slice is not the same size as `T`.
106    fn atomic_write_obj<T: IntoBytes + Immutable + KnownLayout>(&self, obj: &T) {
107        self.atomic_write(obj.as_bytes());
108    }
109
110    /// Fills the slice with `value`.
111    fn atomic_fill(&self, value: u8);
112
113    fn as_atomic<T: Atomic>(&self) -> Option<&T>;
114    fn as_atomic_slice<T: Atomic>(&self) -> Option<&[T]>;
115}
116
117impl AtomicSliceOps for [AtomicU8] {
118    unsafe fn atomic_read_ptr(&self, dest: *mut u8, len: usize) {
119        assert_eq!(
120            self.len(),
121            len,
122            "destination and source slices have different lengths"
123        );
124        // BUGBUG: this is undefined behavior, because
125        // copy_nonoverlapping technically relies on there being no concurrent
126        // mutator of `src`, and there may be here--consider whether calling
127        // memcpy directly might be safer.
128        unsafe { core::ptr::copy_nonoverlapping(self.as_ptr().cast::<u8>(), dest, len) }
129    }
130
131    unsafe fn atomic_write_ptr(&self, src: *const u8, len: usize) {
132        assert_eq!(
133            self.len(),
134            len,
135            "destination and source slices have different lengths"
136        );
137        // BUGBUG: this is undefined behavior, because
138        // copy_nonoverlapping technically relies on there being no other
139        // concurrent mutator of `dst`, and there may be here--consider whether
140        // calling memcpy directly might be safer.
141        unsafe { core::ptr::copy_nonoverlapping(src, self.as_ptr() as *mut u8, len) }
142    }
143
144    fn atomic_fill(&self, value: u8) {
145        // BUGBUG: this is undefined behavior, because write_bytes
146        // technically relies on there being no other concurrent accessor of
147        // `dst`, and there may be here--consider whether calling memset might
148        // be safer.
149        unsafe { core::ptr::write_bytes(self.as_ptr() as *mut u8, value, self.len()) }
150    }
151
152    fn as_atomic<T: Atomic>(&self) -> Option<&T> {
153        // SAFETY: Per https://github.com/rust-lang/unsafe-code-guidelines/issues/345
154        // it *should* be fine to have mixed-size atomic accesses so long as we
155        // don't do more than 16 bytes at a time. Our largest supported type is
156        // 8 bytes.
157        let (a, b, c) = unsafe { self.align_to() };
158        if a.is_empty() && b.len() == 1 && c.is_empty() {
159            Some(&b[0])
160        } else {
161            None
162        }
163    }
164
165    fn as_atomic_slice<T: Atomic>(&self) -> Option<&[T]> {
166        // SAFETY: Per https://github.com/rust-lang/unsafe-code-guidelines/issues/345
167        // it *should* be fine to have mixed-size atomic accesses so long as we
168        // don't do more than 16 bytes at a time. Our largest supported type is
169        // 8 bytes.
170        let (a, b, c) = unsafe { self.align_to() };
171        if a.is_empty() && c.is_empty() {
172            Some(b)
173        } else {
174            None
175        }
176    }
177}