1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Bindings for jemalloc as an allocator
12 //!
13 //! This crate provides bindings to jemalloc as a memory allocator for Rust.
14 //! This crate mainly exports, one type, `Jemalloc`, which implements the
15 //! `GlobalAlloc` trait and optionally the `Alloc` trait,
16 //! and is suitable both as a memory allocator and as a global allocator.
17
18 #![cfg_attr(feature = "alloc_trait", feature(allocator_api))]
19 // TODO: rename the following lint on next minor bump
20 #![allow(renamed_and_removed_lints)]
21 #![deny(missing_docs, broken_intra_doc_links)]
22 #![no_std]
23
24 #[cfg(feature = "alloc_trait")]
25 use core::alloc::{Alloc, AllocErr, CannotReallocInPlace, Excess};
26 use core::alloc::{GlobalAlloc, Layout};
27 #[cfg(feature = "alloc_trait")]
28 use core::ptr::NonNull;
29
30 use libc::{c_int, c_void};
31
32 // This constant equals _Alignof(max_align_t) and is platform-specific. It
33 // contains the _maximum_ alignment that the memory allocations returned by the
34 // C standard library memory allocation APIs (e.g. `malloc`) are guaranteed to
35 // have.
36 //
37 // The memory allocation APIs are required to return memory that can fit any
38 // object whose fundamental aligment is <= _Alignof(max_align_t).
39 //
40 // In C, there are no ZSTs, and the size of all types is a multiple of their
41 // alignment (size >= align). So for allocations with size <=
42 // _Alignof(max_align_t), the malloc-APIs return memory whose alignment is
43 // either the requested size if its a power-of-two, or the next smaller
44 // power-of-two.
45 #[cfg(any(
46 target_arch = "arm",
47 target_arch = "mips",
48 target_arch = "mipsel",
49 target_arch = "powerpc"
50 ))]
51 const ALIGNOF_MAX_ALIGN_T: usize = 8;
52 #[cfg(any(
53 target_arch = "x86",
54 target_arch = "x86_64",
55 target_arch = "aarch64",
56 target_arch = "powerpc64",
57 target_arch = "powerpc64le",
58 target_arch = "loongarch64",
59 target_arch = "mips64",
60 target_arch = "riscv64",
61 target_arch = "s390x",
62 target_arch = "sparc64"
63 ))]
64 const ALIGNOF_MAX_ALIGN_T: usize = 16;
65
66 /// If `align` is less than `_Alignof(max_align_t)`, and if the requested
67 /// allocation `size` is larger than the alignment, we are guaranteed to get a
68 /// suitably aligned allocation by default, without passing extra flags, and
69 /// this function returns `0`.
70 ///
71 /// Otherwise, it returns the alignment flag to pass to the jemalloc APIs.
layout_to_flags(align: usize, size: usize) -> c_int72 fn layout_to_flags(align: usize, size: usize) -> c_int {
73 if align <= ALIGNOF_MAX_ALIGN_T && align <= size {
74 0
75 } else {
76 ffi::MALLOCX_ALIGN(align)
77 }
78 }
79
80 // Assumes a condition that always must hold.
81 macro_rules! assume {
82 ($e:expr) => {
83 debug_assert!($e);
84 if !($e) {
85 core::hint::unreachable_unchecked();
86 }
87 };
88 }
89
90 /// Handle to the jemalloc allocator
91 ///
92 /// This type implements the `GlobalAllocAlloc` trait, allowing usage a global allocator.
93 ///
94 /// When the `alloc_trait` feature of this crate is enabled, it also implements the `Alloc` trait,
95 /// allowing usage in collections.
96 #[derive(Copy, Clone, Default, Debug)]
97 pub struct Jemalloc;
98
99 unsafe impl GlobalAlloc for Jemalloc {
100 #[inline]
alloc(&self, layout: Layout) -> *mut u8101 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
102 assume!(layout.size() != 0);
103 let flags = layout_to_flags(layout.align(), layout.size());
104 let ptr = if flags == 0 {
105 ffi::malloc(layout.size())
106 } else {
107 ffi::mallocx(layout.size(), flags)
108 };
109 ptr as *mut u8
110 }
111
112 #[inline]
alloc_zeroed(&self, layout: Layout) -> *mut u8113 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
114 assume!(layout.size() != 0);
115 let flags = layout_to_flags(layout.align(), layout.size());
116 let ptr = if flags == 0 {
117 ffi::calloc(1, layout.size())
118 } else {
119 ffi::mallocx(layout.size(), flags | ffi::MALLOCX_ZERO)
120 };
121 ptr as *mut u8
122 }
123
124 #[inline]
dealloc(&self, ptr: *mut u8, layout: Layout)125 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
126 assume!(!ptr.is_null());
127 assume!(layout.size() != 0);
128 let flags = layout_to_flags(layout.align(), layout.size());
129 ffi::sdallocx(ptr as *mut c_void, layout.size(), flags)
130 }
131
132 #[inline]
realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8133 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
134 assume!(layout.size() != 0);
135 assume!(new_size != 0);
136 let flags = layout_to_flags(layout.align(), new_size);
137 let ptr = if flags == 0 {
138 ffi::realloc(ptr as *mut c_void, new_size)
139 } else {
140 ffi::rallocx(ptr as *mut c_void, new_size, flags)
141 };
142 ptr as *mut u8
143 }
144 }
145
146 #[cfg(feature = "alloc_trait")]
147 unsafe impl Alloc for Jemalloc {
148 #[inline]
alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr>149 unsafe fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
150 NonNull::new(GlobalAlloc::alloc(self, layout)).ok_or(AllocErr)
151 }
152
153 #[inline]
alloc_zeroed(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr>154 unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
155 NonNull::new(GlobalAlloc::alloc_zeroed(self, layout)).ok_or(AllocErr)
156 }
157
158 #[inline]
dealloc(&mut self, ptr: NonNull<u8>, layout: Layout)159 unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
160 GlobalAlloc::dealloc(self, ptr.as_ptr(), layout)
161 }
162
163 #[inline]
realloc( &mut self, ptr: NonNull<u8>, layout: Layout, new_size: usize, ) -> Result<NonNull<u8>, AllocErr>164 unsafe fn realloc(
165 &mut self,
166 ptr: NonNull<u8>,
167 layout: Layout,
168 new_size: usize,
169 ) -> Result<NonNull<u8>, AllocErr> {
170 NonNull::new(GlobalAlloc::realloc(self, ptr.as_ptr(), layout, new_size)).ok_or(AllocErr)
171 }
172
173 #[inline]
alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr>174 unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
175 let flags = layout_to_flags(layout.align(), layout.size());
176 let ptr = ffi::mallocx(layout.size(), flags);
177 if let Some(nonnull) = NonNull::new(ptr as *mut u8) {
178 let excess = ffi::nallocx(layout.size(), flags);
179 Ok(Excess(nonnull, excess))
180 } else {
181 Err(AllocErr)
182 }
183 }
184
185 #[inline]
realloc_excess( &mut self, ptr: NonNull<u8>, layout: Layout, new_size: usize, ) -> Result<Excess, AllocErr>186 unsafe fn realloc_excess(
187 &mut self,
188 ptr: NonNull<u8>,
189 layout: Layout,
190 new_size: usize,
191 ) -> Result<Excess, AllocErr> {
192 let flags = layout_to_flags(layout.align(), new_size);
193 let ptr = ffi::rallocx(ptr.cast().as_ptr(), new_size, flags);
194 if let Some(nonnull) = NonNull::new(ptr as *mut u8) {
195 let excess = ffi::nallocx(new_size, flags);
196 Ok(Excess(nonnull, excess))
197 } else {
198 Err(AllocErr)
199 }
200 }
201
202 #[inline]
usable_size(&self, layout: &Layout) -> (usize, usize)203 fn usable_size(&self, layout: &Layout) -> (usize, usize) {
204 let flags = layout_to_flags(layout.align(), layout.size());
205 unsafe {
206 let max = ffi::nallocx(layout.size(), flags);
207 (layout.size(), max)
208 }
209 }
210
211 #[inline]
grow_in_place( &mut self, ptr: NonNull<u8>, layout: Layout, new_size: usize, ) -> Result<(), CannotReallocInPlace>212 unsafe fn grow_in_place(
213 &mut self,
214 ptr: NonNull<u8>,
215 layout: Layout,
216 new_size: usize,
217 ) -> Result<(), CannotReallocInPlace> {
218 let flags = layout_to_flags(layout.align(), new_size);
219 let usable_size = ffi::xallocx(ptr.cast().as_ptr(), new_size, 0, flags);
220 if usable_size >= new_size {
221 Ok(())
222 } else {
223 // `xallocx` returns a size smaller than the requested one to
224 // indicate that the allocation could not be grown in place
225 //
226 // the old allocation remains unaltered
227 Err(CannotReallocInPlace)
228 }
229 }
230
231 #[inline]
shrink_in_place( &mut self, ptr: NonNull<u8>, layout: Layout, new_size: usize, ) -> Result<(), CannotReallocInPlace>232 unsafe fn shrink_in_place(
233 &mut self,
234 ptr: NonNull<u8>,
235 layout: Layout,
236 new_size: usize,
237 ) -> Result<(), CannotReallocInPlace> {
238 if new_size == layout.size() {
239 return Ok(());
240 }
241 let flags = layout_to_flags(layout.align(), new_size);
242 let usable_size = ffi::xallocx(ptr.cast().as_ptr(), new_size, 0, flags);
243
244 if usable_size < layout.size() {
245 // If `usable_size` is smaller than the original size, the
246 // size-class of the allocation was shrunk to the size-class of
247 // `new_size`, and it is safe to deallocate the allocation with
248 // `new_size`:
249 Ok(())
250 } else if usable_size == ffi::nallocx(new_size, flags) {
251 // If the allocation was not shrunk and the size class of `new_size`
252 // is the same as the size-class of `layout.size()`, then the
253 // allocation can be properly deallocated using `new_size` (and also
254 // using `layout.size()` because the allocation did not change)
255
256 // note: when the allocation is not shrunk, `xallocx` returns the
257 // usable size of the original allocation, which in this case matches
258 // that of the requested allocation:
259 debug_assert_eq!(
260 ffi::nallocx(new_size, flags),
261 ffi::nallocx(layout.size(), flags)
262 );
263 Ok(())
264 } else {
265 // If the allocation was not shrunk, but the size-class of
266 // `new_size` is not the same as that of the original allocation,
267 // then shrinking the allocation failed:
268 Err(CannotReallocInPlace)
269 }
270 }
271 }
272
273 /// Return the usable size of the allocation pointed to by ptr.
274 ///
275 /// The return value may be larger than the size that was requested during allocation.
276 /// This function is not a mechanism for in-place `realloc()`;
277 /// rather it is provided solely as a tool for introspection purposes.
278 /// Any discrepancy between the requested allocation size
279 /// and the size reported by this function should not be depended on,
280 /// since such behavior is entirely implementation-dependent.
281 ///
282 /// # Safety
283 ///
284 /// `ptr` must have been allocated by `Jemalloc` and must not have been freed yet.
usable_size<T>(ptr: *const T) -> usize285 pub unsafe fn usable_size<T>(ptr: *const T) -> usize {
286 ffi::malloc_usable_size(ptr as *const c_void)
287 }
288
289 /// Raw bindings to jemalloc
290 mod ffi {
291 pub use tikv_jemalloc_sys::*;
292 }
293