snmalloc_rs/
lib.rs

1#![no_std]
2//! `snmalloc-rs` provides a wrapper for [`microsoft/snmalloc`](https://github.com/microsoft/snmalloc) to make it usable as a global allocator for rust.
3//! snmalloc is a research allocator. Its key design features are:
4//! - Memory that is freed by the same thread that allocated it does not require any synchronising operations.
5//! - Freeing memory in a different thread to initially allocated it, does not take any locks and instead uses a novel message passing scheme to return the memory to the original allocator, where it is recycled.
6//! - The allocator uses large ranges of pages to reduce the amount of meta-data required.
7//!
8//! The benchmark is available at the [paper](https://github.com/microsoft/snmalloc/blob/master/snmalloc.pdf) of `snmalloc`
9//! There are three features defined in this crate:
10//! - `debug`: Enable the `Debug` mode in `snmalloc`.
11//! - `1mib`: Use the `1mib` chunk configuration.
12//! - `cache-friendly`: Make the allocator more cache friendly (setting `CACHE_FRIENDLY_OFFSET` to `64` in building the library).
13//!
14//! The whole library supports `no_std`.
15//!
16//! To use `snmalloc-rs` add it as a dependency:
17//! ```toml
18//! # Cargo.toml
19//! [dependencies]
20//! snmalloc-rs = "0.1.0"
21//! ```
22//!
23//! To set `SnMalloc` as the global allocator add this to your project:
24//! ```rust
25//! #[global_allocator]
26//! static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc;
27//! ```
28extern crate snmalloc_sys as ffi;
29
30use core::{
31    alloc::{GlobalAlloc, Layout},
32    ptr::NonNull,
33};
34
35#[derive(Debug, Copy, Clone)]
36#[repr(C)]
37pub struct SnMalloc;
38
39unsafe impl Send for SnMalloc {}
40unsafe impl Sync for SnMalloc {}
41
42impl SnMalloc {
43    #[inline(always)]
44    pub const fn new() -> Self {
45        Self
46    }
47
48    /// Returns the available bytes in a memory block.
49    #[inline(always)]
50    pub fn usable_size(&self, ptr: *const u8) -> Option<usize> {
51        match ptr.is_null() {
52            true => None,
53            false => Some(unsafe { ffi::sn_rust_usable_size(ptr.cast()) })
54        }
55    }
56
57    /// Allocates memory with the given layout, returning a non-null pointer on success
58    #[inline(always)]
59    pub fn alloc_aligned(&self, layout: Layout) -> Option<NonNull<u8>> {
60        match layout.size() {
61            0 => NonNull::new(layout.align() as *mut u8),
62            size => NonNull::new(unsafe { ffi::sn_rust_alloc(layout.align(), size) }.cast())
63        }
64    }
65}
66
67unsafe impl GlobalAlloc for SnMalloc {
68    /// Allocate the memory with the given alignment and size.
69    /// On success, it returns a pointer pointing to the required memory address.
70    /// On failure, it returns a null pointer.
71    /// The client must assure the following things:
72    /// - `alignment` is greater than zero
73    /// - Other constrains are the same as the rust standard library.
74    ///
75    /// The program may be forced to abort if the constrains are not full-filled.
76    #[inline(always)]
77    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
78        match layout.size() {
79            0 => layout.align() as *mut u8,
80            size => ffi::sn_rust_alloc(layout.align(), size).cast()
81        }
82    }
83
84    /// De-allocate the memory at the given address with the given alignment and size.
85    /// The client must assure the following things:
86    /// - the memory is acquired using the same allocator and the pointer points to the start position.
87    /// - Other constrains are the same as the rust standard library.
88    ///
89    /// The program may be forced to abort if the constrains are not full-filled.
90    #[inline(always)]
91    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
92        if layout.size() != 0 {
93            ffi::sn_rust_dealloc(ptr as _, layout.align(), layout.size());
94        }
95    }
96
97    /// Behaves like alloc, but also ensures that the contents are set to zero before being returned.
98    #[inline(always)]
99    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
100        match layout.size() {
101            0 => layout.align() as *mut u8,
102            size => ffi::sn_rust_alloc_zeroed(layout.align(), size).cast()
103        }
104    }
105
106    /// Re-allocate the memory at the given address with the given alignment and size.
107    /// On success, it returns a pointer pointing to the required memory address.
108    /// The memory content within the `new_size` will remains the same as previous.
109    /// On failure, it returns a null pointer. In this situation, the previous memory is not returned to the allocator.
110    /// The client must assure the following things:
111    /// - the memory is acquired using the same allocator and the pointer points to the start position
112    /// - `alignment` fulfills all the requirements as `rust_alloc`
113    /// - Other constrains are the same as the rust standard library.
114    ///
115    /// The program may be forced to abort if the constrains are not full-filled.
116    #[inline(always)]
117    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
118        match new_size {
119            0 => {
120                self.dealloc(ptr, layout);
121                layout.align() as *mut u8
122            }
123            new_size if layout.size() == 0 => {
124                self.alloc(Layout::from_size_align_unchecked(new_size, layout.align()))
125            }
126            _ => ffi::sn_rust_realloc(ptr.cast(), layout.align(), layout.size(), new_size).cast()
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    #[test]
135    fn allocation_lifecycle() {
136        let alloc = SnMalloc::new();
137        unsafe {
138            let layout = Layout::from_size_align(8, 8).unwrap();
139            
140            // Test regular allocation
141            let ptr = alloc.alloc(layout);
142            alloc.dealloc(ptr, layout);
143
144            // Test zeroed allocation
145            let ptr = alloc.alloc_zeroed(layout);
146            alloc.dealloc(ptr, layout);
147
148            // Test reallocation
149            let ptr = alloc.alloc(layout);
150            let ptr = alloc.realloc(ptr, layout, 16);
151            alloc.dealloc(ptr, layout);
152
153            // Test large allocation
154            let large_layout = Layout::from_size_align(1 << 20, 32).unwrap();
155            let ptr = alloc.alloc(large_layout);
156            alloc.dealloc(ptr, large_layout);
157        }
158    }
159    #[test]
160    fn it_frees_allocated_memory() {
161        unsafe {
162            let layout = Layout::from_size_align(8, 8).unwrap();
163            let alloc = SnMalloc;
164
165            let ptr = alloc.alloc(layout);
166            alloc.dealloc(ptr, layout);
167        }
168    }
169
170    #[test]
171    fn it_frees_zero_allocated_memory() {
172        unsafe {
173            let layout = Layout::from_size_align(8, 8).unwrap();
174            let alloc = SnMalloc;
175
176            let ptr = alloc.alloc_zeroed(layout);
177            alloc.dealloc(ptr, layout);
178        }
179    }
180
181    #[test]
182    fn it_frees_reallocated_memory() {
183        unsafe {
184            let layout = Layout::from_size_align(8, 8).unwrap();
185            let alloc = SnMalloc;
186
187            let ptr = alloc.alloc(layout);
188            let ptr = alloc.realloc(ptr, layout, 16);
189            alloc.dealloc(ptr, layout);
190        }
191    }
192
193    #[test]
194    fn it_frees_large_alloc() {
195        unsafe {
196            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
197            let alloc = SnMalloc;
198
199            let ptr = alloc.alloc(layout);
200            alloc.dealloc(ptr, layout);
201        }
202    }
203
204    #[test]
205    fn test_usable_size() {
206        let alloc = SnMalloc::new();
207        unsafe {
208            let layout = Layout::from_size_align(8, 8).unwrap();
209            let ptr = alloc.alloc(layout);
210            let usz = alloc.usable_size(ptr).expect("usable_size returned None");
211            alloc.dealloc(ptr, layout);
212            assert!(usz >= 8);
213        }
214    }
215}