flex_alloc/
alloc.rs

1//! Support for memory allocation.
2
3use core::alloc::Layout;
4#[cfg(not(feature = "allocator-api2"))]
5use core::fmt;
6use core::marker::PhantomData;
7use core::ptr::{self, NonNull};
8#[cfg(feature = "zeroize")]
9use core::slice;
10
11#[cfg(all(feature = "alloc", not(feature = "allocator-api2")))]
12use core::mem::transmute;
13
14#[cfg(all(feature = "alloc", not(feature = "allocator-api2")))]
15use alloc_crate::alloc::{alloc as raw_alloc, dealloc as raw_dealloc};
16
17#[cfg(all(feature = "alloc", feature = "allocator-api2"))]
18pub use allocator_api2::alloc::Global;
19#[cfg(feature = "allocator-api2")]
20pub use allocator_api2::alloc::{AllocError, Allocator};
21
22#[cfg(feature = "zeroize")]
23use zeroize::Zeroize;
24
25#[cfg(all(not(test), feature = "alloc"))]
26pub use alloc_crate::alloc::handle_alloc_error;
27
28#[cfg(any(test, not(feature = "alloc")))]
29/// Custom allocation error handler.
30pub fn handle_alloc_error(layout: Layout) -> ! {
31    panic!("memory allocation of {} bytes failed", layout.size());
32}
33
34/// The AllocError error indicates an allocation failure that may be due to
35/// resource exhaustion or to something wrong when combining the given input
36/// arguments with this allocator.
37#[cfg(not(feature = "allocator-api2"))]
38#[derive(Copy, Clone, Debug, PartialEq, Eq)]
39pub struct AllocError;
40
41#[cfg(not(feature = "allocator-api2"))]
42impl fmt::Display for AllocError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.write_str("memory allocation failed")
45    }
46}
47
48#[cfg(all(feature = "std", not(feature = "allocator-api2")))]
49impl std::error::Error for AllocError {}
50
51/// An implementation of Allocator can allocate, grow, shrink, and deallocate
52/// arbitrary blocks of data described via `Layout`.
53///
54/// Allocator is designed to be implemented on ZSTs, references, or smart
55/// pointers because having an allocator like `MyAlloc([u8; N])` cannot be
56/// moved, without updating the pointers to the allocated memory.
57///
58/// Unlike `GlobalAlloc`, zero-sized allocations are allowed in `Allocator`.
59/// If an underlying allocator does not support this (like `jemalloc`) or
60/// return a null pointer (such as `libc::malloc`), this must be caught by
61/// the implementation.
62///
63/// # Currently allocated memory
64/// Some of the methods require that a memory block be currently allocated via
65/// an allocator. This means that:
66/// - The starting address for that memory block was previously returned by
67///   `allocate`, `grow`, or `shrink`, and
68/// - The memory block has not been subsequently deallocated, where blocks are
69///   either deallocated directly by being passed to deallocate or were change
70///   by being passed to `grow` or `shrink` that returns `Ok`. If `grow` or
71///   `shrink` have returned `Err`, the passed pointer remains valid.
72///
73/// # Memory fitting
74/// Some of the methods require that a layout fit a memory block. What it means
75/// for a layout to "fit" a memory block means (or equivalently, for a memory
76/// block to "fit" a layout) is that the following conditions must hold:
77/// - The block must be allocated with the same alignment as `layout.align()`, and
78/// - The provided `layout.size()` must fall in the range `min..=max`, where:
79///   - `min` is the size of the layout most recently used to allocate the block, and
80///   - `max` is the latest actual size returned from `allocate`, `grow`, or `shrink`.
81///
82/// #Safety
83/// - Memory blocks returned from an allocator must point to valid memory and retain
84/// their validity until the instance and all of its clones are dropped,
85/// - Cloning or moving the allocator must not invalidate memory blocks returned from
86/// this allocator. A cloned allocator must behave like the same allocator, and
87/// - Any pointer to a memory block which is currently allocated may be passed to any
88/// other method of the allocator.
89#[cfg(not(feature = "allocator-api2"))]
90pub unsafe trait Allocator {
91    /// Try to allocate a slice of memory within this allocator instance,
92    /// returning the new allocation.
93    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
94
95    /// Release an allocation produced by this allocator.
96    ///
97    /// # Safety
98    /// The value `ptr` must represent an allocation produced by this allocator, otherwise
99    /// a memory access error may occur. The value `old_layout` must correspond to the
100    /// layout produced by the previous allocation.
101    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
102
103    /// Try to allocate a slice of memory within this allocator instance,
104    /// returning the new allocation. The memory will be initialized with zeroes.
105    #[inline]
106    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
107        let ptr = self.allocate(layout)?;
108        // SAFETY: the result of `allocate` must be properly aligned
109        unsafe { ptr::write_bytes(ptr.cast::<u8>().as_ptr(), 0, ptr.len()) };
110        Ok(ptr)
111    }
112
113    /// Try to extend the size of an allocation to accomodate a new, larger layout.
114    unsafe fn grow(
115        &self,
116        ptr: NonNull<u8>,
117        old_layout: Layout,
118        new_layout: Layout,
119    ) -> Result<NonNull<[u8]>, AllocError> {
120        debug_assert!(
121            new_layout.size() >= old_layout.size(),
122            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
123        );
124
125        // This default implementation simply allocates and copies over the contents.
126        // NB: not copying the entire previous buffer seems to defeat some automatic
127        // optimization and results in much worse performance (on MacOS 14 at least).
128        let new_ptr = self.allocate(new_layout)?;
129        let cp_len = old_layout.size().min(new_ptr.len());
130        if cp_len > 0 {
131            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), cp_len);
132        }
133        self.deallocate(ptr, old_layout);
134        Ok(new_ptr)
135    }
136
137    /// Try to extend the size of an allocation to accomodate a new, larger layout.
138    /// Fill the extra capacity with zeros.
139    unsafe fn grow_zeroed(
140        &self,
141        ptr: NonNull<u8>,
142        old_layout: Layout,
143        new_layout: Layout,
144    ) -> Result<NonNull<[u8]>, AllocError> {
145        debug_assert!(
146            new_layout.size() >= old_layout.size(),
147            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
148        );
149
150        // This default implementation simply allocates and copies over the contents.
151        // NB: not copying the entire previous buffer seems to defeat some automatic
152        // optimization and results in much worse performance (on MacOS 14 at least).
153        let new_ptr = self.allocate_zeroed(new_layout)?;
154        let cp_len = old_layout.size().min(new_ptr.len());
155        if cp_len > 0 {
156            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), cp_len);
157        }
158        self.deallocate(ptr, old_layout);
159        Ok(new_ptr)
160    }
161
162    /// Try to reduce the size of an allocation to accomodate a new, smaller layout.
163    unsafe fn shrink(
164        &self,
165        ptr: NonNull<u8>,
166        old_layout: Layout,
167        new_layout: Layout,
168    ) -> Result<NonNull<[u8]>, AllocError> {
169        debug_assert!(
170            new_layout.size() <= old_layout.size(),
171            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
172        );
173
174        // This default implementation simply allocates and copies over the contents.
175        // NB: not copying the entire previous buffer seems to defeat some automatic
176        // optimization and results in much worse performance (on MacOS 14 at least).
177        let new_ptr = self.allocate(new_layout)?;
178        let cp_len = old_layout.size().min(new_ptr.len());
179        if cp_len > 0 {
180            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), cp_len);
181        }
182        self.deallocate(ptr, old_layout);
183        Ok(new_ptr)
184    }
185
186    /// Obtain a reference to this allocator type.
187    #[inline(always)]
188    fn by_ref(&self) -> &Self
189    where
190        Self: Sized,
191    {
192        self
193    }
194}
195
196/// For all types which are an allocator or reference an allocator, enable their
197/// usage as a target for allocation.
198pub trait AllocateIn: Sized {
199    /// The type of the allocator instance
200    type Alloc: Allocator;
201
202    /// Try to allocate a slice of a memory corresponding to `layout`, returning
203    /// the new allocation and the allocator instance
204    fn allocate_in(self, layout: Layout) -> Result<(NonNull<[u8]>, Self::Alloc), AllocError>;
205
206    /// Try to allocate a slice of a memory corresponding to `layout`, returning
207    /// the new allocation and the allocator instance. The memory will be initialized
208    /// with zeroes.
209    #[inline]
210    fn allocate_zeroed_in(
211        self,
212        layout: Layout,
213    ) -> Result<(NonNull<[u8]>, Self::Alloc), AllocError> {
214        let (ptr, alloc) = self.allocate_in(layout)?;
215        // SAFETY: the result of `allocate` must be properly aligned
216        unsafe { ptr::write_bytes(ptr.cast::<u8>().as_ptr(), 0, ptr.len()) };
217        Ok((ptr, alloc))
218    }
219}
220
221impl<A: Allocator> AllocateIn for A {
222    type Alloc = A;
223
224    #[inline]
225    fn allocate_in(self, layout: Layout) -> Result<(NonNull<[u8]>, Self::Alloc), AllocError> {
226        let data = self.allocate(layout)?;
227        Ok((data, self))
228    }
229
230    #[inline]
231    fn allocate_zeroed_in(
232        self,
233        layout: Layout,
234    ) -> Result<(NonNull<[u8]>, Self::Alloc), AllocError> {
235        let data = self.allocate_zeroed(layout)?;
236        Ok((data, self))
237    }
238}
239
240/// A trait implemented by allocators supporting a constant initializer.
241/// This cannot use ConstDefault as it is not implemented for the external
242/// `Global` allocator.
243pub trait AllocatorDefault: Allocator + Clone + Default {
244    /// The constant initializer for this allocator.
245    const DEFAULT: Self;
246}
247
248/// A marker trait for allocators which zeroize on deallocation.
249pub trait AllocatorZeroizes: Allocator {}
250
251/// Attach an allocator to a fixed allocation buffer. Once the initial
252/// buffer is exhausted, additional buffer(s) may be requested from the
253/// new allocator instance.
254pub trait SpillAlloc<'a>: Sized {
255    /// The concrete type of resulting allocation target.
256    type NewIn<A: 'a>;
257
258    /// Consume the allocator instance, returning a new allocator
259    /// which spills into the Global allocator.
260    #[inline]
261    fn spill_alloc(self) -> Self::NewIn<Global> {
262        Self::spill_alloc_in(self, Global)
263    }
264
265    /// Consume the allocator instance, returning a new allocator
266    /// which spills into the provided allocator instance `alloc`.
267    fn spill_alloc_in<A: Allocator + 'a>(self, alloc: A) -> Self::NewIn<A>;
268}
269
270/// The global memory allocator.
271///
272/// When the `alloc` feature is enabled, this type implements the `Allocator`
273/// trait by forwarding calls to the allocator registered with the
274/// `#[global_allocator]` attribute if there is one, or the `std` crate's default.
275#[cfg(any(not(feature = "alloc"), not(feature = "allocator-api2")))]
276#[derive(Debug, Clone, PartialEq, Eq)]
277#[cfg_attr(feature = "alloc", derive(Default, Copy))]
278pub struct Global;
279
280#[cfg(all(feature = "alloc", not(feature = "allocator-api2")))]
281unsafe impl Allocator for Global {
282    #[inline]
283    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
284        let ptr = if layout.size() == 0 {
285            // FIXME: use Layout::dangling when stabilized
286            // SAFETY: layout alignments are guaranteed to be non-zero.
287            #[allow(clippy::useless_transmute)]
288            unsafe {
289                NonNull::new_unchecked(transmute(layout.align()))
290            }
291        } else {
292            let Some(ptr) = NonNull::new(unsafe { raw_alloc(layout) }) else {
293                return Err(AllocError);
294            };
295            ptr
296        };
297        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
298    }
299
300    #[inline]
301    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
302        if layout.size() > 0 {
303            raw_dealloc(ptr.as_ptr(), layout);
304        }
305    }
306}
307
308#[cfg(not(feature = "alloc"))]
309// Stub implementation to allow Global as the default allocator type
310// even when the `alloc` feature is not enabled. Any usage as an allocator
311// will result in a panic.
312unsafe impl Allocator for Global {
313    fn allocate(&self, _layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
314        unimplemented!();
315    }
316
317    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
318        unimplemented!();
319    }
320}
321
322#[cfg(feature = "alloc")]
323impl AllocatorDefault for Global {
324    const DEFAULT: Self = Global;
325}
326
327/// An allocator backed by a fixed storage buffer.
328#[derive(Debug, Default, PartialEq, Eq)]
329pub struct Fixed<'a>(PhantomData<&'a mut ()>);
330
331unsafe impl Allocator for Fixed<'_> {
332    #[inline(always)]
333    fn allocate(&self, _layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
334        Err(AllocError)
335    }
336
337    #[inline(always)]
338    unsafe fn grow(
339        &self,
340        ptr: NonNull<u8>,
341        old_layout: Layout,
342        new_layout: Layout,
343    ) -> Result<NonNull<[u8]>, AllocError> {
344        if old_layout.align() != new_layout.align() || new_layout.size() > old_layout.size() {
345            Err(AllocError)
346        } else {
347            Ok(NonNull::slice_from_raw_parts(ptr, old_layout.size()))
348        }
349    }
350
351    #[inline(always)]
352    unsafe fn shrink(
353        &self,
354        ptr: NonNull<u8>,
355        old_layout: Layout,
356        new_layout: Layout,
357    ) -> Result<NonNull<[u8]>, AllocError> {
358        if old_layout.align() != new_layout.align() || new_layout.size() > old_layout.size() {
359            Err(AllocError)
360        } else {
361            Ok(NonNull::slice_from_raw_parts(ptr, old_layout.size()))
362        }
363    }
364
365    #[inline]
366    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
367}
368
369impl<'a> AllocatorDefault for Fixed<'a> {
370    const DEFAULT: Self = Self(PhantomData);
371}
372
373impl Clone for Fixed<'_> {
374    fn clone(&self) -> Self {
375        Fixed::DEFAULT
376    }
377}
378
379/// An allocator which may represent either a fixed allocation or a dynamic
380/// allocation with an allocator instance `A`.
381#[derive(Debug)]
382pub struct Spill<'a, A> {
383    alloc: A,
384    initial: *const u8,
385    _fixed: Fixed<'a>,
386}
387
388impl<'a, A> Spill<'a, A> {
389    pub(crate) const fn new(alloc: A, initial: *const u8, fixed: Fixed<'a>) -> Self {
390        Self {
391            alloc,
392            initial,
393            _fixed: fixed,
394        }
395    }
396}
397
398impl<A: Default + Allocator> Default for Spill<'_, A> {
399    #[inline]
400    fn default() -> Self {
401        Self::new(A::default(), ptr::null(), Fixed::DEFAULT)
402    }
403}
404
405unsafe impl<A: Allocator> Allocator for Spill<'_, A> {
406    #[inline]
407    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
408        self.alloc.allocate(layout)
409    }
410
411    #[inline]
412    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
413        if !ptr::eq(self.initial, ptr.as_ptr()) {
414            self.alloc.deallocate(ptr, layout)
415        }
416    }
417}
418
419impl<'a, A: Default + Allocator> Clone for Spill<'a, A> {
420    fn clone(&self) -> Self {
421        Self::default()
422    }
423}
424
425impl<'a, A: AllocatorDefault> AllocatorDefault for Spill<'a, A> {
426    const DEFAULT: Self = Self::new(A::DEFAULT, ptr::null(), Fixed::DEFAULT);
427}
428
429#[cfg(feature = "zeroize")]
430/// An allocator which allocates via `A` and zeroizes all buffers when they are released.
431#[derive(Debug, Default, Clone, Copy)]
432pub struct ZeroizingAlloc<A>(pub A);
433
434#[cfg(feature = "zeroize")]
435unsafe impl<A: Allocator> Allocator for ZeroizingAlloc<A> {
436    #[inline]
437    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
438        self.0.allocate(layout)
439    }
440
441    // The default implementation of `try_resize`` will always allocate a new buffer
442    // and release the old one, allowing it to be zeroized below.
443
444    #[inline]
445    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
446        if layout.size() > 0 {
447            let mem = slice::from_raw_parts_mut(ptr.as_ptr(), layout.size());
448            mem.zeroize();
449        }
450        self.0.deallocate(ptr, layout)
451    }
452}
453
454#[cfg(feature = "zeroize")]
455impl<A: AllocatorDefault> AllocatorDefault for ZeroizingAlloc<A> {
456    const DEFAULT: Self = ZeroizingAlloc(A::DEFAULT);
457}
458
459#[cfg(feature = "zeroize")]
460impl<'a, Z> SpillAlloc<'a> for &'a mut zeroize::Zeroizing<Z>
461where
462    Z: Zeroize + 'a,
463    &'a mut Z: SpillAlloc<'a>,
464{
465    type NewIn<A: 'a> = <&'a mut Z as SpillAlloc<'a>>::NewIn<ZeroizingAlloc<A>>;
466
467    #[inline]
468    fn spill_alloc_in<A: Allocator + 'a>(self, alloc: A) -> Self::NewIn<A> {
469        (&mut **self).spill_alloc_in(ZeroizingAlloc(alloc))
470    }
471}
472
473#[cfg(feature = "zeroize")]
474impl<A: Allocator> AllocatorZeroizes for ZeroizingAlloc<A> {}
475
476/// Convert between types in this crate and standard containers.
477pub trait ConvertAlloc<Target> {
478    /// Convert directly into the target type, ideally without reallocating.
479    fn convert(self) -> Target;
480}