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