Skip to main content

copy_channels/
atomic.rs

1//! This is an alternative implementation of everything. Its upside is that it is
2//! totally free of undefined behaviour. It has some downsides:
3//! 1. It's essentially uses a custom memcpy implementation which may be slower
4//! 2. It's UB to read T and write to some other type if T has padding bytes, so
5//!    we need to restrict the set of types.
6//!
7//! Types must be [`Copyable`] (which is an unsafe trait defined here). Easiest is to
8//! derive [`IntoBytes`] (re-published from the [`zerocopy`] crate):
9//!
10//! ```rust
11//! use copy_channels::atomic::*;
12//!
13//! #[derive(Debug, Default, Clone, Copy, IntoBytes)]
14//! struct MyValue {
15//!   x: u32,
16//!   y: u32,
17//! }
18//!
19//! let (mut sender, mut receiver) = watch::channel(MyValue::default());
20//! sender.send_replace(MyValue { x: 1, y: 2 });
21//! assert!(receiver.has_changed().unwrap());
22//! assert_eq!(receiver.get().x, 1);
23//! ```
24
25/// Marker trait for copyable types.
26///
27/// # Safety
28/// Implement this trait only for types that may safely be cast and read
29/// as a series of bytes. That means the type must not have any internal
30/// padding.
31pub unsafe trait Copyable: Copy {}
32
33// zerocopy::IntoBytes is just that, and comes with a nice derive macro
34pub use zerocopy::IntoBytes;
35unsafe impl<T: Copy + IntoBytes> Copyable for T {}
36
37use crate::loom::sync::atomic::{AtomicUsize, Ordering, Ordering::Relaxed, fence};
38use std::marker::PhantomData;
39use std::mem::{MaybeUninit, size_of};
40
41pub struct SlotItem<T> {
42    cell: AtomicUsize,
43    _t: PhantomData<T>,
44}
45
46pub type Slot<T> = [SlotItem<T>];
47
48fn allocate<T>(n_items: usize) -> Box<[SlotItem<T>]> {
49    std::iter::repeat_with(|| SlotItem {
50        cell: AtomicUsize::new(0),
51        _t: PhantomData,
52    })
53    .take(n_items)
54    .collect::<Vec<_>>()
55    .into_boxed_slice()
56}
57
58const fn required_items<T>() -> usize {
59    size_of::<T>().div_ceil(size_of::<usize>())
60}
61
62pub struct AtomicSlotable;
63use super::Slotable;
64
65impl<T: Copyable> Slotable<T> for AtomicSlotable {
66    type Slot = Slot<T>;
67    type SlotArrayItem = SlotItem<T>;
68
69    fn boxed_uninit_single() -> Box<Self::Slot> {
70        allocate::<T>(required_items::<T>())
71    }
72
73    fn read(slot: &Self::Slot, ordering: Ordering) -> std::mem::MaybeUninit<T> {
74        let n = size_of::<T>() / size_of::<usize>();
75        let mut value = MaybeUninit::<T>::uninit();
76        let tgt = value.as_mut_ptr() as *mut usize;
77        for (i, src) in slot[0..n].iter().enumerate() {
78            let x = src.cell.load(Relaxed);
79            // safety: write to locally allocated MaybeUninit
80            unsafe {
81                tgt.add(i).write_unaligned(x);
82            }
83        }
84        let remain = size_of::<T>() % size_of::<usize>();
85        if remain != 0 {
86            let part = slot[n].cell.load(Relaxed).to_ne_bytes();
87            // safety: write to locally allocated MaybeUninit
88            unsafe {
89                tgt.add(n)
90                    .cast::<u8>()
91                    .copy_from_nonoverlapping(part.as_ptr(), remain);
92            }
93        }
94        if ordering != Relaxed {
95            fence(ordering);
96        }
97        value
98    }
99
100    fn write(slot: &Self::Slot, value: T, ordering: Ordering) {
101        if ordering != Relaxed {
102            fence(ordering);
103        }
104
105        let n = size_of::<T>() / size_of::<usize>();
106        let src = &value as *const T as *const usize;
107        for (i, tgt) in slot[0..n].iter().enumerate() {
108            // safety: value contains no padding, so we can read it and write into slot
109            let x = unsafe { src.add(i).read_unaligned() };
110            tgt.cell.store(x, Relaxed);
111        }
112        let remain = size_of::<T>() % size_of::<usize>();
113        if remain != 0 {
114            let mut part = [0u8; size_of::<usize>()];
115            // safety: value contains no padding, so we can read it and write into slot
116            unsafe {
117                part.as_mut_ptr()
118                    .copy_from_nonoverlapping(src.add(n) as *const u8, remain);
119            }
120            slot[n].cell.store(usize::from_ne_bytes(part), Relaxed);
121        }
122    }
123
124    fn boxed_uninit_multiple(n: usize) -> Box<[Self::SlotArrayItem]> {
125        let req = required_items::<T>() * n;
126        allocate(req)
127    }
128
129    fn index_in_array(items: &[Self::SlotArrayItem], index: usize) -> &Self::Slot {
130        let n = required_items::<T>();
131        &items[index * n..(index + 1) * n]
132    }
133}
134
135crate::impl_channels!(crate::atomic::AtomicSlotable, crate::atomic::Copyable);
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    fn test_rw<T: Copyable + PartialEq + std::fmt::Debug>(value: T) {
142        let slot = AtomicSlotable::boxed_uninit_single();
143        AtomicSlotable::write(&slot, value, Ordering::Relaxed);
144        let readback: T = unsafe { AtomicSlotable::read(&slot, Ordering::Relaxed).assume_init() };
145        assert_eq!(readback, value);
146    }
147
148    #[test]
149    fn read_write_8() {
150        #[derive(Clone, Copy, PartialEq, Debug, IntoBytes)]
151        struct V(u64, u64, u32, u32);
152        test_rw(V(1, 2, 3, 4));
153    }
154
155    #[test]
156    fn read_write_4() {
157        #[derive(Clone, Copy, PartialEq, Debug, IntoBytes)]
158        struct V(u32, u32, u16, u16);
159        test_rw(V(1, 2, 3, 4));
160    }
161
162    #[test]
163    fn read_write_1() {
164        #[derive(Clone, Copy, PartialEq, Debug, IntoBytes)]
165        struct V(u8, u8);
166        test_rw(V(1, 2));
167    }
168}