malloc_best_effort/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Best effort memory allocator for Rust
5//!
6//! Selects suitable allocator depending on target OS and architecture.
7//!
8//! ## Usage
9//! * Put to your `src/main.rs`:
10//! ```rust,ignore
11//! use malloc_best_effort::BEMalloc;
12//!
13//! #[global_allocator]
14//! static GLOBAL: BEMalloc = BEMalloc::new();
15//!
16//! fn main() {
17//!     BEMalloc::init();
18//!
19//!     // Rest of main
20//! }
21//! ```
22//!
23//! * Put to `build.rs` to workaround mimalloc build dependencies:
24//! ```rust,ignore
25//! use std::borrow::Cow;
26//! use std::env;
27//!
28//! fn main() {
29//!     let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target_os not defined!");
30//!     let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("target_arch not defined!"); // on armv6 we need to link with libatomic
31//!
32//!     if target_os == "linux" && target_arch == "arm" {
33//!         // Embrace the atomic capability library across various platforms.
34//!         // For instance, on certain platforms, llvm has relocated the atomic of the arm32 architecture to libclang_rt.builtins.a
35//!         // while some use libatomic.a, and others use libatomic_ops.a.
36//!         let atomic_name = match env::var("DEP_ATOMIC") {
37//!             Ok(atomic_name) => Cow::Owned(atomic_name),
38//!             Err(_) => Cow::Borrowed("atomic"),
39//!         };
40//!         println!("cargo:rustc-link-lib={atomic_name}");
41//!     }
42//!
43//!     // Link with libs needed on Windows
44//!     if target_os == "windows" {
45//!         // https://github.com/microsoft/mimalloc/blob/af21001f7a65eafb8fb16460b018ebf9d75e2ad8/CMakeLists.txt#L487
46//!
47//!         for lib in ["psapi", "shell32", "user32", "advapi32", "bcrypt"] {
48//!             println!("cargo:rustc-link-lib={lib}");
49//!         }
50//!     }
51//! }
52//! ```
53//!
54//! ## Feature flags
55#![doc = document_features::document_features!()]
56
57#[cfg(not(all(
58    target_os = "linux",
59    any(
60        target_arch = "x86_64",
61        target_arch = "aarch64",
62        target_arch = "powerpc64"
63    ),
64    target_endian = "little"
65)))]
66mod mimalloc {
67    pub(crate) use mimalloc::MiMalloc as BEMallocImpl;
68
69    #[inline]
70    pub(crate) fn init_impl() {}
71}
72#[cfg(all(
73    target_os = "linux",
74    any(
75        target_arch = "x86_64",
76        target_arch = "aarch64",
77        target_arch = "powerpc64"
78    ),
79    target_endian = "little"
80))]
81mod tcmalloc {
82    pub(crate) use tcmalloc_better::TCMalloc as BEMallocImpl;
83
84    #[cfg(feature = "std")]
85    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
86    #[inline]
87    pub(crate) fn init_impl() {
88        BEMallocImpl::process_background_actions_thread();
89    }
90
91    #[cfg(not(feature = "std"))]
92    #[cfg_attr(docsrs, doc(cfg(not(feature = "std"))))]
93    #[inline]
94    pub(crate) fn init_impl() {}
95}
96
97#[cfg(not(all(
98    target_os = "linux",
99    any(
100        target_arch = "x86_64",
101        target_arch = "aarch64",
102        target_arch = "powerpc64"
103    ),
104    target_endian = "little"
105)))]
106use crate::mimalloc::BEMallocImpl;
107#[cfg(not(all(
108    target_os = "linux",
109    any(
110        target_arch = "x86_64",
111        target_arch = "aarch64",
112        target_arch = "powerpc64"
113    ),
114    target_endian = "little"
115)))]
116use crate::mimalloc::init_impl;
117#[cfg(all(
118    target_os = "linux",
119    any(
120        target_arch = "x86_64",
121        target_arch = "aarch64",
122        target_arch = "powerpc64"
123    ),
124    target_endian = "little"
125))]
126use crate::tcmalloc::BEMallocImpl;
127#[cfg(all(
128    target_os = "linux",
129    any(
130        target_arch = "x86_64",
131        target_arch = "aarch64",
132        target_arch = "powerpc64"
133    ),
134    target_endian = "little"
135))]
136use crate::tcmalloc::init_impl;
137use core::alloc::{GlobalAlloc, Layout};
138
139/// A memory allocator that can be registered as the standard library’s default
140/// through the `#[global_allocator]` attribute.
141pub struct BEMalloc {
142    alloc_impl: BEMallocImpl,
143}
144
145unsafe impl GlobalAlloc for BEMalloc {
146    #[inline]
147    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
148        unsafe { self.alloc_impl.alloc(layout) }
149    }
150
151    #[inline]
152    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
153        unsafe { self.alloc_impl.dealloc(ptr, layout) }
154    }
155
156    #[inline]
157    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
158        unsafe { self.alloc_impl.alloc_zeroed(layout) }
159    }
160
161    #[inline]
162    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
163        unsafe { self.alloc_impl.realloc(ptr, layout, new_size) }
164    }
165}
166
167impl Default for BEMalloc {
168    #[inline]
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl BEMalloc {
175    /// Create new instance of allocator.
176    #[inline]
177    pub const fn new() -> Self {
178        Self {
179            alloc_impl: BEMallocImpl,
180        }
181    }
182    /// Start allocator background job. Should be called in `main()` function.
183    #[inline]
184    pub fn init() {
185        init_impl();
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn it_frees_allocated_memory() {
195        unsafe {
196            let layout = Layout::from_size_align(8, 8).unwrap();
197            let alloc = BEMalloc::new();
198
199            let ptr = alloc.alloc(layout);
200            alloc.dealloc(ptr, layout);
201        }
202    }
203
204    #[test]
205    fn it_frees_allocated_memory_with_init() {
206        unsafe {
207            let layout = Layout::from_size_align(8, 8).unwrap();
208            let alloc = BEMalloc::new();
209            BEMalloc::init();
210
211            let ptr = alloc.alloc(layout);
212            alloc.dealloc(ptr, layout);
213        }
214    }
215
216    #[test]
217    fn it_frees_allocated_big_memory() {
218        unsafe {
219            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
220            let alloc = BEMalloc::new();
221
222            let ptr = alloc.alloc(layout);
223            alloc.dealloc(ptr, layout);
224        }
225    }
226
227    #[test]
228    fn it_frees_zero_allocated_memory() {
229        unsafe {
230            let layout = Layout::from_size_align(8, 8).unwrap();
231            let alloc = BEMalloc::new();
232
233            let ptr = alloc.alloc_zeroed(layout);
234            alloc.dealloc(ptr, layout);
235        }
236    }
237
238    #[test]
239    fn it_frees_zero_allocated_big_memory() {
240        unsafe {
241            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
242            let alloc = BEMalloc::new();
243
244            let ptr = alloc.alloc_zeroed(layout);
245            alloc.dealloc(ptr, layout);
246        }
247    }
248
249    #[test]
250    fn it_frees_reallocated_memory() {
251        unsafe {
252            let layout = Layout::from_size_align(8, 8).unwrap();
253            let new_size = 16;
254            let new_layout = Layout::from_size_align(new_size, layout.align()).unwrap();
255            let alloc = BEMalloc::new();
256
257            let ptr = alloc.alloc(layout);
258            let ptr = alloc.realloc(ptr, layout, new_size);
259            alloc.dealloc(ptr, new_layout);
260        }
261    }
262
263    #[test]
264    fn it_frees_reallocated_big_memory() {
265        unsafe {
266            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
267            let new_size = 2 << 20;
268            let new_layout = Layout::from_size_align(new_size, layout.align()).unwrap();
269            let alloc = BEMalloc::new();
270
271            let ptr = alloc.alloc(layout);
272            let ptr = alloc.realloc(ptr, layout, new_size);
273            alloc.dealloc(ptr, new_layout);
274        }
275    }
276}