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