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}