Skip to main content

linera_jemallocator/
lib.rs

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")]
25use core::alloc::{Alloc, AllocErr, CannotReallocInPlace, Excess};
26use core::alloc::{GlobalAlloc, Layout};
27#[cfg(feature = "alloc_trait")]
28use core::ptr::NonNull;
29
30use 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(target_arch = "arm", target_arch = "mips", target_arch = "powerpc"))]
46const ALIGNOF_MAX_ALIGN_T: usize = 8;
47#[cfg(any(
48    target_arch = "x86",
49    target_arch = "x86_64",
50    target_arch = "aarch64",
51    target_arch = "powerpc64",
52    target_arch = "loongarch64",
53    target_arch = "mips64",
54    target_arch = "riscv32",
55    target_arch = "riscv64",
56    target_arch = "s390x",
57    target_arch = "sparc64"
58))]
59const ALIGNOF_MAX_ALIGN_T: usize = 16;
60
61/// If `align` is less than `_Alignof(max_align_t)`, and if the requested
62/// allocation `size` is larger than the alignment, we are guaranteed to get a
63/// suitably aligned allocation by default, without passing extra flags, and
64/// this function returns `0`.
65///
66/// Otherwise, it returns the alignment flag to pass to the jemalloc APIs.
67fn layout_to_flags(align: usize, size: usize) -> c_int {
68    if align <= ALIGNOF_MAX_ALIGN_T && align <= size {
69        0
70    } else {
71        ffi::MALLOCX_ALIGN(align)
72    }
73}
74
75// Assumes a condition that always must hold.
76macro_rules! assume {
77    ($e:expr) => {
78        debug_assert!($e);
79        if !($e) {
80            core::hint::unreachable_unchecked();
81        }
82    };
83}
84
85/// Handle to the jemalloc allocator
86///
87/// This type implements the `GlobalAllocAlloc` trait, allowing usage a global allocator.
88///
89/// When the `alloc_trait` feature of this crate is enabled, it also implements the `Alloc` trait,
90/// allowing usage in collections.
91#[derive(Copy, Clone, Default, Debug)]
92pub struct Jemalloc;
93
94unsafe impl GlobalAlloc for Jemalloc {
95    #[inline]
96    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
97        assume!(layout.size() != 0);
98        let flags = layout_to_flags(layout.align(), layout.size());
99        let ptr = if flags == 0 {
100            ffi::malloc(layout.size())
101        } else {
102            ffi::mallocx(layout.size(), flags)
103        };
104        ptr as *mut u8
105    }
106
107    #[inline]
108    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
109        assume!(layout.size() != 0);
110        let flags = layout_to_flags(layout.align(), layout.size());
111        let ptr = if flags == 0 {
112            ffi::calloc(1, layout.size())
113        } else {
114            ffi::mallocx(layout.size(), flags | ffi::MALLOCX_ZERO)
115        };
116        ptr as *mut u8
117    }
118
119    #[inline]
120    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
121        assume!(!ptr.is_null());
122        assume!(layout.size() != 0);
123        let flags = layout_to_flags(layout.align(), layout.size());
124        ffi::sdallocx(ptr as *mut c_void, layout.size(), flags)
125    }
126
127    #[inline]
128    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
129        assume!(layout.size() != 0);
130        assume!(new_size != 0);
131        let flags = layout_to_flags(layout.align(), new_size);
132        let ptr = if flags == 0 {
133            ffi::realloc(ptr as *mut c_void, new_size)
134        } else {
135            ffi::rallocx(ptr as *mut c_void, new_size, flags)
136        };
137        ptr as *mut u8
138    }
139}
140
141#[cfg(feature = "alloc_trait")]
142unsafe impl Alloc for Jemalloc {
143    #[inline]
144    unsafe fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
145        NonNull::new(GlobalAlloc::alloc(self, layout)).ok_or(AllocErr)
146    }
147
148    #[inline]
149    unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
150        NonNull::new(GlobalAlloc::alloc_zeroed(self, layout)).ok_or(AllocErr)
151    }
152
153    #[inline]
154    unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
155        GlobalAlloc::dealloc(self, ptr.as_ptr(), layout)
156    }
157
158    #[inline]
159    unsafe fn realloc(
160        &mut self,
161        ptr: NonNull<u8>,
162        layout: Layout,
163        new_size: usize,
164    ) -> Result<NonNull<u8>, AllocErr> {
165        NonNull::new(GlobalAlloc::realloc(self, ptr.as_ptr(), layout, new_size)).ok_or(AllocErr)
166    }
167
168    #[inline]
169    unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
170        let flags = layout_to_flags(layout.align(), layout.size());
171        let ptr = ffi::mallocx(layout.size(), flags);
172        if let Some(nonnull) = NonNull::new(ptr as *mut u8) {
173            let excess = ffi::nallocx(layout.size(), flags);
174            Ok(Excess(nonnull, excess))
175        } else {
176            Err(AllocErr)
177        }
178    }
179
180    #[inline]
181    unsafe fn realloc_excess(
182        &mut self,
183        ptr: NonNull<u8>,
184        layout: Layout,
185        new_size: usize,
186    ) -> Result<Excess, AllocErr> {
187        let flags = layout_to_flags(layout.align(), new_size);
188        let ptr = ffi::rallocx(ptr.cast().as_ptr(), new_size, flags);
189        if let Some(nonnull) = NonNull::new(ptr as *mut u8) {
190            let excess = ffi::nallocx(new_size, flags);
191            Ok(Excess(nonnull, excess))
192        } else {
193            Err(AllocErr)
194        }
195    }
196
197    #[inline]
198    fn usable_size(&self, layout: &Layout) -> (usize, usize) {
199        let flags = layout_to_flags(layout.align(), layout.size());
200        unsafe {
201            let max = ffi::nallocx(layout.size(), flags);
202            (layout.size(), max)
203        }
204    }
205
206    #[inline]
207    unsafe fn grow_in_place(
208        &mut self,
209        ptr: NonNull<u8>,
210        layout: Layout,
211        new_size: usize,
212    ) -> Result<(), CannotReallocInPlace> {
213        let flags = layout_to_flags(layout.align(), new_size);
214        let usable_size = ffi::xallocx(ptr.cast().as_ptr(), new_size, 0, flags);
215        if usable_size >= new_size {
216            Ok(())
217        } else {
218            // `xallocx` returns a size smaller than the requested one to
219            // indicate that the allocation could not be grown in place
220            //
221            // the old allocation remains unaltered
222            Err(CannotReallocInPlace)
223        }
224    }
225
226    #[inline]
227    unsafe fn shrink_in_place(
228        &mut self,
229        ptr: NonNull<u8>,
230        layout: Layout,
231        new_size: usize,
232    ) -> Result<(), CannotReallocInPlace> {
233        if new_size == layout.size() {
234            return Ok(());
235        }
236        let flags = layout_to_flags(layout.align(), new_size);
237        let usable_size = ffi::xallocx(ptr.cast().as_ptr(), new_size, 0, flags);
238
239        if usable_size < layout.size() {
240            // If `usable_size` is smaller than the original size, the
241            // size-class of the allocation was shrunk to the size-class of
242            // `new_size`, and it is safe to deallocate the allocation with
243            // `new_size`:
244            Ok(())
245        } else if usable_size == ffi::nallocx(new_size, flags) {
246            // If the allocation was not shrunk and the size class of `new_size`
247            // is the same as the size-class of `layout.size()`, then the
248            // allocation can be properly deallocated using `new_size` (and also
249            // using `layout.size()` because the allocation did not change)
250
251            // note: when the allocation is not shrunk, `xallocx` returns the
252            // usable size of the original allocation, which in this case matches
253            // that of the requested allocation:
254            debug_assert_eq!(
255                ffi::nallocx(new_size, flags),
256                ffi::nallocx(layout.size(), flags)
257            );
258            Ok(())
259        } else {
260            // If the allocation was not shrunk, but the size-class of
261            // `new_size` is not the same as that of the original allocation,
262            // then shrinking the allocation failed:
263            Err(CannotReallocInPlace)
264        }
265    }
266}
267
268/// Return the usable size of the allocation pointed to by ptr.
269///
270/// The return value may be larger than the size that was requested during allocation.
271/// This function is not a mechanism for in-place `realloc()`;
272/// rather it is provided solely as a tool for introspection purposes.
273/// Any discrepancy between the requested allocation size
274/// and the size reported by this function should not be depended on,
275/// since such behavior is entirely implementation-dependent.
276///
277/// # Safety
278///
279/// `ptr` must have been allocated by `Jemalloc` and must not have been freed yet.
280pub unsafe fn usable_size<T>(ptr: *const T) -> usize {
281    ffi::malloc_usable_size(ptr as *const c_void)
282}
283
284/// Raw bindings to jemalloc
285mod ffi {
286    pub use linera_jemalloc_sys::*;
287}