1use super::*;
3use std::{
4 mem,
5 hash,
6 num::NonZeroUsize,
7 fs,
8 path::{Path, PathBuf},
9 fmt, error,
10};
11use libc::{
12 c_int,
13 MAP_HUGE_SHIFT,
14};
15
16pub const HUGEPAGE_LOCATION: &'static str = "/sys/kernel/mm/hugepages/";
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
21#[repr(transparent)]
22pub struct MapHugeFlag(c_int);
23
24#[derive(Debug)]
26pub struct HugePageCalcErr(());
27
28impl TryFrom<HugePage> for MapHugeFlag
29{
30 type Error = HugePageCalcErr;
31
32 #[inline]
33 fn try_from(from: HugePage) -> Result<Self, Self::Error>
34 {
35 from.compute_huge().ok_or(HugePageCalcErr(()))
36 }
37}
38
39
40impl error::Error for HugePageCalcErr{}
41impl fmt::Display for HugePageCalcErr
42{
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
44 {
45 f.write_str("Invalid huge-page specification")
46 }
47}
48
49
50impl Default for MapHugeFlag
51{
52 #[inline]
53 fn default() -> Self {
54 Self(MAP_HUGE_SHIFT)
55 }
56}
57#[inline(always)]
58const fn log2(n: usize) -> usize
59{
60 usize::BITS as usize - n.leading_zeros() as usize - 1
61}
62
63impl MapHugeFlag
64{
65 #[inline]
70 pub const unsafe fn from_mask_unchecked(flag: c_int) -> Self
71 {
72 Self(flag)
73 }
74
75 pub const HUGE_DEFAULT: Self = Self(MAP_HUGE_SHIFT);
77 pub const HUGE_2MB: Self = Self(libc::MAP_HUGE_2MB);
79 pub const HUGE_1GB: Self = Self(libc::MAP_HUGE_1GB);
81
82 #[inline(always)]
84 pub const fn calculate(kilobytes: NonZeroUsize) -> Self
85 {
86 Self((log2(kilobytes.get()) << (MAP_HUGE_SHIFT as usize)) as c_int)
87 }
88
89 #[inline]
91 pub const fn try_calculate(kilobytes: usize) -> Option<Self>
92 {
93 match kilobytes {
94 0 => None,
95 kilobytes => {
96 if let Some(shift) = log2(kilobytes).checked_shl(MAP_HUGE_SHIFT as u32) {
97 if shift <= c_int::MAX as usize {
98 return Some(Self(shift as c_int));
99 }
100 }
101 None
102 }
103 }
104 }
105
106 #[inline]
111 pub const fn calculate_or_default(kilobytes: usize) -> Self
112 {
113 match Self::try_calculate(kilobytes) {
114 None => Self::HUGE_DEFAULT,
115 Some(x) => x,
116 }
117 }
118
119 #[inline]
121 pub const fn is_default(&self) -> bool
122 {
123 self.0 == Self::HUGE_DEFAULT.0
124 }
125
126 #[inline(always)]
128 pub const fn get_mask(self) -> c_int
129 {
130 self.0
131 }
132}
133
134impl From<MapHugeFlag> for c_int
135{
136 #[inline]
137 fn from(from: MapHugeFlag) -> Self
138 {
139 from.0
140 }
141}
142
143#[derive(Default, Clone, Copy)]
150pub enum HugePage {
151 Static(MapHugeFlag),
153 Dynamic{ kilobytes: usize },
160 #[default]
162 Smallest,
163 Largest,
165 Selected(for<'r> fn (&'r [usize]) -> Option<&'r usize>),
167}
168
169impl hash::Hash for HugePage {
170 #[inline]
171 fn hash<H: hash::Hasher>(&self, state: &mut H) {
172 mem::discriminant(self).hash(state);
173 match self {
174 Self::Static(hpf) => hpf.hash(state),
175 Self::Dynamic { kilobytes } => kilobytes.hash(state),
176 Self::Selected(func) => ptr::hash(func as *const _, state),
177 _ => (),
178 };
179 }
180}
181
182impl fmt::Debug for HugePage
183{
184 #[inline]
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
186 {
187 f.debug_tuple("HugePage")
188 .field({
189 let v: &dyn fmt::Debug = match &self {
190 Self::Static(ref huge) => huge,
191 Self::Dynamic { ref kilobytes } => kilobytes,
192 Self::Smallest => &"<smallest>",
193 Self::Largest => &"<largest>",
194 Self::Selected(_) => &"<selector>",
195 };
196 v
197 })
198 .finish()
199 }
200}
201
202
203impl Eq for HugePage {}
204impl PartialEq for HugePage
205{
206 #[inline]
207 fn eq(&self, other: &Self) -> bool
208 {
209 match (self, other) {
210 (Self::Static(hpf), Self::Static(hpf2)) => hpf == hpf2,
211 (Self::Dynamic { kilobytes }, Self::Dynamic { kilobytes: kilobytes2 }) => kilobytes == kilobytes2,
212 (Self::Selected(func), Self::Selected(func2)) => ptr::eq(func, func2),
213 _ => mem::discriminant(self) == mem::discriminant(other),
214 }
215 }
216}
217
218impl HugePage
219{
220 #[inline] pub fn compute_huge(self) -> Option<MapHugeFlag>
230 {
231 use HugePage::*;
232 match self {
233 Dynamic { kilobytes: 0 } |
234 Smallest |
235 Static(MapHugeFlag::HUGE_DEFAULT) => Some(MapHugeFlag::HUGE_DEFAULT),
236 Static(mask) => Some(mask),
237 Dynamic { kilobytes } => {
238 MapHugeFlag::try_calculate(kilobytes) },
240 Largest => Self::Selected(|sizes| sizes.iter().max()).compute_huge(),
241 Selected(func) => {
242 fn compute_selected(func: for<'r> fn (&'r [usize]) -> Option<&'r usize>) -> Option<MapHugeFlag>
244 {
245 use std::borrow::Cow;
246 let mask = match SYSTEM_HUGEPAGE_SIZES.as_ref() {
247 Ok(avail) => Cow::Borrowed(&avail[..]),
248 Err(_) => {
249 #[cold]
251 fn rescan() -> io::Result<Vec<usize>>
252 {
253 scan_hugepages().and_then(|x| x.into_iter().collect())
254 }
255 let v = rescan();
256 let mut v = if cfg!(debug_assertions) {
257 v.expect("Failed to compute available hugetlb sizes")
258 } else {
259 v.ok()?
260 };
261 v.sort_unstable();
262 Cow::Owned(v)
263 },
264 };
265
266 match func(mask.as_ref()) {
267 Some(mask) => Dynamic { kilobytes: *mask }.compute_huge(),
268 None => Some(MapHugeFlag::HUGE_DEFAULT),
269 }
270 }
271 compute_selected(func)
272 },
273 }
274 }
275}
276
277lazy_static! {
278 pub(crate) static ref SYSTEM_HUGEPAGE_SIZES: io::Result<Vec<usize>> = {
280 let mut val: io::Result<Vec<usize>> = scan_hugepages().and_then(|x| x.into_iter().collect());
281 if let Ok(ref mut arr) = val.as_mut() {
282 arr.sort_unstable();
283 };
284 val
285 };
286
287 pub static ref SYSTEM_HUGEPAGES: io::Result<Vec<MapHugeFlag>> =
291 SYSTEM_HUGEPAGE_SIZES.as_ref()
292 .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, format!("SYSTEM_HUGEPAGES failed with error {err}")))
293 .map(|vec| vec.iter().map(|&size| MapHugeFlag::calculate_or_default(size)).collect());
294}
295
296pub fn scan_hugepages() -> io::Result<impl IntoIterator<Item=io::Result<usize>> + Send + Sync + 'static>
305{
306 let path = Path::new(HUGEPAGE_LOCATION);
307 let dir = fs::read_dir(path)?;
308
309 #[derive(Debug)]
310 struct FilteredIterator(fs::ReadDir);
311
312 impl Iterator for FilteredIterator
313 {
314 type Item = io::Result<usize>;
315 fn next(&mut self) -> Option<Self::Item> {
316 loop {
317 break if let Some(next) = self.0.next() {
318 let path = match next {
319 Ok(next) => next.file_name(),
320 Err(err) => return Some(Err(err)),
321 };
322 let kbs = if let Some(dash) = memchr::memchr(b'-', path.as_bytes()) {
323 let name = &path.as_bytes()[(dash+1)..];
324 if let Some(k_loc) = memchr::memrchr(b'k', &name) {
325 &name[..k_loc]
326 } else {
327 continue
328 }
329 } else {
330 continue
331 };
332 let kb = if let Ok(kbs) = std::str::from_utf8(kbs) {
333 kbs.parse::<usize>().ok()
334 } else {
335 continue
336 };
337 match kb {
338 None => continue,
339 valid => valid.map(Ok)
340 }
341 } else {
342 None
343 }
344 }
345 }
346 }
347
348 Ok(FilteredIterator(dir))
349}