Skip to main content

rustfs_mimalloc/
heap.rs

1//! Heap and arena operations for advanced memory management.
2
3use core::ffi::c_void;
4use core::ptr::NonNull;
5
6// ── Error type ──────────────────────────────────────────────────────────────
7
8/// Error returned by arena operations.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ArenaError {
11    /// The OS call failed (e.g., out of memory, invalid parameters).
12    Failed,
13}
14
15// ── Heap ────────────────────────────────────────────────────────────────────
16
17/// A handle to a mimalloc heap.
18///
19/// Allocations from a heap can be freed from any thread.
20/// Dropping a `Heap` moves its live blocks to the main heap (via `mi_heap_delete`).
21pub struct Heap {
22    ptr: NonNull<rustfs_mimalloc_sys::mi_heap_t>,
23    owned: bool,
24}
25
26unsafe impl Send for Heap {}
27unsafe impl Sync for Heap {}
28
29impl Heap {
30    /// Create a new heap. Returns `None` on OOM.
31    pub fn new() -> Option<Self> {
32        NonNull::new(unsafe { rustfs_mimalloc_sys::mi_heap_new() }).map(Self::owned)
33    }
34
35    /// Create a heap that allocates exclusively from the given arena.
36    pub fn new_in_arena(arena_id: ArenaId) -> Option<Self> {
37        NonNull::new(unsafe { rustfs_mimalloc_sys::mi_heap_new_in_arena(arena_id.0) })
38            .map(Self::owned)
39    }
40
41    /// Get the main heap.
42    pub fn main() -> Self {
43        let ptr = unsafe { rustfs_mimalloc_sys::mi_heap_main() };
44        Self::borrowed(NonNull::new(ptr).expect("mi_heap_main returned null"))
45    }
46
47    /// Get the heap that owns `ptr`.
48    ///
49    /// # Safety
50    /// `ptr` must be a valid mimalloc-allocated pointer.
51    pub unsafe fn heap_of(ptr: *const u8) -> Option<Self> {
52        NonNull::new(unsafe { rustfs_mimalloc_sys::mi_heap_of(ptr as *const c_void) })
53            .map(Self::borrowed)
54    }
55
56    /// Check if this heap contains `ptr`.
57    ///
58    /// # Safety
59    /// `ptr` must be valid.
60    pub unsafe fn contains(&self, ptr: *const u8) -> bool {
61        unsafe { rustfs_mimalloc_sys::mi_heap_contains(self.ptr.as_ptr(), ptr as *const c_void) }
62    }
63
64    /// Allocate `size` bytes from this heap.
65    ///
66    /// # Safety
67    /// The returned pointer must be freed with `mi_free` (cross-heap frees are allowed).
68    pub unsafe fn malloc(&self, size: usize) -> *mut u8 {
69        unsafe { rustfs_mimalloc_sys::mi_heap_malloc(self.ptr.as_ptr(), size) as *mut u8 }
70    }
71
72    /// Allocate zero-initialized memory from this heap.
73    ///
74    /// # Safety
75    /// The returned pointer must be freed with `mi_free`.
76    pub unsafe fn zalloc(&self, size: usize) -> *mut u8 {
77        unsafe { rustfs_mimalloc_sys::mi_heap_zalloc(self.ptr.as_ptr(), size) as *mut u8 }
78    }
79
80    /// Allocate aligned memory from this heap.
81    ///
82    /// # Safety
83    /// The returned pointer must be freed with `mi_free`.
84    pub unsafe fn malloc_aligned(&self, size: usize, alignment: usize) -> *mut u8 {
85        unsafe {
86            rustfs_mimalloc_sys::mi_heap_malloc_aligned(self.ptr.as_ptr(), size, alignment)
87                as *mut u8
88        }
89    }
90
91    /// Reallocate memory from this heap.
92    ///
93    /// # Safety
94    /// `ptr` must be a valid mimalloc pointer. The returned pointer must be freed with `mi_free`.
95    pub unsafe fn realloc(&self, ptr: *mut u8, new_size: usize) -> *mut u8 {
96        unsafe {
97            rustfs_mimalloc_sys::mi_heap_realloc(self.ptr.as_ptr(), ptr as *mut c_void, new_size)
98                as *mut u8
99        }
100    }
101
102    /// Delete this heap, moving live blocks to the main heap.
103    /// Consumes `self` without running `Drop`. Borrowed heap handles are left untouched.
104    pub fn delete(self) {
105        if self.owned {
106            unsafe { rustfs_mimalloc_sys::mi_heap_delete(self.ptr.as_ptr()) }
107        }
108        core::mem::forget(self);
109    }
110
111    /// Destroy this heap, freeing all live blocks.
112    ///
113    /// # Safety
114    /// All pointers from this heap become dangling. Borrowed heap handles are left untouched.
115    pub unsafe fn destroy(self) {
116        if self.owned {
117            unsafe { rustfs_mimalloc_sys::mi_heap_destroy(self.ptr.as_ptr()) };
118        }
119        core::mem::forget(self);
120    }
121
122    /// Force garbage collection on this heap.
123    pub fn collect(&self, force: bool) {
124        unsafe { rustfs_mimalloc_sys::mi_heap_collect(self.ptr.as_ptr(), force) }
125    }
126
127    /// Allocation statistics for this heap as JSON. Returns empty string on failure.
128    pub fn stats_json(&self) -> String {
129        unsafe {
130            crate::ffi::owned_mimalloc_string(rustfs_mimalloc_sys::mi_heap_stats_get_json(
131                self.ptr.as_ptr(),
132                0,
133                core::ptr::null_mut(),
134            ))
135        }
136    }
137
138    /// Allocation statistics for this heap in mimalloc's human-readable text format.
139    pub fn stats_print(&self) -> String {
140        crate::ffi::collect_mimalloc_output(|out, arg| unsafe {
141            rustfs_mimalloc_sys::mi_heap_stats_print_out(self.ptr.as_ptr(), out, arg);
142        })
143    }
144
145    /// Raw pointer to the underlying `mi_heap_t`.
146    pub fn as_ptr(&self) -> *mut rustfs_mimalloc_sys::mi_heap_t {
147        self.ptr.as_ptr()
148    }
149
150    #[inline]
151    fn owned(ptr: NonNull<rustfs_mimalloc_sys::mi_heap_t>) -> Self {
152        Self { ptr, owned: true }
153    }
154
155    #[inline]
156    fn borrowed(ptr: NonNull<rustfs_mimalloc_sys::mi_heap_t>) -> Self {
157        Self { ptr, owned: false }
158    }
159}
160
161impl Drop for Heap {
162    fn drop(&mut self) {
163        if self.owned {
164            unsafe { rustfs_mimalloc_sys::mi_heap_delete(self.ptr.as_ptr()) }
165        }
166    }
167}
168
169// ── Arena ───────────────────────────────────────────────────────────────────
170
171/// Arena identifier for managing memory regions.
172#[derive(Debug, Clone, Copy)]
173pub struct ArenaId(rustfs_mimalloc_sys::mi_arena_id_t);
174
175unsafe impl Send for ArenaId {}
176unsafe impl Sync for ArenaId {}
177
178/// Reserve OS memory as an exclusive arena.
179pub fn reserve_os_memory(
180    size: usize,
181    commit: bool,
182    allow_large: bool,
183    exclusive: bool,
184) -> Result<ArenaId, ArenaError> {
185    let mut id = core::ptr::null_mut();
186    let rc = unsafe {
187        rustfs_mimalloc_sys::mi_reserve_os_memory_ex(size, commit, allow_large, exclusive, &mut id)
188    };
189    if rc == 0 {
190        Ok(ArenaId(id))
191    } else {
192        Err(ArenaError::Failed)
193    }
194}
195
196/// Manage an existing memory region as an arena.
197///
198/// # Safety
199/// `start` must point to at least `size` valid bytes that outlive the arena.
200pub unsafe fn manage_os_memory(
201    start: *mut u8,
202    size: usize,
203    is_committed: bool,
204    is_pinned: bool,
205    is_zero: bool,
206    numa_node: i32,
207    exclusive: bool,
208) -> Result<ArenaId, ArenaError> {
209    let mut id = core::ptr::null_mut();
210    let ok = unsafe {
211        rustfs_mimalloc_sys::mi_manage_os_memory_ex(
212            start as *mut c_void,
213            size,
214            is_committed,
215            is_pinned,
216            is_zero,
217            numa_node,
218            exclusive,
219            &mut id,
220        )
221    };
222    if ok {
223        Ok(ArenaId(id))
224    } else {
225        Err(ArenaError::Failed)
226    }
227}
228
229/// Minimum alignment for arena allocations.
230#[inline]
231pub fn arena_min_alignment() -> usize {
232    unsafe { rustfs_mimalloc_sys::mi_arena_min_alignment() }
233}
234
235/// Minimum size for arena allocations.
236#[inline]
237pub fn arena_min_size() -> usize {
238    unsafe { rustfs_mimalloc_sys::mi_arena_min_size() }
239}
240
241/// Maximum object size for arena allocations.
242#[inline]
243pub fn arena_max_object_size() -> usize {
244    unsafe { rustfs_mimalloc_sys::mi_arena_max_object_size() }
245}
246
247// ── Tests ───────────────────────────────────────────────────────────────────
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn heap_create_delete() {
255        let heap = Heap::new().expect("heap::new failed");
256        heap.delete();
257    }
258
259    #[test]
260    fn heap_alloc_free() {
261        let heap = Heap::new().unwrap();
262        unsafe {
263            let ptr = heap.malloc(128);
264            assert!(!ptr.is_null());
265            core::ptr::write_bytes(ptr, 0xCD, 128);
266            rustfs_mimalloc_sys::mi_free(ptr as *mut c_void);
267        }
268        heap.delete();
269    }
270
271    #[test]
272    fn heap_aligned_alloc() {
273        let heap = Heap::new().unwrap();
274        unsafe {
275            for pow in 0..=12 {
276                let align = 1usize << pow;
277                let ptr = heap.malloc_aligned(64, align);
278                assert!(!ptr.is_null());
279                assert_eq!(ptr as usize % align, 0, "align={align}");
280                rustfs_mimalloc_sys::mi_free(ptr as *mut c_void);
281            }
282        }
283        heap.delete();
284    }
285
286    #[test]
287    fn arena_min_values_are_sane() {
288        let a = arena_min_alignment();
289        assert!(a > 0 && a.is_power_of_two());
290    }
291
292    #[test]
293    fn heap_stats_are_available() {
294        let heap = Heap::new().unwrap();
295        assert!(!heap.stats_json().is_empty());
296        assert!(!heap.stats_print().is_empty());
297        heap.delete();
298    }
299
300    #[test]
301    fn borrowed_main_heap_delete_is_noop() {
302        let heap = Heap::main();
303        heap.delete();
304
305        unsafe {
306            let ptr = rustfs_mimalloc_sys::mi_malloc(64);
307            assert!(!ptr.is_null());
308            rustfs_mimalloc_sys::mi_free(ptr);
309        }
310    }
311}