Skip to main content

alloc_madvise/
memory.rs

1//! # Platform Support
2//!
3//! This crate uses `madvise(2)` for memory hints, which is a POSIX/Linux-specific system call.
4//! The following advice flags are used:
5//!
6//! - `MADV_SEQUENTIAL`: Hints that memory will be accessed sequentially
7//! - `MADV_HUGEPAGE`: Hints to use transparent huge pages
8//!
9//! On non-Linux platforms (macOS, Windows), these calls may be no-ops or behave differently.
10//! For Windows support, consider using platform-specific allocation APIs directly.
11//!
12//! The `Memory` struct represents an allocated memory block with various allocation flags and methods for allocation and deallocation.
13//!
14//! # Constants
15//! - `ALLOC_FLAGS_NONE`: No special instructions.
16//! - `ALLOC_FLAGS_HUGE_PAGES`: Indicates that huge pages should be used.
17//! - `ALLOC_FLAGS_SEQUENTIAL`: Indicates that memory access is mainly sequential rather than random-access.
18//!
19//! # Structs
20//! - `Memory`: Represents an allocated memory block with methods for allocation, deallocation, and accessing the memory as slices.
21//!
22//! # Methods
23//! - `Memory::allocate`: Allocates memory of the specified number of bytes with optional sequential access pattern and zeroing out.
24//! - `Memory::free`: Frees the allocated memory.
25//! - `Memory::len`: Returns the number of bytes allocated.
26//! - `Memory::is_empty`: Returns whether this instance has zero bytes allocated.
27//! - `Memory::as_ptr`: Returns a pointer to the data buffer.
28//! - `Memory::as_ptr_mut`: Returns a mutable pointer to the data buffer.
29//!
30//! # Macros
31//! - `impl_asref_slice`: Implements `AsRef` and `AsMut` traits for slices of various types.
32//!
33//! # Examples
34//! ```
35//! # use alloc_madvise::Memory;
36//! const FOUR_MEGABYTES: usize = 4 * 1024 * 1024;
37//!
38//! // Allocate 2 MiB of aligned, zeroed-out, sequential read memory.
39//! // The memory will be automatically freed when it leaves scope.
40//! let mut memory = Memory::allocate(FOUR_MEGABYTES, true, true).unwrap();
41//!
42//! // Get a reference to a mutable slice.
43//! let data: &mut [f32] = memory.as_mut();
44//! data[0] = 1.234;
45//! data[1] = 5.678;
46//!
47//! // Get a reference to an immutable slice.
48//! let reference: &[f32] = memory.as_ref();
49//! assert_eq!(reference[0], 1.234);
50//! assert_eq!(reference[1], 5.678);
51//! assert_eq!(reference[2], 0.0);
52//! assert_eq!(reference.len(), memory.len() / std::mem::size_of::<f32>());
53//! ```
54//!
55//! # Safety
56//! - The `madvise` function is used to give advice about the use of memory. The safety of this function relies on the correctness of the pointer and size provided.
57//! - The `free` method ensures that the memory is properly deallocated and the fields are zeroed out to prevent use-after-free errors.
58
59use crate::alignment::AlignmentHint;
60use crate::alloc_free::{alloc_aligned, free_aligned};
61use crate::alloc_result::{AllocResult, AllocationError};
62use libc::madvise;
63use std::ffi::c_void;
64use std::ptr::{null_mut, NonNull};
65
66/// No special instructions.
67const ALLOC_FLAGS_NONE: u32 = 0;
68
69/// Indicates that huge pages should be used.
70const ALLOC_FLAGS_HUGE_PAGES: u32 = 1 << 0;
71
72/// Indicates that memory access is mainly sequential rather than random-access.
73const ALLOC_FLAGS_SEQUENTIAL: u32 = 1 << 1;
74
75/// Configuration for memory allocation.
76///
77/// # Example
78/// ```
79/// use alloc_madvise::Memory;
80///
81/// let memory = Memory::builder(1024)
82///     .sequential()
83///     .zeroed()
84///     .allocate()
85///     .expect("allocation failed");
86/// ```
87#[derive(Debug, Clone, Copy)]
88pub struct AllocationConfig {
89    num_bytes: usize,
90    sequential: bool,
91    zeroed: bool,
92}
93
94impl AllocationConfig {
95    /// Creates a new allocation configuration for the given number of bytes.
96    pub fn new(num_bytes: usize) -> Self {
97        Self {
98            num_bytes,
99            sequential: false,
100            zeroed: false,
101        }
102    }
103
104    /// Enables sequential access pattern hint for `madvise`.
105    #[must_use]
106    pub fn sequential(mut self) -> Self {
107        self.sequential = true;
108        self
109    }
110
111    /// Enables zeroing out the allocated memory.
112    #[must_use]
113    pub fn zeroed(mut self) -> Self {
114        self.zeroed = true;
115        self
116    }
117
118    /// Performs the allocation with this configuration.
119    pub fn allocate(self) -> Result<Memory, AllocationError> {
120        Memory::allocate(self.num_bytes, self.sequential, self.zeroed)
121    }
122}
123
124/// Allocated memory.
125///
126/// ## Example
127/// ```
128/// # use alloc_madvise::Memory;
129/// const FOUR_MEGABYTES: usize = 4 * 1024 * 1024;
130///
131/// // Allocate 2 MiB of aligned, zeroed-out, sequential read memory.
132/// // The memory will be automatically freed when it leaves scope.
133/// let mut memory = Memory::allocate(FOUR_MEGABYTES, true, true).unwrap();
134///
135/// // Get a reference to a mutable slice.
136/// let data: &mut [f32] = memory.as_mut();
137/// data[0] = 1.234;
138/// data[1] = 5.678;
139///
140/// // Get a reference to an immutable slice.
141/// let reference: &[f32] = memory.as_ref();
142/// assert_eq!(reference[0], 1.234);
143/// assert_eq!(reference[1], 5.678);
144/// assert_eq!(reference[2], 0.0);
145/// assert_eq!(reference.len(), memory.len() / std::mem::size_of::<f32>());
146/// ```
147#[derive(Debug)]
148pub struct Memory {
149    pub(crate) flags: u32,
150    pub(crate) num_bytes: usize,
151    pub(crate) address: *mut c_void,
152}
153
154impl Memory {
155    /// Creates a builder for configuring memory allocation.
156    ///
157    /// # Example
158    /// ```
159    /// use alloc_madvise::Memory;
160    ///
161    /// let memory = Memory::builder(1024)
162    ///     .sequential()
163    ///     .zeroed()
164    ///     .allocate()
165    ///     .expect("allocation failed");
166    /// ```
167    pub fn builder(num_bytes: usize) -> AllocationConfig {
168        AllocationConfig::new(num_bytes)
169    }
170
171    /// Allocates memory of the specified number of bytes.
172    ///
173    /// The optimal alignment will be determined by the number of bytes provided.
174    /// If the amount of bytes is a multiple of 2MB, Huge/Large Page support is enabled.
175    ///
176    /// ## Arguments
177    /// * `num_bytes` - The number of bytes to allocate.
178    /// * `sequential` - Whether or not the memory access pattern is sequential mostly.
179    /// * `clear` - Whether or not to zero out the allocated memory.
180    pub fn allocate(
181        num_bytes: usize,
182        sequential: bool,
183        clear: bool,
184    ) -> Result<Self, AllocationError> {
185        if num_bytes == 0 {
186            return Err(AllocationError::EmptyAllocation);
187        }
188
189        let alignment = AlignmentHint::new(num_bytes);
190        let ptr = alloc_aligned(num_bytes, alignment.alignment, clear)?;
191
192        let ptr: *mut c_void = ptr.as_ptr().cast::<c_void>();
193
194        let mut advice = if sequential {
195            libc::MADV_SEQUENTIAL
196        } else {
197            libc::MADV_NORMAL
198        };
199
200        let mut flags = if sequential {
201            ALLOC_FLAGS_SEQUENTIAL
202        } else {
203            ALLOC_FLAGS_NONE
204        };
205
206        if alignment.use_huge_pages {
207            advice |= libc::MADV_HUGEPAGE;
208            flags |= ALLOC_FLAGS_HUGE_PAGES;
209        };
210
211        if advice != 0 {
212            // See https://www.man7.org/linux/man-pages/man2/madvise.2.html
213            // SAFETY: `ptr` came from alloc_aligned(num_bytes, alignment)
214            //
215            // Note: madvise() returns advisory hints. Failures are non-fatal
216            // (e.g., huge pages not configured, unsupported advice on some kernels).
217            // We intentionally ignore the return value.
218            unsafe {
219                madvise(ptr, num_bytes, advice);
220            }
221        }
222
223        Ok(Self::new(AllocResult::Ok, flags, num_bytes, ptr))
224    }
225
226    /// Frees memory of the specified number of bytes.
227    ///
228    /// The memory instance is required to be created by `allocate`.
229    pub fn free(&mut self) {
230        if self.address.is_null() {
231            return;
232        }
233
234        let alignment = AlignmentHint::new(self.num_bytes);
235
236        debug_assert_ne!(self.address, null_mut());
237        let ptr = core::ptr::NonNull::new(self.address);
238
239        if (self.flags & ALLOC_FLAGS_HUGE_PAGES) == ALLOC_FLAGS_HUGE_PAGES {
240            debug_assert!(alignment.use_huge_pages);
241
242            // See https://www.man7.org/linux/man-pages/man2/madvise.2.html
243            // SAFETY: `ptr` came from alloc_aligned(num_bytes, alignment)
244            unsafe {
245                madvise(self.address, self.num_bytes, libc::MADV_FREE);
246            }
247        }
248
249        // SAFETY:
250        // - `ptr` is checked for null before
251        // - `num_bytes` and `alignment` are required to be correct by the caller
252        unsafe {
253            free_aligned(ptr, self.num_bytes, alignment.alignment);
254        }
255
256        // Zero out the fields.
257        self.address = null_mut();
258        self.num_bytes = 0;
259    }
260
261    pub(crate) fn new(
262        status: AllocResult,
263        flags: u32,
264        num_bytes: usize,
265        address: *mut c_void,
266    ) -> Self {
267        debug_assert!(
268            (status == AllocResult::Ok) != address.is_null(),
269            "Allocation status and pointer nullness must agree"
270        );
271        Memory {
272            flags,
273            num_bytes,
274            address,
275        }
276    }
277
278    pub(crate) fn from_error(status: AllocResult) -> Self {
279        assert_ne!(status, AllocResult::Ok);
280        Memory {
281            flags: 0,
282            num_bytes: 0,
283            address: null_mut(),
284        }
285    }
286
287    /// Returns the number of bytes allocated.
288    pub fn len(&self) -> usize {
289        self.num_bytes
290    }
291
292    /// Returns whether this instance has zero bytes allocated.
293    pub fn is_empty(&self) -> bool {
294        debug_assert!(self.num_bytes > 0 || self.address.is_null());
295        self.num_bytes == 0
296    }
297
298    /// See [`Memory::to_ptr_const`] or [`Memory::to_ptr`].
299    #[deprecated(note = "Use to_const_ptr or to_ptr instead", since = "0.5.0")]
300    pub fn as_ptr(&self) -> *const c_void {
301        self.to_ptr_const()
302    }
303
304    /// Returns a pointer to the constant data buffer.
305    ///
306    /// ## Returns
307    /// A valid pointer.
308    ///
309    /// ## Safety
310    /// If the memory is freed while the pointer is in use, access to the address pointed
311    /// at is undefined behavior.
312    pub fn to_ptr_const(&self) -> *const c_void {
313        self.address.cast_const()
314    }
315
316    /// See [`Memory::to_ptr_mut`] or [`Memory::to_ptr`].
317    #[deprecated(note = "Use to_ptr_mut or to_ptr instead", since = "0.5.0")]
318    pub fn as_ptr_mut(&mut self) -> *mut c_void {
319        self.to_ptr_mut()
320    }
321
322    /// Returns a mutable pointer to the mutable data buffer.
323    ///
324    /// ## Returns
325    /// A valid pointer.
326    ///
327    /// ## Safety
328    /// If the memory is freed while the pointer is in use, access to the address pointed
329    /// at is undefined behavior.
330    pub fn to_ptr_mut(&mut self) -> *mut c_void {
331        self.address
332    }
333
334    /// Returns a non-null pointer to the data buffer.
335    ///
336    /// ## Returns
337    /// A pointer that is guaranteed to be non-null if the [`Memory`] was properly
338    /// initialized (i.e., is non-default) and wasn't freed.
339    ///
340    /// ## Safety
341    /// If the memory is freed while the pointer is in use, access to the address pointed
342    /// at is undefined behavior.
343    ///
344    /// # Example
345    ///
346    /// ```
347    /// use alloc_madvise::{Memory, AllocationError};
348    ///
349    /// fn main() -> Result<(), AllocationError> {
350    ///     // Allocate 1024 bytes aligned to 64 bytes
351    ///     const SIZE: usize = 1024;
352    ///     const SEQUENTIAL: bool = true;
353    ///     const CLEAR: bool = true;
354    ///     let memory = Memory::allocate(SIZE, SEQUENTIAL, CLEAR)?;
355    ///     let ptr = memory.to_ptr().expect("pointer was allocated");
356    ///     
357    ///     // Use the allocated memory...
358    ///     assert_ne!(ptr.as_ptr(), std::ptr::null_mut());
359    ///     
360    ///     // Memory is automatically freed when dropped
361    ///     Ok(())
362    /// }
363    /// ```
364    pub fn to_ptr(&self) -> Option<NonNull<c_void>> {
365        NonNull::new(self.address)
366    }
367}
368
369impl Default for Memory {
370    fn default() -> Self {
371        Memory::from_error(AllocResult::Empty)
372    }
373}
374
375impl Drop for Memory {
376    fn drop(&mut self) {
377        self.free()
378    }
379}
380
381// SAFETY: Memory owns its allocation and provides thread-safe access:
382// - Shared references give read-only access (safe for concurrent reads)
383// - Mutable references give exclusive write access (enforced by borrow checker)
384unsafe impl Send for Memory {}
385unsafe impl Sync for Memory {}
386
387/// Implements AsRef and AsMut
388macro_rules! impl_asref_slice {
389    ($type:ty) => {
390        impl AsRef<[$type]> for Memory {
391            fn as_ref(&self) -> &[$type] {
392                let ptr: *const $type = self.address.cast();
393                let len = self.num_bytes / std::mem::size_of::<$type>();
394                unsafe { &*std::ptr::slice_from_raw_parts(ptr, len) }
395            }
396        }
397
398        impl AsMut<[$type]> for Memory {
399            fn as_mut(&mut self) -> &mut [$type] {
400                let ptr: *mut $type = self.address.cast();
401                let len = self.num_bytes / std::mem::size_of::<$type>();
402                unsafe { &mut *std::ptr::slice_from_raw_parts_mut(ptr, len) }
403            }
404        }
405    };
406    ($first:ty, $($rest:ty),+) => {
407        impl_asref_slice!($first);
408        impl_asref_slice!($($rest),+);
409    };
410}
411
412impl_asref_slice!(c_void);
413impl_asref_slice!(i8, u8, i16, u16, i32, u32, i64, u64);
414impl_asref_slice!(isize, usize);
415impl_asref_slice!(f32, f64);
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    const TWO_MEGABYTES: usize = 2 * 1024 * 1024;
422    const SIXTY_FOUR_BYTES: usize = 64;
423
424    #[test]
425    fn alloc_4mb_is_2mb_aligned_hugepage() {
426        const SIZE: usize = TWO_MEGABYTES * 2;
427        let memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
428
429        assert_ne!(memory.address, null_mut());
430        assert_eq!((memory.address as usize) % TWO_MEGABYTES, 0);
431        assert_eq!(memory.len(), SIZE);
432        assert!(!memory.is_empty());
433        assert_eq!(
434            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
435            ALLOC_FLAGS_HUGE_PAGES
436        );
437        assert_eq!(
438            memory.flags & ALLOC_FLAGS_SEQUENTIAL,
439            ALLOC_FLAGS_SEQUENTIAL
440        );
441    }
442
443    #[test]
444    fn alloc_4mb_nonsequential_is_2mb_aligned_hugepage() {
445        const SIZE: usize = TWO_MEGABYTES * 2;
446        let memory = Memory::allocate(SIZE, false, false).expect("allocation failed");
447
448        assert_ne!(memory.address, null_mut());
449        assert_eq!((memory.address as usize) % TWO_MEGABYTES, 0);
450        assert_eq!(memory.len(), SIZE);
451        assert!(!memory.is_empty());
452        assert_eq!(
453            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
454            ALLOC_FLAGS_HUGE_PAGES
455        );
456        assert_ne!(
457            memory.flags & ALLOC_FLAGS_SEQUENTIAL,
458            ALLOC_FLAGS_SEQUENTIAL
459        );
460    }
461
462    #[test]
463    fn alloc_2mb_is_2mb_aligned_hugepage() {
464        const SIZE: usize = TWO_MEGABYTES;
465        let memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
466
467        assert_ne!(memory.address, null_mut());
468        assert_eq!((memory.address as usize) % TWO_MEGABYTES, 0);
469        assert_eq!(memory.len(), SIZE);
470        assert!(!memory.is_empty());
471        assert_eq!(
472            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
473            ALLOC_FLAGS_HUGE_PAGES
474        );
475    }
476
477    #[test]
478    fn alloc_1mb_is_64b_aligned() {
479        const SIZE: usize = TWO_MEGABYTES / 2;
480        let memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
481
482        assert_ne!(memory.address, null_mut());
483        assert_eq!((memory.address as usize) % SIXTY_FOUR_BYTES, 0);
484        assert_eq!(memory.len(), SIZE);
485        assert!(!memory.is_empty());
486        assert_ne!(
487            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
488            ALLOC_FLAGS_HUGE_PAGES
489        );
490    }
491
492    #[test]
493    fn alloc_63kb_is_64b_aligned() {
494        const SIZE: usize = 63 * 1024;
495        let memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
496
497        assert_ne!(memory.address, null_mut());
498        assert_eq!((memory.address as usize) % SIXTY_FOUR_BYTES, 0);
499        assert_eq!(memory.len(), SIZE);
500        assert!(!memory.is_empty());
501        assert_ne!(
502            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
503            ALLOC_FLAGS_HUGE_PAGES
504        );
505    }
506
507    #[test]
508    fn alloc_64kb_is_64b_aligned() {
509        const SIZE: usize = 64 * 1024;
510        let memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
511
512        assert_ne!(memory.address, null_mut());
513        assert_eq!((memory.address as usize) % SIXTY_FOUR_BYTES, 0);
514        assert_eq!(memory.len(), SIZE);
515        assert!(!memory.is_empty());
516        assert_ne!(
517            memory.flags & ALLOC_FLAGS_HUGE_PAGES,
518            ALLOC_FLAGS_HUGE_PAGES
519        );
520    }
521
522    #[test]
523    fn alloc_0b_is_not_allocated() {
524        const SIZE: usize = 0;
525        let err = Memory::allocate(SIZE, true, true).expect_err("the allocation was empty");
526
527        assert_eq!(err, AllocationError::EmptyAllocation);
528    }
529
530    #[test]
531    fn deref_works() {
532        const SIZE: usize = TWO_MEGABYTES * 2;
533        let mut memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
534
535        let addr: *mut u8 = memory.to_ptr_mut() as *mut u8;
536        unsafe {
537            *addr = 0x42;
538        }
539
540        let reference: &[u8] = memory.as_ref();
541        assert_eq!(reference[0], 0x42);
542        assert_eq!(reference[1], 0x00);
543        assert_eq!(reference.len(), memory.len());
544    }
545
546    #[test]
547    fn deref_mut_works() {
548        const SIZE: usize = TWO_MEGABYTES * 2;
549        let mut memory = Memory::allocate(SIZE, true, true).expect("allocation failed");
550
551        let addr: &mut [f32] = memory.as_mut();
552        addr[0] = 1.234;
553        addr[1] = 5.678;
554
555        let reference: &[f32] = memory.as_ref();
556        assert_eq!(reference[0], 1.234);
557        assert_eq!(reference[1], 5.678);
558        assert_eq!(reference[2], 0.0);
559        assert_eq!(reference.len(), memory.len() / std::mem::size_of::<f32>());
560    }
561
562    #[test]
563    fn default_is_empty() {
564        let memory = Memory::default();
565        assert!(memory.is_empty());
566        assert_eq!(memory.len(), 0);
567        assert!(memory.to_ptr().is_none());
568    }
569
570    #[test]
571    fn default_free_is_noop() {
572        let mut memory = Memory::default();
573        memory.free(); // should not panic
574        assert!(memory.is_empty());
575    }
576
577    #[test]
578    fn double_free_is_noop() {
579        const SIZE: usize = 1024;
580        let mut memory = Memory::allocate(SIZE, false, false).expect("allocation failed");
581        memory.free();
582        memory.free(); // second free should be a no-op
583        assert!(memory.is_empty());
584        assert!(memory.to_ptr().is_none());
585    }
586
587    #[test]
588    fn builder_api_works() {
589        const SIZE: usize = 1024;
590        let memory = Memory::builder(SIZE)
591            .sequential()
592            .zeroed()
593            .allocate()
594            .expect("allocation failed");
595
596        assert_eq!(memory.len(), SIZE);
597        assert!(!memory.is_empty());
598        assert!(memory.to_ptr().is_some());
599    }
600
601    #[test]
602    fn memory_is_send_and_sync() {
603        fn assert_send<T: Send>() {}
604        fn assert_sync<T: Sync>() {}
605        assert_send::<Memory>();
606        assert_sync::<Memory>();
607    }
608}