1use std::fmt;
11
12#[derive(Debug, Clone, Copy)]
14pub struct AllocFailed {
15 pub requested_size: usize,
17 pub error_code: Option<i32>,
19 pub kind: AllocErrorKind,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AllocErrorKind {
26 OutOfMemory,
28 InvalidArgument,
30 PermissionDenied,
32 HugePagesUnavailable,
34 MlockFailed,
36 Unknown,
38}
39
40impl std::error::Error for AllocFailed {}
41
42impl fmt::Display for AllocFailed {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self.error_code {
45 Some(code) => write!(
46 f,
47 "Memory allocation failed ({:?}): requested {} bytes, error code {}",
48 self.kind, self.requested_size, code
49 ),
50 None => write!(
51 f,
52 "Memory allocation failed ({:?}): requested {} bytes",
53 self.kind, self.requested_size
54 ),
55 }
56 }
57}
58
59impl AllocFailed {
60 pub fn new(size: usize) -> Self {
62 Self {
63 requested_size: size,
64 error_code: None,
65 kind: AllocErrorKind::Unknown,
66 }
67 }
68
69 pub fn with_code(size: usize, code: i32) -> Self {
70 Self {
71 requested_size: size,
72 error_code: Some(code),
73 kind: AllocErrorKind::Unknown,
74 }
75 }
76
77 pub fn with_kind(size: usize, kind: AllocErrorKind) -> Self {
78 Self {
79 requested_size: size,
80 error_code: None,
81 kind,
82 }
83 }
84
85 pub fn out_of_memory(size: usize) -> Self {
86 Self::with_kind(size, AllocErrorKind::OutOfMemory)
87 }
88
89 pub fn huge_pages_unavailable(size: usize) -> Self {
90 Self::with_kind(size, AllocErrorKind::HugePagesUnavailable)
91 }
92
93 pub fn mlock_failed(size: usize) -> Self {
94 Self::with_kind(size, AllocErrorKind::MlockFailed)
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum HugePageSize {
101 Size2MB,
103 Size1GB,
105}
106
107impl HugePageSize {
108 pub fn bytes(&self) -> usize {
109 match self {
110 HugePageSize::Size2MB => 2 * 1024 * 1024,
111 HugePageSize::Size1GB => 1024 * 1024 * 1024,
112 }
113 }
114}
115
116#[cfg(feature = "guard-pages")]
118pub struct GuardedAlloc {
119 pub ptr: *mut u8,
121 pub total_size: usize,
123 pub usable_size: usize,
125 pub base_ptr: *mut u8,
127}
128
129pub mod sys {
131 use super::*;
132
133 #[cfg(target_os = "linux")]
139 #[inline]
140 pub fn alloc(size: usize) -> Result<*mut u8, AllocFailed> {
141 use rustix::mm::{mmap_anonymous, MapFlags, ProtFlags};
142 use std::ptr;
143
144 debug_assert!(size > 0);
145
146 unsafe {
147 match mmap_anonymous(
148 ptr::null_mut(),
149 size,
150 ProtFlags::READ | ProtFlags::WRITE,
151 MapFlags::PRIVATE | MapFlags::NORESERVE,
152 ) {
153 Ok(ptr) => {
154 let ptr = ptr as *mut u8;
156 if ptr.is_null() {
157 return Err(AllocFailed::out_of_memory(size));
158 }
159 Ok(ptr)
160 }
161 Err(_) => Err(AllocFailed::out_of_memory(size)),
162 }
163 }
164 }
165
166 #[cfg(all(target_os = "linux", feature = "huge-pages"))]
168 pub fn alloc_huge(size: usize, huge_size: HugePageSize) -> Result<*mut u8, AllocFailed> {
169 use rustix::mm::{mmap_anonymous, MapFlags, ProtFlags};
170 use std::ptr;
171
172 debug_assert!(size > 0);
173
174 let page_size = huge_size.bytes();
176 let aligned_size = (size + page_size - 1) & !(page_size - 1);
177
178 let huge_flag = match huge_size {
180 HugePageSize::Size2MB => 21 << 26, HugePageSize::Size1GB => 30 << 26, };
183
184 unsafe {
185 let flags = MapFlags::PRIVATE | MapFlags::from_bits_retain(0x40000 | huge_flag); match mmap_anonymous(
189 ptr::null_mut(),
190 aligned_size,
191 ProtFlags::READ | ProtFlags::WRITE,
192 flags,
193 ) {
194 Ok(ptr) => Ok(ptr as *mut u8),
195 Err(_) => Err(AllocFailed::huge_pages_unavailable(size)),
196 }
197 }
198 }
199
200 #[cfg(all(target_os = "linux", feature = "guard-pages"))]
202 pub fn alloc_with_guards(size: usize) -> Result<GuardedAlloc, AllocFailed> {
203 use rustix::mm::{mmap_anonymous, mprotect, MapFlags, MprotectFlags, ProtFlags};
204 use std::ptr;
205
206 const PAGE_SIZE: usize = 4096;
207
208 let total_size = PAGE_SIZE + size + PAGE_SIZE;
210
211 unsafe {
212 let base = match mmap_anonymous(
213 ptr::null_mut(),
214 total_size,
215 ProtFlags::READ | ProtFlags::WRITE,
216 MapFlags::PRIVATE,
217 ) {
218 Ok(ptr) => ptr as *mut u8,
219 Err(_) => return Err(AllocFailed::out_of_memory(size)),
220 };
221
222 if mprotect(base as *mut _, PAGE_SIZE, MprotectFlags::empty()).is_err() {
224 let _ = dealloc(base, total_size);
225 return Err(AllocFailed::with_kind(
226 size,
227 AllocErrorKind::PermissionDenied,
228 ));
229 }
230
231 let rear_guard = base.add(PAGE_SIZE + size);
233 if mprotect(rear_guard as *mut _, PAGE_SIZE, MprotectFlags::empty()).is_err() {
234 let _ = dealloc(base, total_size);
235 return Err(AllocFailed::with_kind(
236 size,
237 AllocErrorKind::PermissionDenied,
238 ));
239 }
240
241 Ok(GuardedAlloc {
242 ptr: base.add(PAGE_SIZE),
243 total_size,
244 usable_size: size,
245 base_ptr: base,
246 })
247 }
248 }
249
250 #[cfg(all(target_os = "linux", feature = "mlock"))]
252 pub fn mlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
253 unsafe {
254 if libc::mlock(ptr as *const _, size) == 0 {
255 Ok(())
256 } else {
257 Err(AllocFailed::mlock_failed(size))
258 }
259 }
260 }
261
262 #[cfg(all(target_os = "linux", feature = "mlock"))]
264 pub fn munlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
265 unsafe {
266 if libc::munlock(ptr as *const _, size) == 0 {
267 Ok(())
268 } else {
269 Err(AllocFailed::mlock_failed(size))
270 }
271 }
272 }
273
274 #[cfg(target_os = "linux")]
276 #[inline]
277 pub fn dealloc(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
278 use rustix::mm::munmap;
279
280 if ptr.is_null() {
281 return Ok(());
282 }
283
284 unsafe {
285 match munmap(ptr as *mut _, size) {
286 Ok(()) => Ok(()),
287 Err(_) => Err(AllocFailed::new(size)),
288 }
289 }
290 }
291
292 #[cfg(target_vendor = "apple")]
297 #[inline]
298 pub fn alloc(size: usize) -> Result<*mut u8, AllocFailed> {
299 use mach2::kern_return::KERN_SUCCESS;
300 use mach2::traps::mach_task_self;
301 use mach2::vm::mach_vm_allocate;
302 use mach2::vm_statistics::VM_FLAGS_ANYWHERE;
303 use mach2::vm_types::{mach_vm_address_t, mach_vm_size_t};
304
305 debug_assert!(size > 0);
306
307 let task = unsafe { mach_task_self() };
308 let mut address: mach_vm_address_t = 0;
309 let vm_size: mach_vm_size_t = size as mach_vm_size_t;
310
311 let retval = unsafe { mach_vm_allocate(task, &mut address, vm_size, VM_FLAGS_ANYWHERE) };
312
313 if retval == KERN_SUCCESS {
314 let ptr = address as *mut u8;
316 if ptr.is_null() {
317 return Err(AllocFailed::out_of_memory(size));
318 }
319 Ok(ptr)
320 } else {
321 Err(AllocFailed::with_code(size, retval))
322 }
323 }
324
325 #[cfg(all(target_vendor = "apple", feature = "mlock"))]
327 pub fn mlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
328 unsafe {
329 if libc::mlock(ptr as *const _, size) == 0 {
330 Ok(())
331 } else {
332 Err(AllocFailed::mlock_failed(size))
333 }
334 }
335 }
336
337 #[cfg(all(target_vendor = "apple", feature = "mlock"))]
338 pub fn munlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
339 unsafe {
340 if libc::munlock(ptr as *const _, size) == 0 {
341 Ok(())
342 } else {
343 Err(AllocFailed::mlock_failed(size))
344 }
345 }
346 }
347
348 #[cfg(all(target_vendor = "apple", feature = "guard-pages"))]
350 pub fn alloc_with_guards(size: usize) -> Result<GuardedAlloc, AllocFailed> {
351 use mach2::kern_return::KERN_SUCCESS;
352 use mach2::traps::mach_task_self;
353 use mach2::vm::{mach_vm_allocate, mach_vm_protect};
354 use mach2::vm_prot::VM_PROT_NONE;
355 use mach2::vm_statistics::VM_FLAGS_ANYWHERE;
356 use mach2::vm_types::{mach_vm_address_t, mach_vm_size_t};
357
358 const PAGE_SIZE: usize = 4096;
359 let total_size = PAGE_SIZE + size + PAGE_SIZE;
360
361 let task = unsafe { mach_task_self() };
362 let mut address: mach_vm_address_t = 0;
363
364 let retval = unsafe {
365 mach_vm_allocate(
366 task,
367 &mut address,
368 total_size as mach_vm_size_t,
369 VM_FLAGS_ANYWHERE,
370 )
371 };
372
373 if retval != KERN_SUCCESS {
374 return Err(AllocFailed::out_of_memory(size));
375 }
376
377 let base = address as *mut u8;
378
379 unsafe {
380 let ret = mach_vm_protect(task, address, PAGE_SIZE as mach_vm_size_t, 0, VM_PROT_NONE);
382 if ret != KERN_SUCCESS {
383 let _ = dealloc(base, total_size);
384 return Err(AllocFailed::with_kind(
385 size,
386 AllocErrorKind::PermissionDenied,
387 ));
388 }
389
390 let rear_addr = address + (PAGE_SIZE + size) as u64;
392 let ret = mach_vm_protect(
393 task,
394 rear_addr,
395 PAGE_SIZE as mach_vm_size_t,
396 0,
397 VM_PROT_NONE,
398 );
399 if ret != KERN_SUCCESS {
400 let _ = dealloc(base, total_size);
401 return Err(AllocFailed::with_kind(
402 size,
403 AllocErrorKind::PermissionDenied,
404 ));
405 }
406 }
407
408 Ok(GuardedAlloc {
409 ptr: unsafe { base.add(PAGE_SIZE) },
410 total_size,
411 usable_size: size,
412 base_ptr: base,
413 })
414 }
415
416 #[cfg(target_vendor = "apple")]
418 #[inline]
419 pub fn dealloc(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
420 use mach2::kern_return::KERN_SUCCESS;
421 use mach2::traps::mach_task_self;
422 use mach2::vm::mach_vm_deallocate;
423 use mach2::vm_types::mach_vm_size_t;
424
425 if ptr.is_null() {
426 return Ok(());
427 }
428
429 let task = unsafe { mach_task_self() };
430 let retval = unsafe { mach_vm_deallocate(task, ptr as u64, size as mach_vm_size_t) };
431
432 if retval == KERN_SUCCESS {
433 Ok(())
434 } else {
435 Err(AllocFailed::with_code(size, retval))
436 }
437 }
438
439 #[cfg(target_os = "windows")]
444 #[inline]
445 pub fn alloc(size: usize) -> Result<*mut u8, AllocFailed> {
446 use std::ptr;
447
448 const MEM_COMMIT: u32 = 0x00001000;
449 const MEM_RESERVE: u32 = 0x00002000;
450 const PAGE_READWRITE: u32 = 0x04;
451
452 extern "system" {
453 fn VirtualAlloc(
454 lpAddress: *mut u8,
455 dwSize: usize,
456 flAllocationType: u32,
457 flProtect: u32,
458 ) -> *mut u8;
459 }
460
461 debug_assert!(size > 0);
462
463 let result = unsafe {
464 VirtualAlloc(
465 ptr::null_mut(),
466 size,
467 MEM_COMMIT | MEM_RESERVE,
468 PAGE_READWRITE,
469 )
470 };
471
472 if result.is_null() {
473 Err(AllocFailed::out_of_memory(size))
474 } else {
475 Ok(result)
476 }
477 }
478
479 #[cfg(all(target_os = "windows", feature = "guard-pages"))]
481 pub fn alloc_with_guards(size: usize) -> Result<GuardedAlloc, AllocFailed> {
482 use std::ptr;
483
484 const MEM_COMMIT: u32 = 0x00001000;
485 const MEM_RESERVE: u32 = 0x00002000;
486 const PAGE_READWRITE: u32 = 0x04;
487 const PAGE_NOACCESS: u32 = 0x01;
488 const PAGE_SIZE: usize = 4096;
489
490 extern "system" {
491 fn VirtualAlloc(
492 lpAddress: *mut u8,
493 dwSize: usize,
494 flAllocationType: u32,
495 flProtect: u32,
496 ) -> *mut u8;
497 fn VirtualProtect(
498 lpAddress: *mut u8,
499 dwSize: usize,
500 flNewProtect: u32,
501 lpflOldProtect: *mut u32,
502 ) -> i32;
503 }
504
505 let total_size = PAGE_SIZE + size + PAGE_SIZE;
506
507 let base = unsafe {
508 VirtualAlloc(
509 ptr::null_mut(),
510 total_size,
511 MEM_COMMIT | MEM_RESERVE,
512 PAGE_READWRITE,
513 )
514 };
515
516 if base.is_null() {
517 return Err(AllocFailed::out_of_memory(size));
518 }
519
520 unsafe {
521 let mut old_protect: u32 = 0;
522
523 if VirtualProtect(base, PAGE_SIZE, PAGE_NOACCESS, &mut old_protect) == 0 {
525 let _ = dealloc(base, total_size);
526 return Err(AllocFailed::with_kind(
527 size,
528 AllocErrorKind::PermissionDenied,
529 ));
530 }
531
532 let rear_guard = base.add(PAGE_SIZE + size);
534 if VirtualProtect(rear_guard, PAGE_SIZE, PAGE_NOACCESS, &mut old_protect) == 0 {
535 let _ = dealloc(base, total_size);
536 return Err(AllocFailed::with_kind(
537 size,
538 AllocErrorKind::PermissionDenied,
539 ));
540 }
541 }
542
543 Ok(GuardedAlloc {
544 ptr: unsafe { base.add(PAGE_SIZE) },
545 total_size,
546 usable_size: size,
547 base_ptr: base,
548 })
549 }
550
551 #[cfg(all(target_os = "windows", feature = "mlock"))]
553 pub fn mlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
554 extern "system" {
555 fn VirtualLock(lpAddress: *mut u8, dwSize: usize) -> i32;
556 }
557
558 unsafe {
559 if VirtualLock(ptr, size) != 0 {
560 Ok(())
561 } else {
562 Err(AllocFailed::mlock_failed(size))
563 }
564 }
565 }
566
567 #[cfg(all(target_os = "windows", feature = "mlock"))]
568 pub fn munlock(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
569 extern "system" {
570 fn VirtualUnlock(lpAddress: *mut u8, dwSize: usize) -> i32;
571 }
572
573 unsafe {
574 if VirtualUnlock(ptr, size) != 0 {
575 Ok(())
576 } else {
577 Err(AllocFailed::mlock_failed(size))
578 }
579 }
580 }
581
582 #[cfg(target_os = "windows")]
584 #[inline]
585 pub fn dealloc(ptr: *mut u8, _size: usize) -> Result<(), AllocFailed> {
586 const MEM_RELEASE: u32 = 0x00008000;
587
588 extern "system" {
589 fn VirtualFree(lpAddress: *mut u8, dwSize: usize, dwFreeType: u32) -> i32;
590 }
591
592 if ptr.is_null() {
593 return Ok(());
594 }
595
596 let result = unsafe { VirtualFree(ptr, 0, MEM_RELEASE) };
598
599 if result != 0 {
600 Ok(())
601 } else {
602 Err(AllocFailed::new(0))
603 }
604 }
605
606 #[cfg(all(
612 not(target_os = "linux"),
613 not(target_vendor = "apple"),
614 not(target_os = "windows"),
615 unix
616 ))]
617 #[inline]
618 pub fn alloc(size: usize) -> Result<*mut u8, AllocFailed> {
619 use libc::{mmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE};
620 use std::ptr;
621
622 debug_assert!(size > 0);
623
624 let result = unsafe {
625 mmap(
626 ptr::null_mut(),
627 size,
628 PROT_READ | PROT_WRITE,
629 MAP_PRIVATE | MAP_ANON,
630 -1,
631 0,
632 )
633 };
634
635 if result == MAP_FAILED {
636 Err(AllocFailed::out_of_memory(size))
637 } else {
638 Ok(result as *mut u8)
639 }
640 }
641
642 #[cfg(all(
644 not(target_os = "linux"),
645 not(target_vendor = "apple"),
646 not(target_os = "windows"),
647 unix
648 ))]
649 #[inline]
650 pub fn dealloc(ptr: *mut u8, size: usize) -> Result<(), AllocFailed> {
651 use libc::munmap;
652
653 if ptr.is_null() {
654 return Ok(());
655 }
656
657 let result = unsafe { munmap(ptr as *mut _, size) };
658
659 if result == 0 {
660 Ok(())
661 } else {
662 Err(AllocFailed::new(size))
663 }
664 }
665
666 #[cfg(feature = "guard-pages")]
672 pub fn dealloc_guarded(guarded: &GuardedAlloc) -> Result<(), AllocFailed> {
673 dealloc(guarded.base_ptr, guarded.total_size)
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn test_alloc_dealloc_roundtrip() {
683 let size = 4096;
684 let ptr = sys::alloc(size).expect("allocation should succeed");
685
686 assert!(!ptr.is_null());
687
688 unsafe {
690 std::ptr::write_bytes(ptr, 0xAB, size);
691 }
692
693 sys::dealloc(ptr, size).expect("deallocation should succeed");
694 }
695
696 #[test]
697 fn test_large_allocation() {
698 let size = 64 * 1024 * 1024; let ptr = sys::alloc(size).expect("large allocation should succeed");
700
701 assert!(!ptr.is_null());
702
703 unsafe {
705 *ptr = 0x42;
706 *ptr.add(size - 1) = 0x42;
707 }
708
709 sys::dealloc(ptr, size).expect("deallocation should succeed");
710 }
711
712 #[test]
713 fn test_alloc_failed_display() {
714 let err = AllocFailed::new(1024);
715 let msg = format!("{}", err);
716 assert!(msg.contains("1024"));
717 }
718
719 #[test]
720 fn test_alloc_error_kinds() {
721 let err = AllocFailed::out_of_memory(1024);
722 assert_eq!(err.kind, AllocErrorKind::OutOfMemory);
723
724 let err = AllocFailed::huge_pages_unavailable(1024);
725 assert_eq!(err.kind, AllocErrorKind::HugePagesUnavailable);
726
727 let err = AllocFailed::mlock_failed(1024);
728 assert_eq!(err.kind, AllocErrorKind::MlockFailed);
729 }
730
731 #[test]
732 #[cfg(all(target_os = "linux", feature = "guard-pages"))]
733 fn test_guard_pages() {
734 let size = 4096;
735 let guarded = sys::alloc_with_guards(size).expect("guarded allocation should succeed");
736
737 assert!(!guarded.ptr.is_null());
738 assert_eq!(guarded.usable_size, size);
739 assert!(guarded.total_size > size);
740
741 unsafe {
743 std::ptr::write_bytes(guarded.ptr, 0xAB, size);
744 }
745
746 sys::dealloc_guarded(&guarded).expect("deallocation should succeed");
747 }
748}