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}