bump_scope/alloc/
global.rs

1//! Memory allocation APIs
2#![expect(clippy::unused_self)]
3
4use alloc_crate::alloc::{alloc, alloc_zeroed, dealloc, realloc};
5use core::{
6    alloc::Layout,
7    hint,
8    ptr::{self, NonNull},
9};
10
11use crate::polyfill;
12
13use super::{AllocError, Allocator};
14
15/// The global memory allocator.
16///
17/// This type implements the [`Allocator`] trait by forwarding calls
18/// to the allocator registered with the `#[global_allocator]` attribute
19/// if there is one, or the `std` crate’s default.
20#[derive(Copy, Clone, Default, Debug)]
21// the compiler needs to know when a Box uses the global allocator vs a custom one
22pub struct Global;
23
24impl Global {
25    #[inline]
26    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
27    fn alloc_impl(&self, layout: Layout, zeroed: bool) -> Result<NonNull<[u8]>, AllocError> {
28        match layout.size() {
29            0 => Ok(NonNull::slice_from_raw_parts(polyfill::layout::dangling(layout), 0)),
30            // SAFETY: `layout` is non-zero in size,
31            size => unsafe {
32                let raw_ptr = if zeroed { alloc_zeroed(layout) } else { alloc(layout) };
33                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
34                Ok(NonNull::slice_from_raw_parts(ptr, size))
35            },
36        }
37    }
38
39    // SAFETY: Same as `Allocator::grow`
40    #[inline]
41    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
42    unsafe fn grow_impl(
43        &self,
44        ptr: NonNull<u8>,
45        old_layout: Layout,
46        new_layout: Layout,
47        zeroed: bool,
48    ) -> Result<NonNull<[u8]>, AllocError> {
49        debug_assert!(
50            new_layout.size() >= old_layout.size(),
51            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
52        );
53
54        match old_layout.size() {
55            0 => self.alloc_impl(new_layout, zeroed),
56
57            // SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
58            // as required by safety conditions. Other conditions must be upheld by the caller
59            old_size if old_layout.align() == new_layout.align() => unsafe {
60                let new_size = new_layout.size();
61
62                // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
63                hint::assert_unchecked(new_size >= old_layout.size());
64
65                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
66                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
67                if zeroed {
68                    raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
69                }
70                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
71            },
72
73            // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
74            // both the old and new memory allocation are valid for reads and writes for `old_size`
75            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
76            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
77            // for `dealloc` must be upheld by the caller.
78            old_size => unsafe {
79                let new_ptr = self.alloc_impl(new_layout, zeroed)?;
80                ptr::copy_nonoverlapping(ptr.as_ptr(), polyfill::non_null::as_mut_ptr(new_ptr), old_size);
81                self.deallocate(ptr, old_layout);
82                Ok(new_ptr)
83            },
84        }
85    }
86}
87
88unsafe impl Allocator for Global {
89    #[inline]
90    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
91    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
92        self.alloc_impl(layout, false)
93    }
94
95    #[inline]
96    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
97    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
98        self.alloc_impl(layout, true)
99    }
100
101    #[inline]
102    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
103    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
104        if layout.size() != 0 {
105            // SAFETY:
106            // * We have checked that `layout` is non-zero in size.
107            // * The caller is obligated to provide a layout that "fits", and in this case,
108            //   "fit" always means a layout that is equal to the original, because our
109            //   `allocate()`, `grow()`, and `shrink()` implementations never returns a larger
110            //   allocation than requested.
111            // * Other conditions must be upheld by the caller, as per `Allocator::deallocate()`'s
112            //   safety documentation.
113            unsafe { dealloc(ptr.as_ptr(), layout) }
114        }
115    }
116
117    #[inline]
118    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
119    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
120        // SAFETY: all conditions must be upheld by the caller
121        unsafe { self.grow_impl(ptr, old_layout, new_layout, false) }
122    }
123
124    #[inline]
125    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
126    unsafe fn grow_zeroed(
127        &self,
128        ptr: NonNull<u8>,
129        old_layout: Layout,
130        new_layout: Layout,
131    ) -> Result<NonNull<[u8]>, AllocError> {
132        // SAFETY: all conditions must be upheld by the caller
133        unsafe { self.grow_impl(ptr, old_layout, new_layout, true) }
134    }
135
136    #[inline]
137    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
138    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
139        debug_assert!(
140            new_layout.size() <= old_layout.size(),
141            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
142        );
143
144        match new_layout.size() {
145            // SAFETY: conditions must be upheld by the caller
146            0 => unsafe {
147                self.deallocate(ptr, old_layout);
148                Ok(NonNull::slice_from_raw_parts(polyfill::layout::dangling(new_layout), 0))
149            },
150
151            // SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
152            new_size if old_layout.align() == new_layout.align() => unsafe {
153                // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
154                hint::assert_unchecked(new_size <= old_layout.size());
155
156                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
157                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
158                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
159            },
160
161            // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
162            // both the old and new memory allocation are valid for reads and writes for `new_size`
163            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
164            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
165            // for `dealloc` must be upheld by the caller.
166            new_size => unsafe {
167                let new_ptr = self.allocate(new_layout)?;
168                ptr::copy_nonoverlapping(ptr.as_ptr(), polyfill::non_null::as_mut_ptr(new_ptr), new_size);
169                self.deallocate(ptr, old_layout);
170                Ok(new_ptr)
171            },
172        }
173    }
174}