Skip to main content

oxidelake_core/
layout.rs

1//! Buffer layout descriptors and the alignment constants every allocator honours.
2
3use crate::EngineError;
4
5/// Alignment of host buffers: one cache line, also the AVX-512 vector width.
6pub const HOST_ALIGN: usize = 64;
7
8/// Alignment of buffers bound for a device: one coalesced global-memory transaction.
9pub const DEVICE_ALIGN: usize = 128;
10
11/// Length and alignment of a buffer, with helpers for padding and validation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct BufferLayout {
14    len: usize,
15    align: usize,
16}
17
18impl BufferLayout {
19    /// Builds a layout, validating that `align` is a non-zero power of two.
20    pub fn new(len: usize, align: usize) -> Result<Self, EngineError> {
21        if align == 0 || !align.is_power_of_two() {
22            return Err(EngineError::allocation(
23                len,
24                align,
25                "alignment must be a non-zero power of two",
26            ));
27        }
28        Ok(Self { len, align })
29    }
30
31    /// A host-aligned ([`HOST_ALIGN`]) layout.
32    pub const fn host(len: usize) -> Self {
33        Self {
34            len,
35            align: HOST_ALIGN,
36        }
37    }
38
39    /// A device-aligned ([`DEVICE_ALIGN`]) layout.
40    pub const fn device(len: usize) -> Self {
41        Self {
42            len,
43            align: DEVICE_ALIGN,
44        }
45    }
46
47    /// The requested length in bytes.
48    pub const fn len(&self) -> usize {
49        self.len
50    }
51
52    /// `true` when the buffer holds no bytes.
53    pub const fn is_empty(&self) -> bool {
54        self.len == 0
55    }
56
57    /// The alignment in bytes.
58    pub const fn align(&self) -> usize {
59        self.align
60    }
61
62    /// The length rounded up to a multiple of the alignment (zero stays zero).
63    pub const fn padded_len(&self) -> usize {
64        let rem = self.len % self.align;
65        if rem == 0 {
66            self.len
67        } else {
68            self.len + (self.align - rem)
69        }
70    }
71
72    /// `true` when `addr` satisfies this layout's alignment.
73    pub const fn is_aligned(&self, addr: usize) -> bool {
74        addr.is_multiple_of(self.align)
75    }
76}
77
78#[cfg(test)]
79#[allow(clippy::unwrap_used, clippy::expect_used)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn rejects_bad_alignment() {
85        assert!(BufferLayout::new(8, 0).is_err());
86        assert!(BufferLayout::new(8, 48).is_err());
87        assert!(BufferLayout::new(8, 64).is_ok());
88    }
89
90    #[test]
91    fn pads_to_alignment() {
92        assert_eq!(BufferLayout::host(0).padded_len(), 0);
93        assert_eq!(BufferLayout::host(1).padded_len(), 64);
94        assert_eq!(BufferLayout::host(64).padded_len(), 64);
95        assert_eq!(BufferLayout::device(129).padded_len(), 256);
96    }
97
98    #[test]
99    fn checks_addresses() {
100        assert!(BufferLayout::device(1).is_aligned(0x1000));
101        assert!(!BufferLayout::device(1).is_aligned(0x1040));
102        assert!(BufferLayout::host(1).is_aligned(0x1040));
103    }
104}