ialloc/allocator/alloc/
global.rs

1use crate::*;
2use crate::meta::*;
3
4use core::alloc::Layout;
5use core::mem::MaybeUninit;
6use core::ptr::NonNull;
7
8
9
10// XXX: Can't implement core::alloc::GlobalAlloc on alloc::alloc::Global
11// #[cfg(    allocator_api = "1.50" )] pub use alloc::alloc::Global;
12// #[cfg(not(allocator_api = "1.50"))]
13
14/// Use <code>[alloc::alloc]::{[alloc](alloc::alloc::alloc), [alloc_zeroed](alloc::alloc::alloc_zeroed), [realloc](alloc::alloc::realloc), [dealloc](alloc::alloc::realloc)}</code>
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Global;
16
17#[cfg(feature = "alloc")] #[cfg(allocator_api = "*")] impl From<Global> for alloc::alloc::Global { fn from(_: Global) -> Self { Self } }
18#[cfg(feature = "alloc")] #[cfg(allocator_api = "*")] impl From<alloc::alloc::Global> for Global { fn from(_: alloc::alloc::Global) -> Self { Self } }
19
20
21
22// meta::*
23
24impl Meta for Global {
25    type Error                  = ();
26    const MAX_ALIGN : Alignment = Alignment::MAX;
27    const MAX_SIZE  : usize     = usize::MAX/2;
28    const ZST_SUPPORTED : bool  = true;
29}
30
31impl ZstSupported for Global {}
32
33// SAFETY: ✔️ simply returns dangling pointers when size is zero
34unsafe impl ZstInfalliable for Global {}
35
36// SAFETY: ✔️ global state only
37unsafe impl Stateless for Global {}
38
39
40
41// fat::*
42
43// SAFETY: ✔️ all `impl fat::* for Global` are compatible with each other and return allocations compatible with their alignments
44unsafe impl fat::Alloc for Global {
45    fn alloc_uninit(&self, layout: Layout) -> Result<AllocNN, Self::Error> {
46        match layout.size() {
47            0                       => Ok(util::nn::dangling(layout)),
48            n if n > Self::MAX_SIZE => Err(()),
49            _ => {
50                debug_assert!(layout.pad_to_align().size() <= Self::MAX_SIZE, "bug: undefined behavior: Layout when padded to alignment exceeds isize::MAX, which violates Layout's invariants");
51                // SAFETY: ✔️ we just ensured `layout` has a valid (nonzero, <= isize::MAX) size
52                let alloc = unsafe { alloc::alloc::alloc(layout) };
53                NonNull::new(alloc.cast()).ok_or(())
54            }
55        }
56    }
57
58    fn alloc_zeroed(&self, layout: Layout) -> Result<AllocNN0, Self::Error> {
59        match layout.size() {
60            0                       => Ok(util::nn::dangling(layout)),
61            n if n > Self::MAX_SIZE => Err(()),
62            _ => {
63                debug_assert!(layout.pad_to_align().size() <= Self::MAX_SIZE, "bug: undefined behavior: Layout when padded to alignment exceeds isize::MAX, which violates Layout's invariants");
64                // SAFETY: ✔️ we just ensured `layout` has a nonzero size
65                let alloc = unsafe { alloc::alloc::alloc_zeroed(layout) };
66                NonNull::new(alloc.cast()).ok_or(())
67            }
68        }
69    }
70}
71
72// SAFETY: ✔️ all `impl fat::* for Global` are compatible with each other and return allocations compatible with their alignments
73unsafe impl fat::Free for Global {
74    unsafe fn free(&self, ptr: AllocNN, layout: Layout) {
75        if layout.size() == 0 { return }
76        // SAFETY: ✔️ `ptr` belongs to `self` and `layout` describes the allocation per [`fat::Free::free`]'s documented safety preconditions
77        unsafe { alloc::alloc::dealloc(ptr.as_ptr().cast(), layout) }
78    }
79}
80
81// SAFETY: ✔️ all `impl fat::* for Global` are compatible with each other and return allocations compatible with their alignments
82unsafe impl fat::Realloc for Global {
83    unsafe fn realloc_uninit(&self, ptr: AllocNN, old_layout: Layout, new_layout: Layout) -> Result<AllocNN, Self::Error> {
84        if new_layout.size() > Self::MAX_SIZE {
85            Err(())
86        } else if old_layout == new_layout {
87            Ok(ptr)
88        } else if old_layout.align() != new_layout.align() || old_layout.size() == 0 || new_layout.size() == 0 {
89            let alloc = fat::Alloc::alloc_uninit(self, new_layout)?;
90            {
91                // SAFETY: ✔️ `ptr` is valid for `old_layout` by `fat::Realloc::realloc_uninit`'s documented safety preconditions
92                // SAFETY: ✔️ `alloc` was just allocated using `new_layout`
93                #![allow(clippy::undocumented_unsafe_blocks)]
94
95                let old : &    [MaybeUninit<u8>] = unsafe { util::slice::from_raw_bytes_layout    (ptr,   old_layout) };
96                let new : &mut [MaybeUninit<u8>] = unsafe { util::slice::from_raw_bytes_layout_mut(alloc, new_layout) };
97                let n = old.len().min(new.len());
98                new[..n].copy_from_slice(&old[..n]);
99            }
100            // SAFETY: ✔️ `ptr` belongs to `self`, and `old_layout` should describe the allocation, by `fat::Realloc::realloc_uninit`'s documented safety preconditions
101            unsafe { fat::Free::free(self, ptr, old_layout) };
102            Ok(alloc)
103        } else {
104            // SAFETY: ✔️ layouts have same alignments
105            // SAFETY: ✔️ layouts have nonzero sizes
106            // SAFETY: ✔️ `ptr` belongs to `self` by `fat::Realloc::realloc_uninit`'s documented safety preconditions
107            // SAFETY: ✔️ `ptr` is valid for `old_layout` by `fat::Realloc::realloc_uninit`'s documented safety preconditions
108            // SAFETY: ✔️ `new_layout.size()` was bounds checked at start of fn
109            let alloc = unsafe { alloc::alloc::realloc(ptr.as_ptr().cast(), old_layout, new_layout.size()) };
110            NonNull::new(alloc.cast()).ok_or(())
111        }
112    }
113
114    // realloc_uninit "could" be specialized to use alloc_zeroed on alloc realignment, but it's unclear if that'd be a perf gain (free zeroed memory pages) or perf loss (double zeroing)
115}
116
117
118
119// core::alloc::*
120
121#[allow(clippy::undocumented_unsafe_blocks)] // SAFETY: ✔️ alloc::alloc::*'s preconditions are documented in terms of GlobalAlloc's equivalents
122unsafe impl core::alloc::GlobalAlloc for Global {
123    unsafe fn alloc(&self, layout: Layout)                                  -> *mut u8  { unsafe { alloc::alloc::alloc(layout) } }
124    unsafe fn alloc_zeroed(&self, layout: Layout)                           -> *mut u8  { unsafe { alloc::alloc::alloc_zeroed(layout) } }
125    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout)                              { unsafe { alloc::alloc::dealloc(ptr, layout) } }
126    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8  { unsafe { alloc::alloc::realloc(ptr, layout, new_size) } }
127}
128
129//  • "Allocator is designed to be implemented on ZSTs, references, or smart pointers because having an allocator like MyAlloc([u8; N]) cannot be moved, without updating the pointers to the allocated memory."
130//    ✔️ Trivial: `Global` is indeed a ZST containing none of the memory intended for allocation.
131//
132//  • "Unlike GlobalAlloc, zero-sized allocations are allowed in Allocator. If an underlying allocator does not support this (like jemalloc) or return a null pointer (such as libc::malloc), this must be caught by the implementation."
133//    ✔️ `ZST_SUPPORTED` is currently `true`, matching behavior of `alloc::alloc::Global`.
134//
135// ## Currently allocated memory
136//
137//  • "Some of the methods require that a memory block be currently allocated via an allocator. This means that [...]"
138//    ✔️ Trivial: All impl fns use `fat::*` traits, which impose equivalent allocation validity requirements.
139//
140//
141// ## Memory fitting
142//
143// ⚠️ `fat::*`'s layout requirements are currently a bit stricter than core::alloc::Allocator 's:
144// <https://doc.rust-lang.org/std/alloc/trait.Allocator.html#memory-fitting>
145//
146// Both interfaces require identical `layout.align()`ments.
147// `fat::*` requires identical `layout.size()`s.
148// `core::alloc::Allocator` allows a `layout.size()` in the range `min ..= max` where:
149//  • `min` is the size of the layout most recently used to allocate the block, and
150//  • `max` is the latest actual size returned from `allocate`, `grow`, or `shrink`.
151//
152// The implementation of this interface unifies requirements by ensuring `min` = `max`.
153// That is, all interfaces return slices based *exactly* on `layout.size()`.
154//
155//
156// ## Trait-level Safety Requirements:
157//
158//  • "Memory blocks returned from an allocator must point to valid memory and retain their validity until the instance and all of its copies and clones are dropped,"
159//    ✔️ Trivial: all allocations remain valid until deallocated, even if all `Global`s are dropped.
160//
161//  • "copying, cloning, or moving the allocator must not invalidate memory blocks returned from this allocator. A copied or cloned allocator must behave like the same allocator, and"
162//    ✔️ Trivial: `Global` contains none of the memory nor bookkeeping for memory blocks
163//
164//  • "any pointer to a memory block which is currently allocated may be passed to any other method of the allocator."
165//    ✔️ Trivial: all fns use `fat::*` allocators exclusively, which are intended to be intercompatible.
166//
167#[cfg(allocator_api = "1.50")] unsafe impl core::alloc::Allocator for Global {
168    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
169        let data = fat::Alloc::alloc_uninit(self, layout).map_err(|_| core::alloc::AllocError)?;
170        Ok(util::nn::slice_from_raw_parts(data.cast(), layout.size()))
171    }
172
173    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
174        let data = fat::Alloc::alloc_zeroed(self, layout).map_err(|_| core::alloc::AllocError)?;
175        Ok(util::nn::slice_from_raw_parts(data.cast(), layout.size()))
176    }
177
178    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
179        unsafe { fat::Free::free(self, ptr.cast(), layout) }
180    }
181
182    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
183        let data = unsafe { fat::Realloc::realloc_uninit(self, ptr.cast(), old_layout, new_layout) }.map_err(|_| core::alloc::AllocError)?;
184        Ok(util::nn::slice_from_raw_parts(data.cast(), new_layout.size()))
185    }
186
187    unsafe fn grow_zeroed(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
188        let data = unsafe { fat::Realloc::realloc_zeroed(self, ptr.cast(), old_layout, new_layout) }.map_err(|_| core::alloc::AllocError)?;
189        Ok(util::nn::slice_from_raw_parts(data.cast(), new_layout.size()))
190    }
191
192    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
193        let data = unsafe { fat::Realloc::realloc_uninit(self, ptr.cast(), old_layout, new_layout) }.map_err(|_| core::alloc::AllocError)?;
194        Ok(util::nn::slice_from_raw_parts(data.cast(), new_layout.size()))
195    }
196}
197
198
199
200#[test] fn fat_alignment()          { fat::test::alignment(Global) }
201#[test] fn fat_edge_case_sizes()    { fat::test::edge_case_sizes(Global) }
202#[test] fn fat_uninit()             { if !cfg!(target_os = "linux") { unsafe { fat::test::uninit_alloc_unsound(Global) } } }
203#[test] fn fat_zeroed()             { fat::test::zeroed_alloc(Global) }
204#[test] fn fat_zst_support()        { fat::test::zst_supported_accurate(Global) }