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}