Skip to main content

zigzag_alloc/alloc/
system.rs

1//! System allocator backed by the OS memory-management API.
2//!
3//! [`SystemAllocator`] wraps platform alignment-aware allocation primitives:
4//!
5//! | Platform | Alloc              | Free              |
6//! |----------|--------------------|-------------------|
7//! | Unix     | `posix_memalign`   | `free`            |
8//! | Windows  | `_aligned_malloc`  | `_aligned_free`   |
9//! | Other    | always returns `None` | no-op          |
10//!
11//! This is the lowest-level allocator in the crate.  Higher-level allocators
12//! (arena, bump, pool) typically use a `SystemAllocator` as their *backing*
13//! allocator.
14
15#[warn(unused_doc_comments)]
16#[allow(unused_imports)]
17
18use core::{alloc::Layout, ffi::c_void, mem, ptr::NonNull};
19
20use super::allocator::Allocator;
21
22/// Allocates `size` bytes with a minimum alignment of `alignment`.
23///
24/// On success writes the pointer to `*memptr` and returns 0.
25/// On failure returns a non-zero error code and leaves `*memptr` unchanged.
26///
27/// # Safety
28/// `alignment` must be a power of two and a multiple of `sizeof(void*)`.
29/// `size` must be greater than zero.
30#[cfg(target_family = "unix")]
31unsafe extern "C" {
32    fn posix_memalign(memptr: *mut *mut c_void, alignment: usize, size: usize) -> i32;
33
34    /// Releases memory previously obtained from `posix_memalign` (or `malloc`).
35    ///
36    /// # Safety
37    /// `ptr` must have been returned by a prior successful allocation call.
38    /// Passing `null` is defined (no-op).  Double-free is undefined behaviour.
39    fn free(ptr: *mut c_void);
40}
41
42/// Allocates `size` bytes with the given `alignment`.
43///
44/// Returns a non-null pointer on success, or null on failure.
45///
46/// # Safety
47/// `alignment` must be a power of two.  `size` must be greater than zero.
48#[cfg(target_family = "windows")]
49unsafe extern "C" {
50    fn _aligned_malloc(size: usize, alignment: usize) -> *mut c_void;
51
52    /// Releases memory previously obtained from `_aligned_malloc`.
53    ///
54    /// # Safety
55    /// `ptr` must have been returned by `_aligned_malloc`.
56    /// Passing `null` is defined (no-op).  Double-free is undefined behaviour.
57    fn _aligned_free(ptr: *mut c_void);
58}
59
60// ── SystemAllocator ──────────────────────────────────────────────────────────
61
62/// An [`Allocator`] that delegates directly to the operating system.
63///
64/// `SystemAllocator` is a zero-sized type; it holds no state and is safe to
65/// share across threads.  It is the typical *backing allocator* for arena,
66/// pool, and bump allocators when running on a hosted OS.
67///
68/// # Platform Notes
69///
70/// * **Unix** — Uses `posix_memalign` to satisfy arbitrary alignment requests.
71///   The effective alignment is always at least `sizeof(void*)` (usually 8 or
72///   16 bytes) because `posix_memalign` requires it.
73/// * **Windows** — Uses `_aligned_malloc` / `_aligned_free`.  The effective
74///   alignment is at least `sizeof(void*)`.
75/// * **Other** — [`alloc`](Allocator::alloc) always returns `None`.
76pub struct SystemAllocator;
77
78impl Allocator for SystemAllocator {
79    /// Allocates a block of memory according to `layout`.
80    ///
81    /// Returns `Some(ptr)` where `ptr` is aligned to at least `layout.align()`
82    /// and valid for `layout.size()` bytes.  Returns `None` if the OS call
83    /// fails or if the target platform is unsupported.
84    ///
85    /// # Safety
86    ///
87    /// * `layout.size()` must be greater than zero; zero-sized allocations are
88    ///   rejected by `posix_memalign` and `_aligned_malloc`.
89    /// * The returned pointer must eventually be passed to
90    ///   [`dealloc`](Allocator::dealloc) with the same `layout`.
91    unsafe fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
92        let size = layout.size();
93
94        if size == 0 {
95            // Callers must not pass zero-sized layouts, but we guard defensively
96            // by returning a dangling non-null pointer rather than invoking
97            // undefined behaviour in the OS allocation function.
98            return Some(NonNull::dangling());
99        }
100
101        #[cfg(target_family = "unix")]
102        {
103            let mut ptr: *mut c_void = core::ptr::null_mut();
104
105            // SAFETY: posix_memalign requires alignment to be a power of two and
106            // a multiple of sizeof(void*).  We enforce the latter by taking the
107            // maximum of the requested alignment and the pointer size.
108            let min_align = core::mem::size_of::<*mut c_void>();
109            let align = layout.align().max(min_align);
110
111            let rc = unsafe { posix_memalign(&mut ptr, align, size) };
112
113            if rc != 0 {
114                return None;
115            }
116            NonNull::new(ptr as *mut u8)
117        }
118
119        #[cfg(target_family = "windows")]
120        {
121            // SAFETY: _aligned_malloc requires alignment to be a power of two.
122            // Layout::align() already guarantees that invariant.
123            let align = layout.align().max(mem::size_of::<*const ()>());
124            let ptr = unsafe { _aligned_malloc(size, align) as *mut u8 };
125            NonNull::new(ptr)
126        }
127
128        #[cfg(not(any(target_family = "unix", target_family = "windows")))]
129        {
130            None
131        }
132    }
133
134    /// Releases a block previously allocated by this `SystemAllocator`.
135    ///
136    /// # Safety
137    ///
138    /// * `ptr` must have been returned by a prior successful call to
139    ///   [`alloc`](Allocator::alloc) on **this same** `SystemAllocator`.
140    /// * `layout` must exactly match the layout passed to that `alloc` call.
141    /// * After `dealloc` returns, `ptr` is invalid and must not be accessed.
142    /// * Calling `dealloc` twice for the same pointer is undefined behaviour.
143    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
144        if layout.size() == 0 {
145            // Matches the dangling-pointer fast-path in `alloc`.
146            return;
147        }
148
149        #[cfg(target_family = "unix")]
150        {
151            // SAFETY: `ptr` was obtained from `posix_memalign` and has not been
152            // freed before — both guaranteed by the caller of this method.
153            unsafe { free(ptr.as_ptr() as *mut c_void) }
154        }
155
156        #[cfg(target_family = "windows")]
157        {
158            // SAFETY: `ptr` was obtained from `_aligned_malloc` and has not been
159            // freed before — both guaranteed by the caller of this method.
160            unsafe { _aligned_free(ptr.as_ptr() as *mut c_void) };
161        }
162    }
163}