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 if let Some(file) = file {
298 let ptr = libc::mmap64(
299 core::ptr::null_mut(),
300 alloc_min_len,
301 libc::PROT_READ | libc::PROT_WRITE,
302 libc::MAP_PRIVATE,
303 std::os::unix::io::AsRawFd::as_raw_fd(&file),
304 0,
305 );
306 if ptr != libc::MAP_FAILED {
307 return Ok(HugeSlice {
308 ptr: ptr.cast::<T>(),
309 len,
310 capacity: alloc_min_len,
311 file,
312 });
313 }
314
315 return Err(std::io::Error::last_os_error());
316 }
317
318 for (try_page_size, try_flags) in [
319 #[cfg(target_os = "linux")]
320 ((1 << 30), libc::MFD_HUGE_1GB),
321 #[cfg(target_os = "linux")]
322 ((256 << 20), libc::MFD_HUGE_256MB),
323 #[cfg(target_os = "linux")]
324 ((32 << 20), libc::MFD_HUGE_32MB),
325 #[cfg(target_os = "linux")]
326 ((16 << 20), libc::MFD_HUGE_16MB),
327 #[cfg(target_os = "linux")]
328 ((8 << 20), libc::MFD_HUGE_8MB),
329 #[cfg(target_os = "linux")]
330 ((2 << 20), libc::MFD_HUGE_2MB),
331 ((page_size), 0),
332 ]
333 .into_iter()
334 .filter(|(try_page_size, _flags)| *try_page_size >= page_size)
335 .filter(|(try_page_size, flags)| *flags == 0 || alloc_min_len * 2 > *try_page_size)
337 {
338 let fd = libc::memfd_create(
339 c"scrypt-opt-huge-page-file".as_ptr().cast(),
340 libc::MFD_CLOEXEC | libc::MFD_HUGETLB | try_flags,
341 );
342
343 if fd == -1 {
344 continue;
345 }
346
347 let try_file = std::os::unix::io::FromRawFd::from_raw_fd(fd);
348
349 let try_size = alloc_min_len
350 .checked_next_multiple_of(try_page_size)
351 .ok_or(std::io::Error::new(
352 std::io::ErrorKind::Other,
353 format!(
354 "Allocation length overflow (requested: {}, max: {})",
355 alloc_min_len,
356 usize::MAX
357 ),
358 ))?;
359
360 let ptr = libc::mmap64(
361 core::ptr::null_mut(),
362 try_size,
363 libc::PROT_READ | libc::PROT_WRITE,
364 libc::MAP_PRIVATE | libc::MAP_POPULATE,
365 fd,
366 0,
367 );
368 if ptr != libc::MAP_FAILED {
369 return Ok(HugeSlice {
370 ptr: ptr.cast::<T>(),
371 len,
372 capacity: try_size,
373 file: try_file,
374 });
375 }
376 }
377
378 Err(std::io::Error::last_os_error())
379 }
380 }
381
382 #[cfg(all(
383 not(target_os = "windows"),
384 not(target_os = "android"),
385 not(target_os = "linux")
386 ))]
387 pub fn new(len: usize) -> Result<Self, std::io::Error> {
388 Err(std::io::Error::new(
389 std::io::ErrorKind::Other,
390 "Huge page not supported on this platform",
391 ))
392 }
393}
394
395#[cfg(feature = "huge-page")]
396impl<T> HugeSlice<core::mem::MaybeUninit<T>> {
397 pub unsafe fn assume_init(self) -> HugeSlice<T> {
399 let forgotten = core::mem::ManuallyDrop::new(self);
400 HugeSlice {
401 ptr: forgotten.ptr.cast::<T>(),
402 len: forgotten.len,
403 #[cfg(any(target_os = "android", target_os = "linux"))]
404 capacity: forgotten.capacity,
405 #[cfg(any(target_os = "android", target_os = "linux"))]
406 file: unsafe {
407 std::os::unix::io::FromRawFd::from_raw_fd(std::os::unix::io::AsRawFd::as_raw_fd(
408 &forgotten.file,
409 ))
410 },
411 }
412 }
413}
414
415#[cfg(feature = "huge-page")]
416impl<T> core::convert::AsRef<[T]> for HugeSlice<T> {
417 fn as_ref(&self) -> &[T] {
418 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
419 }
420}
421
422#[cfg(feature = "huge-page")]
423impl<T> core::convert::AsMut<[T]> for HugeSlice<T> {
424 fn as_mut(&mut self) -> &mut [T] {
425 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
426 }
427}
428
429#[cfg(feature = "huge-page")]
430impl<T> core::ops::Deref for HugeSlice<T> {
431 type Target = [T];
432 fn deref(&self) -> &Self::Target {
433 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
434 }
435}
436
437#[cfg(feature = "huge-page")]
438impl<T> core::ops::DerefMut for HugeSlice<T> {
439 fn deref_mut(&mut self) -> &mut Self::Target {
440 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
441 }
442}
443
444#[cfg(feature = "huge-page")]
445impl<T> Drop for HugeSlice<T> {
446 #[cfg(target_os = "windows")]
447 fn drop(&mut self) {
448 use windows_sys::Win32::System::Memory::{MEM_RELEASE, VirtualFree};
449 unsafe {
450 debug_assert!(
451 VirtualFree(self.ptr as *mut _, 0, MEM_RELEASE) != 0,
452 "Failed to free huge page"
453 );
454 }
455 }
456
457 #[cfg(any(target_os = "android", target_os = "linux"))]
458 fn drop(&mut self) {
459 unsafe {
460 libc::munmap(self.ptr as *mut libc::c_void, self.capacity);
461 }
462 }
463
464 #[cfg(all(
465 not(target_os = "windows"),
466 not(target_os = "android"),
467 not(target_os = "linux")
468 ))]
469 fn drop(&mut self) {
470 }
472}
473
474#[cfg(feature = "alloc")]
475pub enum MaybeHugeSlice<T> {
477 #[cfg(feature = "huge-page")]
478 Huge(HugeSlice<T>),
480 Normal(alloc::boxed::Box<[T]>),
482}
483
484impl<T> core::ops::Deref for MaybeHugeSlice<T> {
485 type Target = [T];
486 fn deref(&self) -> &Self::Target {
487 self.as_ref()
488 }
489}
490
491impl<T> core::ops::DerefMut for MaybeHugeSlice<T> {
492 fn deref_mut(&mut self) -> &mut Self::Target {
493 self.as_mut()
494 }
495}
496
497impl<T> AsRef<[T]> for MaybeHugeSlice<T> {
498 fn as_ref(&self) -> &[T] {
499 match self {
500 #[cfg(feature = "huge-page")]
501 MaybeHugeSlice::Huge(b) => b.as_ref(),
502 MaybeHugeSlice::Normal(b) => b,
503 }
504 }
505}
506
507impl<T> core::convert::AsMut<[T]> for MaybeHugeSlice<T> {
508 fn as_mut(&mut self) -> &mut [T] {
509 match self {
510 #[cfg(feature = "huge-page")]
511 MaybeHugeSlice::Huge(b) => b.as_mut(),
512 MaybeHugeSlice::Normal(b) => b.as_mut(),
513 }
514 }
515}
516
517impl<T> MaybeHugeSlice<T> {
518 pub fn is_huge_page(&self) -> bool {
520 match self {
521 #[cfg(feature = "huge-page")]
522 MaybeHugeSlice::Huge(_) => true,
523 MaybeHugeSlice::Normal(_) => false,
524 }
525 }
526
527 #[cfg(feature = "huge-page")]
529 pub fn new_huge_slice_zeroed(len: usize) -> Result<Self, std::io::Error> {
530 let b: HugeSlice<core::mem::MaybeUninit<T>> = HugeSlice::new(len)?;
531 unsafe {
532 core::ptr::write_bytes(b.ptr.cast::<T>(), 0, len);
533 Ok(MaybeHugeSlice::Huge(b.assume_init()))
534 }
535 }
536
537 #[cfg(feature = "alloc")]
539 pub fn new_slice_zeroed(len: usize) -> Self {
540 let mut b = alloc::vec::Vec::<T>::with_capacity(len);
541 unsafe {
542 b.set_len(len);
543 MaybeHugeSlice::Normal(b.into())
544 }
545 }
546
547 #[cfg(feature = "alloc")]
549 pub fn new_maybe(len: usize) -> Self {
550 #[cfg(feature = "huge-page")]
551 {
552 match Self::new_huge_slice_zeroed(len) {
553 Ok(huge) => huge,
554 Err(_) => Self::new_slice_zeroed(len),
555 }
556 }
557
558 #[cfg(not(feature = "huge-page"))]
559 Self::new_slice_zeroed(len)
560 }
561
562 #[cfg(all(feature = "huge-page", target_os = "linux", feature = "std"))]
564 pub fn new_in(len: usize, file: std::fs::File) -> Result<Self, std::io::Error> {
565 let huge = HugeSlice::new_unix(len, Some(file))?;
566 Ok(Self::Huge(huge))
567 }
568
569 #[cfg(feature = "std")]
571 pub fn new(len: usize) -> (Self, Option<std::io::Error>) {
572 #[cfg(feature = "huge-page")]
573 {
574 match Self::new_huge_slice_zeroed(len) {
575 Ok(huge) => (huge, None),
576 Err(e) => (Self::new_slice_zeroed(len), Some(e.into())),
577 }
578 }
579
580 #[cfg(not(feature = "huge-page"))]
581 (Self::new_slice_zeroed(len), None)
582 }
583}