devela/sys/mem/bound/align/cache.rs
1// devela/src/sys/mem/bound/align/cache.rs
2//
3//! Defines the [`CacheAlign`] struct.
4//
5
6#[doc = crate::_tags!(mem)]
7/// Aligns and pads a value to the length of a cache line.
8#[doc = crate::_doc_meta!{location("sys/mem")}]
9///
10/// In concurrent programming, sometimes it is desirable to make sure commonly accessed pieces of
11/// data are not placed into the same cache line. Updating an atomic value invalidates the whole
12/// cache line it belongs to, which makes the next access to the same cache line slower for other
13/// CPU cores. Use `CacheAlign` to ensure updating one piece of data doesn't invalidate other
14/// cached data.
15///
16/// # Size and alignment
17/// Cache lines are assumed to be N bytes long, depending on the architecture:
18/// * On x86-64, aarch64, and powerpc64, N = 128.
19/// * On arm, mips, mips64, sparc, and hexagon, N = 32.
20/// * On m68k, N = 16.
21/// * On s390x, N = 256.
22/// * On all others, N = 64.
23///
24/// Note that N is just a reasonable guess and is not guaranteed to match the actual cache line
25/// length of the machine the program is running on. On modern Intel architectures, spatial
26/// prefetcher is pulling pairs of 64-byte cache lines at a time, so we pessimistically assume that
27/// cache lines are 128 bytes long.
28///
29/// The size of `CacheAlign<T>` is the smallest multiple of N bytes large enough to accommodate
30/// a value of type `T`.
31///
32/// The alignment of `CacheAlign<T>` is the maximum of N bytes and the alignment of `T`.
33///
34/// # Examples
35/// Alignment and padding:
36/// ```
37/// # use devela::CacheAlign;
38/// let array = [CacheAlign::new(1i8), CacheAlign::new(2i8)];
39/// let addr1 = &*array[0] as *const i8 as usize;
40/// let addr2 = &*array[1] as *const i8 as usize;
41///
42/// assert!(addr2 - addr1 >= 32);
43/// assert_eq!(addr1 % 32, 0);
44/// assert_eq!(addr2 % 32, 0);
45/// ```
46///
47/// When building a concurrent queue with a head and a tail index, it is wise to place them in
48/// different cache lines so that concurrent threads pushing and popping elements don't invalidate
49/// each other's cache lines:
50/// ```ignore
51/// # use devela::{CacheAlign, AtomicUsize};
52/// struct Queue<T> {
53/// head: CacheAlign<AtomicUsize>,
54/// tail: CacheAlign<AtomicUsize>,
55/// buffer: *mut T,
56/// }
57/// ```
58#[doc = crate::_doc_vendor!("crossbeam-utils")]
59//
60// Starting from Intel's Sandy Bridge, spatial prefetcher is now pulling pairs of 64-byte cache
61// lines at a time, so we have to align to 128 bytes rather than 64.
62//
63// Sources:
64// - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
65// - https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107
66//
67// aarch64/arm64ec's big.LITTLE architecture has asymmetric cores and "big" cores have 128-byte cache line size.
68//
69// Sources:
70// - https://www.mono-project.com/news/2016/09/12/arm64-icache/
71//
72// powerpc64 has 128-byte cache line size.
73//
74// Sources:
75// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_ppc64x.go#L9
76// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/powerpc/include/asm/cache.h#L26
77#[cfg_attr(
78 any(
79 target_arch = "x86_64",
80 target_arch = "aarch64",
81 target_arch = "arm64ec",
82 target_arch = "powerpc64",
83 ),
84 repr(align(128))
85)]
86// arm, mips, mips64, sparc, and hexagon have 32-byte cache line size.
87//
88// Sources:
89// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_arm.go#L7
90// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips.go#L7
91// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mipsle.go#L7
92// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_mips64x.go#L9
93// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L17
94// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/hexagon/include/asm/cache.h#L12
95#[cfg_attr(
96 any(
97 target_arch = "arm",
98 target_arch = "mips",
99 target_arch = "mips32r6",
100 target_arch = "mips64",
101 target_arch = "mips64r6",
102 target_arch = "sparc",
103 target_arch = "hexagon",
104 ),
105 repr(align(32))
106)]
107// m68k has 16-byte cache line size.
108//
109// Sources:
110// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/m68k/include/asm/cache.h#L9
111#[cfg_attr(target_arch = "m68k", repr(align(16)))]
112// s390x has 256-byte cache line size.
113//
114// Sources:
115// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_s390x.go#L7
116// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/s390/include/asm/cache.h#L13
117#[cfg_attr(target_arch = "s390x", repr(align(256)))]
118// x86, wasm, riscv, and sparc64 have 64-byte cache line size.
119//
120// Sources:
121// - https://github.com/golang/go/blob/dda2991c2ea0c5914714469c4defc2562a907230/src/internal/cpu/cpu_x86.go#L9
122// - https://github.com/golang/go/blob/3dd58676054223962cd915bb0934d1f9f489d4d2/src/internal/cpu/cpu_wasm.go#L7
123// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/riscv/include/asm/cache.h#L10
124// - https://github.com/torvalds/linux/blob/3516bd729358a2a9b090c1905bd2a3fa926e24c6/arch/sparc/include/asm/cache.h#L19
125//
126// All others are assumed to have 64-byte cache line size.
127#[cfg_attr(
128 not(any(
129 target_arch = "x86_64",
130 target_arch = "aarch64",
131 target_arch = "arm64ec",
132 target_arch = "powerpc64",
133 target_arch = "arm",
134 target_arch = "mips",
135 target_arch = "mips32r6",
136 target_arch = "mips64",
137 target_arch = "mips64r6",
138 target_arch = "sparc",
139 target_arch = "hexagon",
140 target_arch = "m68k",
141 target_arch = "s390x",
142 )),
143 repr(align(64))
144)]
145#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
146pub struct CacheAlign<T> {
147 value: T,
148}
149
150#[rustfmt::skip]
151impl<T> CacheAlign<T> {
152 /// The alignment of a cache line in the current platform.
153 pub const ALIGN: usize = align_of::<Self>();
154
155 #[must_use]
156 /// Pads and aligns a value to the length of a cache line.
157 /// ```
158 /// # use devela::CacheAlign;
159 /// let padded_value = CacheAlign::new(1);
160 /// ```
161 pub const fn new(t: T) -> CacheAlign<T> { CacheAlign::<T> { value: t } }
162
163 #[must_use]
164 /// The inner value.
165 /// ```
166 /// # use devela::CacheAlign;
167 /// let padded_value = CacheAlign::new(7);
168 /// let value = padded_value.into_inner();
169 /// assert_eq!(value, 7);
170 /// ```
171 pub fn into_inner(self) -> T { self.value }
172
173 #[must_use]
174 /// A copy of the inner value, in compile-time.
175 pub const fn into_inner_copy(self) -> T where Self: Copy { self.value }
176}
177
178#[rustfmt::skip]
179mod impls {
180 use crate::{CacheAlign, Debug, Deref, DerefMut, Display, FmtResult, Formatter};
181
182 impl<T> From<T> for CacheAlign<T> {
183 fn from(t: T) -> Self { CacheAlign::new(t) }
184 }
185 impl<T> Deref for CacheAlign<T> {
186 type Target = T;
187 fn deref(&self) -> &T { &self.value }
188 }
189 impl<T> DerefMut for CacheAlign<T> {
190 fn deref_mut(&mut self) -> &mut T { &mut self.value }
191 }
192 impl<T: Debug> Debug for CacheAlign<T> {
193 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
194 f.debug_struct("CacheAlign")
195 .field("align", &Self::ALIGN)
196 .field("value", &self.value).finish()
197 }
198 }
199 impl<T: Display> Display for CacheAlign<T> {
200 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
201 Display::fmt(&self.value, f)
202 }
203 }
204 #[crate::macro_apply(crate::__cfg_item_unsafe_show("safe_mem", "unsafe_sync"))]
205 unsafe impl<T: Send> Send for CacheAlign<T> {}
206 #[crate::macro_apply(crate::__cfg_item_unsafe_show("safe_mem", "unsafe_sync"))]
207 unsafe impl<T: Sync> Sync for CacheAlign<T> {}
208}