Skip to main content

vortex_buffer/
alignment.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::ops::Deref;
6
7use vortex_error::VortexError;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_error::vortex_err;
12
13/// The alignment of a buffer.
14///
15/// This type is a wrapper around `usize` that ensures the alignment is a non-zero power of 2.
16#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct Alignment(usize);
18
19impl Alignment {
20    /// Default alignment for device-to-host buffer copies.
21    pub const HOST_COPY: Self = Alignment::new(256);
22
23    /// Default alignment for all buffers.
24    ///
25    /// Chosen to be larger than any SIMD register (e.g. AVX-512's 64-byte
26    /// registers) so that buffers can be processed with vectorized loads/stores
27    /// without alignment fixups, and to match the alignment guarantees of the
28    /// CUDA allocator (256 bytes) so host buffers can be copied to/from device
29    /// memory without re-alignment.
30    pub const DEFAULT_ALIGNMENT: Self = Alignment::new(256);
31
32    /// Create a new alignment.
33    ///
34    /// ## Panics
35    ///
36    /// Panics if `align` is zero or is not a power of 2.
37    #[inline]
38    pub const fn new(align: usize) -> Self {
39        assert!(align > 0, "Alignment must be greater than 0");
40        assert!(align.is_power_of_two(), "Alignment must be a power of 2");
41        Self(align)
42    }
43
44    /// Create a new 1-byte alignment.
45    #[inline]
46    pub const fn none() -> Self {
47        Self::new(1)
48    }
49
50    /// Create an alignment from the alignment of a type `T`.
51    ///
52    /// ## Example
53    ///
54    /// ```
55    /// use vortex_buffer::Alignment;
56    ///
57    /// assert_eq!(Alignment::new(4), Alignment::of::<i32>());
58    /// assert_eq!(Alignment::new(8), Alignment::of::<i64>());
59    /// assert_eq!(Alignment::new(16), Alignment::of::<u128>());
60    /// ```
61    #[inline]
62    pub const fn of<T>() -> Self {
63        Self::new(align_of::<T>())
64    }
65
66    /// The largest valid alignment: the greatest power of 2 representable in a `usize`.
67    pub const MAX: Alignment = Alignment::new(1 << (usize::BITS - 1));
68
69    /// Check if `self` alignment is a "larger" than `other` alignment.
70    ///
71    /// ## Example
72    ///
73    /// ```
74    /// use vortex_buffer::Alignment;
75    ///
76    /// let a = Alignment::new(4);
77    /// let b = Alignment::new(2);
78    /// assert!(a.is_aligned_to(b));
79    /// assert!(!b.is_aligned_to(a));
80    /// ```
81    #[inline]
82    pub const fn is_aligned_to(&self, other: Alignment) -> bool {
83        // Since both alignments are powers of 2, divisibility is equivalent to ordering.
84        self.0 >= other.0
85    }
86
87    /// Check if the given byte offset (or length) is a multiple of this alignment.
88    ///
89    /// ## Example
90    ///
91    /// ```
92    /// use vortex_buffer::Alignment;
93    ///
94    /// let a = Alignment::new(4);
95    /// assert!(a.is_offset_aligned(8));
96    /// assert!(!a.is_offset_aligned(2));
97    /// ```
98    #[inline]
99    pub const fn is_offset_aligned(&self, offset: usize) -> bool {
100        // Alignment is always a power of 2, so a mask test is equivalent to `offset % self == 0`.
101        offset & (self.0 - 1) == 0
102    }
103
104    /// Check if the given pointer is aligned to this alignment.
105    #[inline]
106    pub fn is_ptr_aligned<T>(&self, ptr: *const T) -> bool {
107        self.is_offset_aligned(ptr.addr())
108    }
109
110    /// Returns the log2 of the alignment.
111    pub fn exponent(&self) -> u8 {
112        u8::try_from(self.0.trailing_zeros())
113            .vortex_expect("alignment is a power of 2 within usize, so its exponent fits in u8")
114    }
115
116    /// Create from the log2 exponent of the alignment.
117    ///
118    /// ## Panics
119    ///
120    /// Panics if `1 << exponent` overflows `usize`. Use [`Self::try_from_exponent`] when parsing
121    /// untrusted input.
122    #[inline]
123    pub const fn from_exponent(exponent: u8) -> Self {
124        assert!(
125            (exponent as u32) < usize::BITS,
126            "Alignment exponent must fit in usize"
127        );
128        Self::new(1 << exponent)
129    }
130
131    /// Create from the log2 exponent of the alignment, returning an error rather than panicking if
132    /// `1 << exponent` would overflow `usize`.
133    ///
134    /// Prefer this over [`from_exponent`](Self::from_exponent) when the exponent originates from
135    /// untrusted input such as a serialized file, where a too-large value must not panic.
136    #[inline]
137    pub fn try_from_exponent(exponent: u8) -> VortexResult<Self> {
138        if u32::from(exponent) >= usize::BITS {
139            vortex_bail!(
140                "Alignment exponent {exponent} is too large for a {}-bit usize",
141                usize::BITS
142            );
143        }
144        Ok(Self::new(1 << exponent))
145    }
146}
147
148impl Display for Alignment {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "{}", self.0)
151    }
152}
153
154impl Deref for Alignment {
155    type Target = usize;
156
157    #[inline]
158    fn deref(&self) -> &Self::Target {
159        &self.0
160    }
161}
162
163impl From<usize> for Alignment {
164    #[inline]
165    fn from(value: usize) -> Self {
166        Self::new(value)
167    }
168}
169
170impl From<u16> for Alignment {
171    #[inline]
172    fn from(value: u16) -> Self {
173        Self::new(usize::from(value))
174    }
175}
176
177impl From<Alignment> for usize {
178    #[inline]
179    fn from(value: Alignment) -> Self {
180        value.0
181    }
182}
183
184impl From<Alignment> for u32 {
185    #[inline]
186    fn from(value: Alignment) -> Self {
187        u32::try_from(value.0).vortex_expect("Alignment must fit into u32")
188    }
189}
190
191impl TryFrom<u32> for Alignment {
192    type Error = VortexError;
193
194    fn try_from(value: u32) -> Result<Self, Self::Error> {
195        let value = usize::try_from(value)
196            .map_err(|_| vortex_err!("Alignment must fit into usize, got {value}"))?;
197
198        if value == 0 {
199            return Err(vortex_err!("Alignment must be greater than 0"));
200        }
201        if !value.is_power_of_two() {
202            return Err(vortex_err!("Alignment must be a power of 2, got {value}"));
203        }
204
205        Ok(Self(value))
206    }
207}
208
209#[cfg(test)]
210mod test {
211    use super::*;
212
213    #[test]
214    #[should_panic]
215    fn alignment_zero() {
216        Alignment::new(0);
217    }
218
219    #[test]
220    fn alignment_above_u16() {
221        // 64KiB alignment (one past `u16::MAX`) is valid — common on ARM with 64K pages.
222        let alignment = Alignment::new(u16::MAX as usize + 1);
223        assert_eq!(*alignment, 1 << 16);
224        assert_eq!(alignment, Alignment::from_exponent(16));
225    }
226
227    #[test]
228    #[should_panic]
229    fn alignment_not_power_of_two() {
230        Alignment::new(3);
231    }
232
233    #[test]
234    fn alignment_exponent() {
235        let alignment = Alignment::new(1024);
236        assert_eq!(alignment.exponent(), 10);
237        assert_eq!(Alignment::from_exponent(10), alignment);
238    }
239
240    #[test]
241    fn is_aligned_to() {
242        assert!(Alignment::new(1).is_aligned_to(Alignment::new(1)));
243        assert!(Alignment::new(2).is_aligned_to(Alignment::new(1)));
244        assert!(Alignment::new(4).is_aligned_to(Alignment::new(1)));
245        assert!(!Alignment::new(1).is_aligned_to(Alignment::new(2)));
246    }
247
248    #[test]
249    fn try_from_u32() {
250        match Alignment::try_from(8u32) {
251            Ok(alignment) => assert_eq!(alignment, Alignment::new(8)),
252            Err(err) => panic!("unexpected error for valid alignment: {err}"),
253        }
254        match Alignment::try_from(1u32 << 16) {
255            Ok(alignment) => assert_eq!(alignment, Alignment::new(1 << 16)),
256            Err(err) => panic!("64KiB alignment should be valid: {err}"),
257        }
258        assert!(Alignment::try_from(0u32).is_err());
259        assert!(Alignment::try_from(3u32).is_err());
260    }
261
262    #[test]
263    fn try_from_exponent() {
264        match Alignment::try_from_exponent(10) {
265            Ok(alignment) => assert_eq!(alignment, Alignment::new(1024)),
266            Err(err) => panic!("valid exponent should succeed: {err}"),
267        }
268        // Exponents whose `1 << exponent` would overflow a usize must error rather than panic.
269        // 64 is `>= usize::BITS` on both 32- and 64-bit targets.
270        assert!(Alignment::try_from_exponent(64).is_err());
271        assert!(Alignment::try_from_exponent(u8::MAX).is_err());
272    }
273
274    #[test]
275    fn into_u32() {
276        let alignment = Alignment::new(64);
277        assert_eq!(u32::from(alignment), 64u32);
278    }
279}