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