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