1use core::ops::{Deref, DerefMut};
2
3#[repr(align(64))]
4#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
5pub struct Align64<T>(pub T);
7
8impl<T> Align64<T> {
9 #[inline(always)]
14 pub const unsafe fn new_unchecked(input: &T) -> &Self {
15 unsafe { &*(input as *const T as *const Self) }
16 }
17
18 pub fn new(input: &T) -> &Self {
24 let ptr = input as *const T;
25 assert_eq!(
26 ptr.align_offset(64),
27 0,
28 "Input pointer is not aligned to 64 bytes"
29 );
30 unsafe { Self::new_unchecked(input) }
31 }
32
33 #[inline(always)]
38 pub const unsafe fn new_mut_unchecked(input: &mut T) -> &mut Self {
39 unsafe { &mut *(input as *mut T as *mut Self) }
40 }
41
42 pub fn new_mut(input: &mut T) -> &mut Self {
48 let ptr = input as *mut T;
49 assert_eq!(
50 ptr.align_offset(64),
51 0,
52 "Input pointer is not aligned to 64 bytes"
53 );
54 unsafe { Self::new_mut_unchecked(input) }
55 }
56}
57
58impl<T> AsRef<T> for Align64<T> {
59 fn as_ref(&self) -> &T {
60 &self.0
61 }
62}
63
64impl<T> AsRef<Align64<T>> for Align64<T> {
65 fn as_ref(&self) -> &Align64<T> {
66 self
67 }
68}
69
70impl<T> AsMut<Align64<T>> for Align64<T> {
71 fn as_mut(&mut self) -> &mut Align64<T> {
72 self
73 }
74}
75
76impl<T> AsMut<T> for Align64<T> {
77 fn as_mut(&mut self) -> &mut T {
78 &mut self.0
79 }
80}
81
82impl<T> Deref for Align64<T> {
83 type Target = T;
84 fn deref(&self) -> &Self::Target {
85 &self.0
86 }
87}
88
89impl<T> DerefMut for Align64<T> {
90 fn deref_mut(&mut self) -> &mut Self::Target {
91 &mut self.0
92 }
93}
94
95#[cfg(feature = "huge-page")]
97pub struct HugeSlice<T> {
98 ptr: *mut T,
99 len: usize,
100 #[cfg(any(target_os = "android", target_os = "linux"))]
101 capacity: usize,
102 #[cfg(any(target_os = "android", target_os = "linux"))]
103 file: std::fs::File,
104}
105
106#[cfg(feature = "huge-page")]
107unsafe impl<T> Send for HugeSlice<T> where T: Send {}
108
109#[cfg(feature = "huge-page")]
110unsafe impl<T> Sync for HugeSlice<T> where T: Sync {}
111
112#[cfg(feature = "huge-page")]
113impl<T> core::convert::AsMut<T> for HugeSlice<T> {
114 fn as_mut(&mut self) -> &mut T {
115 unsafe { &mut *self.ptr }
116 }
117}
118
119#[cfg(feature = "huge-page")]
120impl<T> HugeSlice<T> {
121 #[cfg(target_os = "windows")]
123 pub fn new(len: usize) -> Result<Self, std::io::Error> {
124 use windows_sys::Win32::{
125 Security::TOKEN_ADJUST_PRIVILEGES,
126 System::{
127 Memory::{
128 GetLargePageMinimum, MEM_COMMIT, MEM_LARGE_PAGES, MEM_RESERVE, PAGE_READWRITE,
129 VirtualAlloc,
130 },
131 Threading::{GetCurrentProcess, OpenProcessToken},
132 },
133 };
134 unsafe {
135 use windows_sys::Win32::Security::{
136 AdjustTokenPrivileges, LUID_AND_ATTRIBUTES, LookupPrivilegeValueA,
137 SE_PRIVILEGE_ENABLED, TOKEN_PRIVILEGES,
138 };
139
140 let large_page_minimum = GetLargePageMinimum();
141 if large_page_minimum == 0 {
142 return Err(std::io::Error::new(
143 std::io::ErrorKind::Other,
144 "Large page not supported by the system",
145 ));
146 }
147
148 if core::mem::align_of::<T>() > large_page_minimum {
149 return Err(std::io::Error::new(
150 std::io::ErrorKind::Other,
151 format!(
152 "Alignment of the type is greater than the large page minimum: {} requires {} alignment, large page minimum is {}",
153 core::any::type_name::<T>(),
154 core::mem::align_of::<T>(),
155 large_page_minimum
156 ),
157 ));
158 }
159
160 let min_alloc_size = core::mem::size_of::<T>()
161 .checked_mul(len)
162 .and_then(|x| x.checked_next_multiple_of(large_page_minimum))
163 .and_then(|x| x.checked_next_multiple_of(8))
164 .ok_or_else(|| {
165 std::io::Error::new(
166 std::io::ErrorKind::Other,
167 format!(
168 "Allocation size overflow (requested: {}, max: {})",
169 core::mem::size_of::<T>() as u128 * len as u128,
170 usize::MAX
171 ),
172 )
173 })?;
174
175 if min_alloc_size == 0 {
176 return Err(std::io::Error::new(
177 std::io::ErrorKind::Other,
178 "Allocation size must be non-zero",
179 ));
180 }
181
182 let mut token_handle = Default::default();
183 if OpenProcessToken(
184 GetCurrentProcess(),
185 TOKEN_ADJUST_PRIVILEGES,
186 &mut token_handle,
187 ) == 0
188 {
189 return Err(std::io::Error::last_os_error());
190 }
191
192 let mut privs = [TOKEN_PRIVILEGES {
193 PrivilegeCount: 1,
194 Privileges: [LUID_AND_ATTRIBUTES {
195 Luid: Default::default(),
196 Attributes: SE_PRIVILEGE_ENABLED,
197 }],
198 }];
199
200 if LookupPrivilegeValueA(
201 core::ptr::null_mut(),
202 c"SeLockMemoryPrivilege".as_ptr().cast(),
203 &mut privs[0].Privileges[0].Luid,
204 ) == 0
205 {
206 return Err(std::io::Error::last_os_error());
207 }
208
209 if AdjustTokenPrivileges(
210 token_handle,
211 0,
212 privs.as_ptr(),
213 0,
214 core::ptr::null_mut(),
215 core::ptr::null_mut(),
216 ) == 0
217 {
218 return Err(std::io::Error::new(
219 std::io::ErrorKind::Other,
220 "Failed to adjust token privileges",
221 ));
222 }
223
224 let ptr = VirtualAlloc(
225 core::ptr::null_mut(),
226 min_alloc_size,
227 MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES,
228 PAGE_READWRITE,
229 );
230
231 if ptr == core::ptr::null_mut() {
232 return Err(std::io::Error::last_os_error());
233 }
234
235 Ok(Self {
236 ptr: ptr.cast::<T>(),
237 len,
238 })
239 }
240 }
241
242 #[cfg(any(target_os = "android", target_os = "linux"))]
244 pub fn new(len: usize) -> Result<Self, std::io::Error> {
245 Self::new_unix(
246 len,
247 #[cfg(all(target_os = "linux", feature = "std"))]
248 None,
249 )
250 }
251
252 #[cfg(any(target_os = "android", target_os = "linux"))]
254 pub fn new_unix(
255 len: usize,
256 #[cfg(target_os = "linux")] file: Option<std::fs::File>,
257 ) -> Result<Self, std::io::Error> {
258 unsafe {
259 let pagesz = libc::sysconf(libc::_SC_PAGESIZE);
260 if pagesz == -1 {
261 return Err(std::io::Error::last_os_error());
262 }
263
264 let page_size = pagesz as usize;
265 if core::mem::align_of::<T>() > page_size {
266 return Err(std::io::Error::new(
267 std::io::ErrorKind::Other,
268 format!(
269 "Alignment of the type is greater than the page size: {} requires {} alignment, page size is {}",
270 core::any::type_name::<T>(),
271 core::mem::align_of::<T>(),
272 page_size
273 ),
274 ));
275 }
276
277 let alloc_min_len = core::mem::size_of::<T>()
278 .checked_mul(len)
279 .and_then(|x| x.checked_next_multiple_of(page_size))
280 .and_then(|x| x.checked_next_multiple_of(8))
281 .ok_or(std::io::Error::new(
282 std::io::ErrorKind::Other,
283 format!(
284 "Allocation length overflow (requested: {}, max: {})",
285 core::mem::size_of::<T>() as u128 * len as u128,
286 usize::MAX
287 ),
288 ))?;
289
290 if alloc_min_len == 0 {
291 return Err(std::io::Error::new(
292 std::io::ErrorKind::Other,
293 "Allocation length must be non-zero",
294 ));
295 }
296
297 #[cfg(target_os = "linux")]
298 if let Some(file) = file {
299 let ptr = libc::mmap64(
300 core::ptr::null_mut(),
301 alloc_min_len,
302 libc::PROT_READ | libc::PROT_WRITE,
303 libc::MAP_PRIVATE,
304 std::os::unix::io::AsRawFd::as_raw_fd(&file),
305 0,
306 );
307 if ptr != libc::MAP_FAILED {
308 return Ok(HugeSlice {
309 ptr: ptr.cast::<T>(),
310 len,
311 capacity: alloc_min_len,
312 file,
313 });
314 }
315
316 return Err(std::io::Error::last_os_error());
317 }
318
319 for (try_page_size, try_flags) in [
320 #[cfg(target_os = "linux")]
321 ((1 << 30), libc::MFD_HUGE_1GB),
322 #[cfg(target_os = "linux")]
323 ((256 << 20), libc::MFD_HUGE_256MB),
324 #[cfg(target_os = "linux")]
325 ((32 << 20), libc::MFD_HUGE_32MB),
326 #[cfg(target_os = "linux")]
327 ((16 << 20), libc::MFD_HUGE_16MB),
328 #[cfg(target_os = "linux")]
329 ((8 << 20), libc::MFD_HUGE_8MB),
330 #[cfg(target_os = "linux")]
331 ((2 << 20), libc::MFD_HUGE_2MB),
332 ((page_size), 0),
333 ]
334 .into_iter()
335 .filter(|(try_page_size, _flags)| *try_page_size >= page_size)
336 .filter(|(try_page_size, flags)| *flags == 0 || alloc_min_len * 2 > *try_page_size)
338 {
339 let fd = libc::memfd_create(
340 c"scrypt-opt-huge-page-file".as_ptr().cast(),
341 libc::MFD_CLOEXEC | libc::MFD_HUGETLB | try_flags,
342 );
343
344 if fd == -1 {
345 continue;
346 }
347
348 let try_file = std::os::unix::io::FromRawFd::from_raw_fd(fd);
349
350 let try_size = alloc_min_len
351 .checked_next_multiple_of(try_page_size)
352 .ok_or(std::io::Error::new(
353 std::io::ErrorKind::Other,
354 format!(
355 "Allocation length overflow (requested: {}, max: {})",
356 alloc_min_len,
357 usize::MAX
358 ),
359 ))?;
360
361 let ptr = libc::mmap64(
362 core::ptr::null_mut(),
363 try_size,
364 libc::PROT_READ | libc::PROT_WRITE,
365 libc::MAP_PRIVATE | libc::MAP_POPULATE,
366 fd,
367 0,
368 );
369 if ptr != libc::MAP_FAILED {
370 return Ok(HugeSlice {
371 ptr: ptr.cast::<T>(),
372 len,
373 capacity: try_size,
374 file: try_file,
375 });
376 }
377 }
378
379 Err(std::io::Error::last_os_error())
380 }
381 }
382
383 #[cfg(all(
384 not(target_os = "windows"),
385 not(target_os = "android"),
386 not(target_os = "linux")
387 ))]
388 pub fn new(_len: usize) -> Result<Self, std::io::Error> {
390 Err(std::io::Error::new(
391 std::io::ErrorKind::Other,
392 "Huge page not supported on this platform",
393 ))
394 }
395}
396
397#[cfg(feature = "huge-page")]
398impl<T> HugeSlice<core::mem::MaybeUninit<T>> {
399 pub unsafe fn assume_init(self) -> HugeSlice<T> {
401 let forgotten = core::mem::ManuallyDrop::new(self);
402 HugeSlice {
403 ptr: forgotten.ptr.cast::<T>(),
404 len: forgotten.len,
405 #[cfg(any(target_os = "android", target_os = "linux"))]
406 capacity: forgotten.capacity,
407 #[cfg(any(target_os = "android", target_os = "linux"))]
408 file: unsafe {
409 std::os::unix::io::FromRawFd::from_raw_fd(std::os::unix::io::AsRawFd::as_raw_fd(
410 &forgotten.file,
411 ))
412 },
413 }
414 }
415}
416
417#[cfg(feature = "huge-page")]
418impl<T> core::convert::AsRef<[T]> for HugeSlice<T> {
419 fn as_ref(&self) -> &[T] {
420 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
421 }
422}
423
424#[cfg(feature = "huge-page")]
425impl<T> core::convert::AsMut<[T]> for HugeSlice<T> {
426 fn as_mut(&mut self) -> &mut [T] {
427 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
428 }
429}
430
431#[cfg(feature = "huge-page")]
432impl<T> core::ops::Deref for HugeSlice<T> {
433 type Target = [T];
434 fn deref(&self) -> &Self::Target {
435 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
436 }
437}
438
439#[cfg(feature = "huge-page")]
440impl<T> core::ops::DerefMut for HugeSlice<T> {
441 fn deref_mut(&mut self) -> &mut Self::Target {
442 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
443 }
444}
445
446#[cfg(feature = "huge-page")]
447impl<T> Drop for HugeSlice<T> {
448 #[cfg(target_os = "windows")]
449 fn drop(&mut self) {
450 use windows_sys::Win32::System::Memory::{MEM_RELEASE, VirtualFree};
451 unsafe {
452 debug_assert!(
453 VirtualFree(self.ptr as *mut _, 0, MEM_RELEASE) != 0,
454 "Failed to free huge page"
455 );
456 }
457 }
458
459 #[cfg(any(target_os = "android", target_os = "linux"))]
460 fn drop(&mut self) {
461 unsafe {
462 libc::munmap(self.ptr as *mut libc::c_void, self.capacity);
463 }
464 }
465
466 #[cfg(all(
467 not(target_os = "windows"),
468 not(target_os = "android"),
469 not(target_os = "linux")
470 ))]
471 fn drop(&mut self) {
472 }
474}
475
476#[cfg(feature = "alloc")]
477pub enum MaybeHugeSlice<T> {
479 #[cfg(feature = "huge-page")]
480 Huge(HugeSlice<T>),
482 Normal(alloc::boxed::Box<[T]>),
484}
485
486impl<T> core::ops::Deref for MaybeHugeSlice<T> {
487 type Target = [T];
488 fn deref(&self) -> &Self::Target {
489 self.as_ref()
490 }
491}
492
493impl<T> core::ops::DerefMut for MaybeHugeSlice<T> {
494 fn deref_mut(&mut self) -> &mut Self::Target {
495 self.as_mut()
496 }
497}
498
499impl<T> AsRef<[T]> for MaybeHugeSlice<T> {
500 fn as_ref(&self) -> &[T] {
501 match self {
502 #[cfg(feature = "huge-page")]
503 MaybeHugeSlice::Huge(b) => b.as_ref(),
504 MaybeHugeSlice::Normal(b) => b,
505 }
506 }
507}
508
509impl<T> core::convert::AsMut<[T]> for MaybeHugeSlice<T> {
510 fn as_mut(&mut self) -> &mut [T] {
511 match self {
512 #[cfg(feature = "huge-page")]
513 MaybeHugeSlice::Huge(b) => b.as_mut(),
514 MaybeHugeSlice::Normal(b) => b.as_mut(),
515 }
516 }
517}
518
519impl<T> MaybeHugeSlice<T> {
520 pub fn is_huge_page(&self) -> bool {
522 match self {
523 #[cfg(feature = "huge-page")]
524 MaybeHugeSlice::Huge(_) => true,
525 MaybeHugeSlice::Normal(_) => false,
526 }
527 }
528
529 #[cfg(feature = "huge-page")]
531 pub fn new_huge_slice_zeroed(len: usize) -> Result<Self, std::io::Error> {
532 let b: HugeSlice<core::mem::MaybeUninit<T>> = HugeSlice::new(len)?;
533 unsafe {
534 core::ptr::write_bytes(b.ptr.cast::<T>(), 0, len);
535 Ok(MaybeHugeSlice::Huge(b.assume_init()))
536 }
537 }
538
539 #[cfg(feature = "alloc")]
541 pub fn new_slice_zeroed(len: usize) -> Self {
542 let mut b = alloc::vec::Vec::<T>::with_capacity(len);
543 unsafe {
544 b.set_len(len);
545 MaybeHugeSlice::Normal(b.into())
546 }
547 }
548
549 #[cfg(feature = "alloc")]
551 pub fn new_maybe(len: usize) -> Self {
552 #[cfg(feature = "huge-page")]
553 {
554 match Self::new_huge_slice_zeroed(len) {
555 Ok(huge) => huge,
556 Err(_) => Self::new_slice_zeroed(len),
557 }
558 }
559
560 #[cfg(not(feature = "huge-page"))]
561 Self::new_slice_zeroed(len)
562 }
563
564 #[cfg(all(feature = "huge-page", target_os = "linux", feature = "std"))]
566 pub fn new_in(len: usize, file: std::fs::File) -> Result<Self, std::io::Error> {
567 let huge = HugeSlice::new_unix(len, Some(file))?;
568 Ok(Self::Huge(huge))
569 }
570
571 #[cfg(feature = "std")]
573 pub fn new(len: usize) -> (Self, Option<std::io::Error>) {
574 #[cfg(feature = "huge-page")]
575 {
576 match Self::new_huge_slice_zeroed(len) {
577 Ok(huge) => (huge, None),
578 Err(e) => (Self::new_slice_zeroed(len), Some(e.into())),
579 }
580 }
581
582 #[cfg(not(feature = "huge-page"))]
583 (Self::new_slice_zeroed(len), None)
584 }
585}