Skip to main content

frozen_core/memmap/
page_size.rs

1//! Strongly-typed virtual memory page size definitions and platform validation
2
3use crate::{
4    error::{FrozenError, FrozenResult},
5    hints,
6    memmap::err,
7};
8
9/// Strongly-typed virtual memory page size representing hardware MMU translation granules
10///
11/// Across supported operating systems and CPU architectures, base virtual memory page sizes
12/// form a strictly bounded set corresponding to hardware translation granules:
13///
14/// - **4 KiB** (`4,096` bytes): Standard on x86/x86_64, Windows, and default Linux AArch64
15/// - **16 KiB** (`16,384` bytes): Apple Silicon macOS and 16k Linux kernels
16/// - **64 KiB** (`65,536` bytes): Enterprise Linux AArch64 (RHEL, CentOS Stream, Amazon Linux)
17///
18/// ## Example
19///
20/// ```
21/// use frozen_core::memmap::PageSize;
22///
23/// let ps = PageSize::try_from(4096).unwrap();
24/// assert_eq!(ps, PageSize::K4);
25/// assert_eq!(ps.as_usize(), 4096);
26/// assert_eq!(ps.as_u32(), 4096);
27/// assert!(ps.is_power_of_two());
28/// ```
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30#[repr(usize)]
31pub enum PageSize {
32    /// 4 KiB (4,096 bytes) - Standard x86_64, Windows, and default Linux AArch64
33    K4 = 4096,
34    /// 16 KiB (16,384 bytes) - Native Apple Silicon macOS and 16k Linux kernels
35    K16 = 16384,
36    /// 64 KiB (65,536 bytes) - Enterprise Linux AArch64 (RHEL, CentOS, Amazon Linux)
37    K64 = 65536,
38}
39
40impl PageSize {
41    /// Returns the page size in bytes as `usize`
42    #[inline]
43    pub const fn as_usize(self) -> usize {
44        self as usize
45    }
46
47    /// Returns the page size in bytes as `u32`
48    #[inline]
49    pub const fn as_u32(self) -> u32 {
50        self as usize as u32
51    }
52
53    /// Returns `true` since all valid hardware page sizes are powers of two
54    #[inline]
55    pub const fn is_power_of_two(self) -> bool {
56        true
57    }
58
59    /// Validates whether this page size is supported on the active host operating system and
60    /// architecture
61    ///
62    /// ## Platform rules
63    ///
64    /// - **Windows**: Only `PageSize::K4` (4096 bytes) is valid
65    /// - **macOS**: `PageSize::K16` (16384 bytes) on Apple Silicon (`aarch64`); `PageSize::K4` (4096 bytes) on Intel (`x86_64`)
66    /// - **Linux**: `PageSize::K4` (4096 bytes) on `x86`/`x86_64`; `PageSize::K4`, `PageSize::K16`, or `PageSize::K64` on `aarch64`
67    pub fn validate_platform(self) -> FrozenResult<Self> {
68        Self::validate_target_platform(self, std::env::consts::OS, std::env::consts::ARCH)
69    }
70
71    pub(crate) fn validate_target_platform(self, os: &str, arch: &str) -> FrozenResult<Self> {
72        let valid = match (os, arch) {
73            ("windows", _) => self == Self::K4,
74            ("macos", "aarch64") => self == Self::K16,
75            ("macos", "x86_64") => self == Self::K4,
76            ("linux", "x86" | "x86_64") => self == Self::K4,
77            ("linux", "aarch64") => matches!(self, Self::K4 | Self::K16 | Self::K64),
78            _ => false,
79        };
80
81        if hints::likely(valid) {
82            Ok(self)
83        } else {
84            err::raw_error(
85                err::INV,
86                format!(
87                    "unsupported page size {} for target platform {os}/{arch}",
88                    self.as_usize()
89                ),
90            )
91        }
92    }
93}
94
95impl From<PageSize> for usize {
96    #[inline]
97    fn from(ps: PageSize) -> Self {
98        ps as usize
99    }
100}
101
102impl From<PageSize> for u32 {
103    #[inline]
104    fn from(ps: PageSize) -> Self {
105        ps as usize as u32
106    }
107}
108
109impl From<PageSize> for u64 {
110    #[inline]
111    fn from(ps: PageSize) -> Self {
112        ps as usize as u64
113    }
114}
115
116impl core::fmt::Display for PageSize {
117    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118        write!(f, "{}", self.as_usize())
119    }
120}
121
122impl TryFrom<usize> for PageSize {
123    type Error = FrozenError;
124
125    fn try_from(size: usize) -> Result<Self, Self::Error> {
126        match size {
127            4096 => Ok(Self::K4),
128            16384 => Ok(Self::K16),
129            65536 => Ok(Self::K64),
130            other => err::raw_error(err::INV, format!("unsupported page size: {other}")),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn ok_page_size_conversions() {
141        assert_eq!(PageSize::K4.as_usize(), 4096);
142        assert_eq!(PageSize::K16.as_usize(), 16384);
143        assert_eq!(PageSize::K64.as_usize(), 65536);
144
145        assert_eq!(PageSize::K4.as_u32(), 4096);
146        assert_eq!(PageSize::K16.as_u32(), 16384);
147        assert_eq!(PageSize::K64.as_u32(), 65536);
148
149        assert_eq!(usize::from(PageSize::K4), 4096);
150        assert_eq!(u32::from(PageSize::K16), 16384);
151        assert_eq!(u64::from(PageSize::K64), 65536);
152
153        assert_eq!(PageSize::try_from(4096).unwrap(), PageSize::K4);
154        assert_eq!(PageSize::try_from(16384).unwrap(), PageSize::K16);
155        assert_eq!(PageSize::try_from(65536).unwrap(), PageSize::K64);
156
157        assert_eq!(format!("{}", PageSize::K4), "4096");
158        assert_eq!(format!("{}", PageSize::K16), "16384");
159        assert_eq!(format!("{}", PageSize::K64), "65536");
160
161        assert!(PageSize::K4.is_power_of_two());
162        assert!(PageSize::K16.is_power_of_two());
163        assert!(PageSize::K64.is_power_of_two());
164    }
165
166    #[test]
167    fn err_page_size_invalid_values() {
168        assert!(PageSize::try_from(0).is_err());
169        assert!(PageSize::try_from(1024).is_err());
170        assert!(PageSize::try_from(2048).is_err());
171        assert!(PageSize::try_from(5000).is_err());
172        assert!(PageSize::try_from(8192).is_err());
173        assert!(PageSize::try_from(32768).is_err());
174        assert!(PageSize::try_from(131072).is_err());
175    }
176
177    #[test]
178    fn ok_platform_validation_matrix() {
179        // Windows allows only K4
180        assert!(PageSize::K4.validate_target_platform("windows", "x86_64").is_ok());
181        assert!(PageSize::K4.validate_target_platform("windows", "aarch64").is_ok());
182        assert!(PageSize::K16.validate_target_platform("windows", "x86_64").is_err());
183        assert!(PageSize::K64.validate_target_platform("windows", "x86_64").is_err());
184
185        // macOS Apple Silicon (aarch64) allows only K16
186        assert!(PageSize::K16.validate_target_platform("macos", "aarch64").is_ok());
187        assert!(PageSize::K4.validate_target_platform("macos", "aarch64").is_err());
188        assert!(PageSize::K64.validate_target_platform("macos", "aarch64").is_err());
189
190        // macOS Intel (x86_64) allows only K4
191        assert!(PageSize::K4.validate_target_platform("macos", "x86_64").is_ok());
192        assert!(PageSize::K16.validate_target_platform("macos", "x86_64").is_err());
193        assert!(PageSize::K64.validate_target_platform("macos", "x86_64").is_err());
194
195        // Linux x86 / x86_64 allows only K4
196        assert!(PageSize::K4.validate_target_platform("linux", "x86_64").is_ok());
197        assert!(PageSize::K4.validate_target_platform("linux", "x86").is_ok());
198        assert!(PageSize::K16.validate_target_platform("linux", "x86_64").is_err());
199        assert!(PageSize::K64.validate_target_platform("linux", "x86_64").is_err());
200
201        // Linux aarch64 allows K4, K16, K64
202        assert!(PageSize::K4.validate_target_platform("linux", "aarch64").is_ok());
203        assert!(PageSize::K16.validate_target_platform("linux", "aarch64").is_ok());
204        assert!(PageSize::K64.validate_target_platform("linux", "aarch64").is_ok());
205
206        // Unsupported platform
207        assert!(PageSize::K4.validate_target_platform("freebsd", "x86_64").is_err());
208        assert!(PageSize::K4.validate_target_platform("unknown", "unknown").is_err());
209    }
210
211    #[test]
212    fn ok_current_platform_validation() {
213        #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
214        assert!(PageSize::K4.validate_platform().is_ok());
215
216        #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
217        {
218            assert!(PageSize::K4.validate_platform().is_ok());
219            assert!(PageSize::K16.validate_platform().is_ok());
220            assert!(PageSize::K64.validate_platform().is_ok());
221        }
222
223        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
224        {
225            assert!(PageSize::K16.validate_platform().is_ok());
226            assert!(PageSize::K4.validate_platform().is_err());
227        }
228
229        #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
230        {
231            assert!(PageSize::K4.validate_platform().is_ok());
232            assert!(PageSize::K16.validate_platform().is_err());
233        }
234
235        #[cfg(target_os = "windows")]
236        {
237            assert!(PageSize::K4.validate_platform().is_ok());
238            assert!(PageSize::K16.validate_platform().is_err());
239        }
240    }
241}