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}