dlmalloc/
lib.rs

1//! A Rust port of the `dlmalloc` allocator.
2//!
3//! The `dlmalloc` allocator is described at
4//! <https://gee.cs.oswego.edu/dl/html/malloc.html> and this Rust crate is a straight
5//! port of the C code for the allocator into Rust. The implementation is
6//! wrapped up in a `Dlmalloc` type and has support for Linux, OSX, and Wasm
7//! currently.
8//!
9//! The primary purpose of this crate is that it serves as the default memory
10//! allocator for the `wasm32-unknown-unknown` target in the standard library.
11//! Support for other platforms is largely untested and unused, but is used when
12//! testing this crate.
13
14#![allow(dead_code)]
15#![no_std]
16#![deny(missing_docs)]
17#![cfg_attr(target_arch = "wasm64", feature(simd_wasm64))]
18
19use core::cmp;
20use core::ptr;
21use sys::System;
22
23#[cfg(feature = "global")]
24pub use self::global::{enable_alloc_after_fork, GlobalDlmalloc};
25
26mod dlmalloc;
27#[cfg(feature = "global")]
28mod global;
29
30/// In order for this crate to efficiently manage memory, it needs a way to communicate with the
31/// underlying platform. This `Allocator` trait provides an interface for this communication.
32pub unsafe trait Allocator: Send {
33    /// Allocates system memory region of at least `size` bytes
34    /// Returns a triple of `(base, size, flags)` where `base` is a pointer to the beginning of the
35    /// allocated memory region. `size` is the actual size of the region while `flags` specifies
36    /// properties of the allocated region. If `EXTERN_BIT` (bit 0) set in flags, then we did not
37    /// allocate this segment and so should not try to deallocate or merge with others.
38    /// This function can return a `std::ptr::null_mut()` when allocation fails (other values of
39    /// the triple will be ignored).
40    fn alloc(&self, size: usize) -> (*mut u8, usize, u32);
41
42    /// Remaps system memory region at `ptr` with size `oldsize` to a potential new location with
43    /// size `newsize`. `can_move` indicates if the location is allowed to move to a completely new
44    /// location, or that it is only allowed to change in size. Returns a pointer to the new
45    /// location in memory.
46    /// This function can return a `std::ptr::null_mut()` to signal an error.
47    fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8;
48
49    /// Frees a part of a memory chunk. The original memory chunk starts at `ptr` with size `oldsize`
50    /// and is turned into a memory region starting at the same address but with `newsize` bytes.
51    /// Returns `true` iff the access memory region could be freed.
52    fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool;
53
54    /// Frees an entire memory region. Returns `true` iff the operation succeeded. When `false` is
55    /// returned, the `dlmalloc` may re-use the location on future allocation requests
56    fn free(&self, ptr: *mut u8, size: usize) -> bool;
57
58    /// Indicates if the system can release a part of memory. For the `flags` argument, see
59    /// `Allocator::alloc`
60    fn can_release_part(&self, flags: u32) -> bool;
61
62    /// Indicates whether newly allocated regions contain zeros.
63    fn allocates_zeros(&self) -> bool;
64
65    /// Returns the page size. Must be a power of two
66    fn page_size(&self) -> usize;
67}
68
69/// An allocator instance
70///
71/// Instances of this type are used to allocate blocks of memory. For best
72/// results only use one of these. Currently doesn't implement `Drop` to release
73/// lingering memory back to the OS. That may happen eventually though!
74pub struct Dlmalloc<A = System>(dlmalloc::Dlmalloc<A>);
75
76cfg_if::cfg_if! {
77    if #[cfg(target_family = "wasm")] {
78        #[path = "wasm.rs"]
79        mod sys;
80    } else if #[cfg(target_os = "windows")] {
81        #[path = "windows.rs"]
82        mod sys;
83    } else if #[cfg(target_os = "xous")] {
84        #[path = "xous.rs"]
85        mod sys;
86    } else if #[cfg(any(target_os = "linux", target_os = "macos"))] {
87        #[path = "unix.rs"]
88        mod sys;
89    } else {
90        #[path = "dummy.rs"]
91        mod sys;
92    }
93}
94
95impl Dlmalloc<System> {
96    /// Creates a new instance of an allocator
97    pub const fn new() -> Dlmalloc<System> {
98        Dlmalloc(dlmalloc::Dlmalloc::new(System::new()))
99    }
100}
101
102impl<A> Dlmalloc<A> {
103    /// Creates a new instance of an allocator
104    pub const fn new_with_allocator(sys_allocator: A) -> Dlmalloc<A> {
105        Dlmalloc(dlmalloc::Dlmalloc::new(sys_allocator))
106    }
107}
108
109impl<A: Allocator> Dlmalloc<A> {
110    /// Allocates `size` bytes with `align` align.
111    ///
112    /// Returns a null pointer if allocation fails. Returns a valid pointer
113    /// otherwise.
114    ///
115    /// Safety and contracts are largely governed by the `GlobalAlloc::alloc`
116    /// method contracts.
117    #[inline]
118    pub unsafe fn malloc(&mut self, size: usize, align: usize) -> *mut u8 {
119        if align <= self.0.malloc_alignment() {
120            self.0.malloc(size)
121        } else {
122            self.0.memalign(align, size)
123        }
124    }
125
126    /// Same as `malloc`, except if the allocation succeeds it's guaranteed to
127    /// point to `size` bytes of zeros.
128    #[inline]
129    pub unsafe fn calloc(&mut self, size: usize, align: usize) -> *mut u8 {
130        let ptr = self.malloc(size, align);
131        if !ptr.is_null() && self.0.calloc_must_clear(ptr) {
132            ptr::write_bytes(ptr, 0, size);
133        }
134        ptr
135    }
136
137    /// Deallocates a `ptr` with `size` and `align` as the previous request used
138    /// to allocate it.
139    ///
140    /// Safety and contracts are largely governed by the `GlobalAlloc::dealloc`
141    /// method contracts.
142    #[inline]
143    pub unsafe fn free(&mut self, ptr: *mut u8, size: usize, align: usize) {
144        let _ = align;
145        self.0.validate_size(ptr, size);
146        self.0.free(ptr)
147    }
148
149    /// Reallocates `ptr`, a previous allocation with `old_size` and
150    /// `old_align`, to have `new_size` and the same alignment as before.
151    ///
152    /// Returns a null pointer if the memory couldn't be reallocated, but `ptr`
153    /// is still valid. Returns a valid pointer and frees `ptr` if the request
154    /// is satisfied.
155    ///
156    /// Safety and contracts are largely governed by the `GlobalAlloc::realloc`
157    /// method contracts.
158    #[inline]
159    pub unsafe fn realloc(
160        &mut self,
161        ptr: *mut u8,
162        old_size: usize,
163        old_align: usize,
164        new_size: usize,
165    ) -> *mut u8 {
166        self.0.validate_size(ptr, old_size);
167
168        if old_align <= self.0.malloc_alignment() {
169            self.0.realloc(ptr, new_size)
170        } else {
171            let res = self.malloc(new_size, old_align);
172            if !res.is_null() {
173                let size = cmp::min(old_size, new_size);
174                ptr::copy_nonoverlapping(ptr, res, size);
175                self.free(ptr, old_size, old_align);
176            }
177            res
178        }
179    }
180
181    /// If possible, gives memory back to the system if there is unused memory
182    /// at the high end of the malloc pool or in unused segments.
183    ///
184    /// You can call this after freeing large blocks of memory to potentially
185    /// reduce the system-level memory requirements of a program. However, it
186    /// cannot guarantee to reduce memory. Under some allocation patterns, some
187    /// large free blocks of memory will be locked between two used chunks, so
188    /// they cannot be given back to the system.
189    ///
190    /// The `pad` argument represents the amount of free trailing space to
191    /// leave untrimmed. If this argument is zero, only the minimum amount of
192    /// memory to maintain internal data structures will be left. Non-zero
193    /// arguments can be supplied to maintain enough trailing space to service
194    /// future expected allocations without having to re-obtain memory from the
195    /// system.
196    ///
197    /// Returns `true` if it actually released any memory, else `false`.
198    pub unsafe fn trim(&mut self, pad: usize) -> bool {
199        self.0.trim(pad)
200    }
201
202    /// Releases all allocations in this allocator back to the system,
203    /// consuming self and preventing further use.
204    ///
205    /// Returns the number of bytes released to the system.
206    pub unsafe fn destroy(self) -> usize {
207        self.0.destroy()
208    }
209
210    /// Get a reference the underlying [`Allocator`] that this `Dlmalloc` was
211    /// constructed with.
212    pub fn allocator(&self) -> &A {
213        self.0.allocator()
214    }
215}