Skip to main content

todc_mem/register/
atomic.rs

1use std::marker::PhantomData;
2
3use crate::sync::{AtomicU64, Ordering};
4
5use super::Register;
6
7/// A shared-memory register, backed by an 64 bits of "atomic" memory.
8///
9/// This object works by serializing data and storing it in an
10/// [`AtomicU64`], and so can only be used to store small amounts
11/// of data.
12///
13/// # Atomics and Memory Ordering
14///
15/// Unfortunately, the theoretical atomic memory model in which atomic really means
16/// [_linearizable_](https://en.wikipedia.org/wiki/Linearizability),
17/// is not the same as the model used by languages such as
18/// [Rust](https://doc.rust-lang.org/nomicon/atomics.html) and
19/// [C++](https://en.cppreference.com/w/cpp/atomic/memory_order).
20/// In practice, the compiler and hardware optimizations that make life livable
21/// come at the cost of potentially re-ordering memory accesses. The strictest
22/// consistency model that we can ask for in Rust is
23/// [_sequential consistency_](https://en.wikipedia.org/wiki/Sequential_consistency),
24/// which means that all processes perform operations in a sequential order, but
25/// the relative order of operations perfomed by different processes is undefined.
26///
27/// As a result, operations performed on an `AtomicRegister` are only
28/// guaranteed to be sequentially consistent, not necessarily lineariazable.
29///
30/// Thankfully, it was recently shown by Perrin, Petrolia, Mostefaoui, and Jard
31/// [[PPM+2016]](https://arxiv.org/abs/1607.06258) that objects that would be
32/// linearizable if they were implemented on top of linearizable memory become
33/// sequentially consistent if implemented on top of sequentially consistent
34/// memory. This means that, while implemenations of linearizable algorithms
35/// from [`AtomicRegister`] objects may fail to be linearizable, they will at least
36/// be sequentially consistent, and will retain all other properties such as
37/// wait-freedom.
38///
39/// ## Linearizability
40///
41/// For a register that guarantees linearizability at the cost of lock-freedom,
42/// see [`MutexRegister`](super::mutex::MutexRegister).
43///
44/// # Examples
45///
46/// A simple spinlock.
47///
48/// ```
49/// use std::sync::Arc;
50/// use std::{hint, thread};
51/// use todc_mem::register::{AtomicRegister, Register};
52///
53///
54/// let register: Arc<AtomicRegister<u64>> = Arc::new(AtomicRegister::new());
55///
56/// let register_clone = register.clone();
57/// let thread = thread::spawn(move || {
58///     register_clone.write(1)
59/// });
60///
61/// while register.read() == 0 {
62///     hint::spin_loop();
63/// }
64///
65/// thread.join().unwrap();
66/// ```
67///
68/// Although space is limited, it is still possible to store any type that can
69/// be converted to [`u64`] and back again.
70///
71/// ```
72/// use heapless::String;
73/// use todc_mem::register::{AtomicRegister, Register};
74///
75/// // A String with a fixed capacity of 64 bits
76/// #[derive(Clone, Debug, Default, PartialEq)]
77/// struct TinyString(String<8>);
78///
79/// impl From<TinyString> for u64 {
80///     fn from(string: TinyString) -> Self {
81///         // -- snipped --
82/// #       let mut result = Self::MAX;
83/// #       let bytes = string.0.into_bytes();
84/// #       for (i, num) in bytes.iter().rev().enumerate() {
85/// #           let mut num = (*num as u64) << (i * 8);
86/// #           for j in 0..i {
87/// #               num |= (u8::MAX as u64) << (j * 8);
88/// #           }
89/// #           for k in 0..(8 - i - 1) {
90/// #               num |= ((u8::MAX as u64) << ((8 - k - 1) * 8));
91/// #           }
92/// #           result &= num;
93/// #       }
94/// #       result
95///     }
96/// }
97///
98/// impl From<u64> for TinyString {
99///     fn from(value: u64) -> Self {
100///         // -- snipped --
101/// #       let bytes: Vec<u8> = value.to_be_bytes()
102/// #           .into_iter()
103/// #           .filter(|&x| x != u8::MAX)
104/// #           .collect();
105/// #       let mut result: String<8> = String::from("");
106/// #       if let Ok(string) = std::str::from_utf8(&bytes[..]) {
107/// #           result.push_str(string);
108/// #       };  
109/// #       Self(result)
110///     }
111/// }
112///
113/// let register: AtomicRegister<TinyString> = AtomicRegister::new();
114///
115/// let empty = TinyString(String::from(""));
116/// assert_eq!(register.read(), empty);
117///
118/// let greeting = TinyString(String::from("hi"));
119/// register.write(greeting.clone());
120/// assert_eq!(register.read(), greeting);
121///
122/// let emojis = TinyString(String::from("👋🦀"));
123/// register.write(emojis.clone());
124/// assert_eq!(register.read(), emojis);
125/// ```
126pub struct AtomicRegister<T: Default + From<u64> + Into<u64>> {
127    register: AtomicU64,
128    _value_type: PhantomData<T>,
129}
130
131impl<T: Default + From<u64> + Into<u64>> Register for AtomicRegister<T> {
132    type Value = T;
133
134    /// Creates a new register containing the default value of `T`.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use todc_mem::register::{AtomicRegister, Register};
140    ///
141    /// let register: AtomicRegister<u64> = AtomicRegister::new();
142    /// assert_eq!(register.read(), u64::default());
143    /// ```
144    fn new() -> Self {
145        Self {
146            register: AtomicU64::new(T::default().into()),
147            _value_type: PhantomData,
148        }
149    }
150
151    /// Returns the value currently contained in the register.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use todc_mem::register::{AtomicRegister, Register};
157    ///
158    /// let register: AtomicRegister<u64> = AtomicRegister::new();
159    /// assert_eq!(register.read(), 0);
160    /// ```
161    fn read(&self) -> T {
162        self.register.load(Ordering::SeqCst).into()
163    }
164
165    /// Sets contents of the register to the specified value.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use todc_mem::register::{AtomicRegister, Register};
171    ///
172    /// let register: AtomicRegister<u64> = AtomicRegister::new();
173    /// register.write(42);
174    /// assert_eq!(register.read(), 42);
175    /// ```
176    fn write(&self, value: T) {
177        self.register.store(value.into(), Ordering::SeqCst)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
186    struct Pair(bool, bool);
187
188    impl From<Pair> for u64 {
189        fn from(pair: Pair) -> Self {
190            let mut result = u64::MAX;
191            let Pair(first, second) = pair;
192            result &= if first { 0b1 } else { 0b0 };
193            result &= if second { 0b11 } else { 0b01 };
194            result
195        }
196    }
197
198    impl From<u64> for Pair {
199        fn from(value: u64) -> Self {
200            let first = value & 1 != 0;
201            let second = (value & (1 << 1)) != 0;
202            Pair(first, second)
203        }
204    }
205
206    #[test]
207    fn initializes_both_to_false() {
208        let register: AtomicRegister<Pair> = AtomicRegister::new();
209        assert_eq!(Pair(false, false), register.read());
210    }
211
212    #[test]
213    fn read_returns_previously_written_value() {
214        let pair = Pair(true, false);
215        let register: AtomicRegister<Pair> = AtomicRegister::new();
216        register.write(pair);
217        assert_eq!(pair, register.read());
218    }
219}