Skip to main content

ferray_core/
layout.rs

1// ferray-core: Memory layout enum (REQ-1)
2
3/// Describes the memory layout of an N-dimensional array.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum MemoryLayout {
6    /// Row-major (C-style) contiguous layout.
7    C,
8    /// Column-major (Fortran-style) contiguous layout.
9    Fortran,
10    /// Non-contiguous or custom stride layout.
11    Custom,
12}
13
14impl MemoryLayout {
15    /// Returns `true` if the layout is C-contiguous (row-major).
16    #[inline]
17    pub fn is_c_contiguous(self) -> bool {
18        self == Self::C
19    }
20
21    /// Returns `true` if the layout is Fortran-contiguous (column-major).
22    #[inline]
23    pub fn is_f_contiguous(self) -> bool {
24        self == Self::Fortran
25    }
26
27    /// Returns `true` if the layout is neither C nor Fortran contiguous.
28    #[inline]
29    pub fn is_custom(self) -> bool {
30        self == Self::Custom
31    }
32}
33
34impl core::fmt::Display for MemoryLayout {
35    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36        match self {
37            Self::C => write!(f, "C"),
38            Self::Fortran => write!(f, "F"),
39            Self::Custom => write!(f, "Custom"),
40        }
41    }
42}
43
44/// Determine memory layout from shape and strides.
45#[cfg(not(feature = "no_std"))]
46pub(crate) fn detect_layout(shape: &[usize], strides: &[isize]) -> MemoryLayout {
47    if shape.is_empty() {
48        return MemoryLayout::C; // scalar-like
49    }
50
51    let is_c = is_c_contiguous(shape, strides);
52    let is_f = is_f_contiguous(shape, strides);
53
54    if is_c {
55        MemoryLayout::C
56    } else if is_f {
57        MemoryLayout::Fortran
58    } else {
59        MemoryLayout::Custom
60    }
61}
62
63#[cfg(not(feature = "no_std"))]
64fn is_c_contiguous(shape: &[usize], strides: &[isize]) -> bool {
65    if shape.len() != strides.len() {
66        return false;
67    }
68    let ndim = shape.len();
69    if ndim == 0 {
70        return true;
71    }
72    let mut expected: isize = 1;
73    for i in (0..ndim).rev() {
74        if shape[i] == 0 {
75            return true; // empty array is contiguous by convention
76        }
77        if shape[i] != 1 && strides[i] != expected {
78            return false;
79        }
80        expected = strides[i] * shape[i] as isize;
81    }
82    true
83}
84
85#[cfg(not(feature = "no_std"))]
86fn is_f_contiguous(shape: &[usize], strides: &[isize]) -> bool {
87    if shape.len() != strides.len() {
88        return false;
89    }
90    let ndim = shape.len();
91    if ndim == 0 {
92        return true;
93    }
94    let mut expected: isize = 1;
95    for i in 0..ndim {
96        if shape[i] == 0 {
97            return true; // empty array is contiguous by convention
98        }
99        if shape[i] != 1 && strides[i] != expected {
100            return false;
101        }
102        expected = strides[i] * shape[i] as isize;
103    }
104    true
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn detect_c_contiguous() {
113        // 3x4 C-contiguous: strides = [4, 1]
114        assert_eq!(detect_layout(&[3, 4], &[4, 1]), MemoryLayout::C);
115    }
116
117    #[test]
118    fn detect_f_contiguous() {
119        // 3x4 F-contiguous: strides = [1, 3]
120        assert_eq!(detect_layout(&[3, 4], &[1, 3]), MemoryLayout::Fortran);
121    }
122
123    #[test]
124    fn detect_custom() {
125        // non-contiguous strides
126        assert_eq!(detect_layout(&[3, 4], &[8, 2]), MemoryLayout::Custom);
127    }
128
129    #[test]
130    fn detect_empty() {
131        assert_eq!(detect_layout(&[], &[]), MemoryLayout::C);
132    }
133
134    #[test]
135    fn display() {
136        assert_eq!(MemoryLayout::C.to_string(), "C");
137        assert_eq!(MemoryLayout::Fortran.to_string(), "F");
138        assert_eq!(MemoryLayout::Custom.to_string(), "Custom");
139    }
140}