Skip to main content

musli_core/alloc/
global.rs

1use core::alloc::Layout;
2use core::cmp;
3use core::mem::{align_of, size_of};
4use core::ptr::NonNull;
5
6use rust_alloc::alloc;
7
8use super::{Alloc, AllocError, Allocator, GlobalAllocator};
9
10/// Global buffer that can be used in combination with an [`Allocator`].
11///
12/// This uses the global allocator.
13///
14/// # Examples
15///
16/// ```
17/// use musli::alloc::{Global, Vec};
18///
19/// let alloc = Global::new();
20///
21/// let mut buf1 = Vec::new_in(alloc);
22/// let mut buf2 = Vec::new_in(alloc);
23//
24/// buf1.extend_from_slice(b"Hello, ")?;
25/// buf2.extend_from_slice(b"world!")?;
26///
27/// assert_eq!(buf1.as_slice(), b"Hello, ");
28/// assert_eq!(buf2.as_slice(), b"world!");
29///
30/// buf1.extend(buf2);
31/// assert_eq!(buf1.as_slice(), b"Hello, world!");
32/// # Ok::<_, musli::alloc::AllocError>(())
33/// ```
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub struct Global;
37
38impl Global {
39    /// Construct a new global allocator.
40    #[inline]
41    pub const fn new() -> Self {
42        Self
43    }
44}
45
46impl Default for Global {
47    #[inline]
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53unsafe impl GlobalAllocator for Global {
54    #[inline]
55    fn __do_not_implement() {}
56
57    #[inline]
58    fn new() -> Self {
59        Self
60    }
61
62    #[inline]
63    fn clone_alloc<T>(alloc: &Self::Alloc<T>) -> Self::Alloc<T> {
64        if size_of::<T>() == 0 {
65            // Zero-sized types are never actually allocated, so cloning must
66            // not go through the global allocator with a zero-sized layout.
67            return GlobalAlloc {
68                data: NonNull::dangling(),
69                size: alloc.size,
70            };
71        }
72
73        if alloc.size == 0 {
74            return GlobalAlloc::DANGLING;
75        }
76
77        unsafe {
78            // SAFETY: The layout assumption has already been checked.
79            let layout =
80                Layout::from_size_align_unchecked(alloc.size * size_of::<T>(), align_of::<T>());
81            let data = alloc::alloc(layout);
82
83            if data.is_null() {
84                alloc::handle_alloc_error(layout);
85            }
86
87            GlobalAlloc {
88                data: NonNull::new_unchecked(data).cast(),
89                size: alloc.size,
90            }
91        }
92    }
93
94    #[inline]
95    fn slice_from_raw_parts<T>(ptr: NonNull<T>, len: usize) -> Self::Alloc<T> {
96        GlobalAlloc {
97            data: ptr,
98            size: len,
99        }
100    }
101}
102
103unsafe impl Allocator for Global {
104    #[inline]
105    fn __do_not_implement() {}
106
107    const IS_GLOBAL: bool = true;
108
109    type Alloc<T> = GlobalAlloc<T>;
110
111    #[inline]
112    fn alloc<T>(self, value: T) -> Result<Self::Alloc<T>, AllocError> {
113        let mut raw = GlobalAlloc::<T>::alloc()?;
114
115        if size_of::<T>() != 0 {
116            // SAFETY: The above ensures the data has been allocated.
117            unsafe {
118                raw.as_mut_ptr().write(value);
119            }
120        }
121
122        Ok(raw)
123    }
124
125    #[inline]
126    fn alloc_empty<T>(self) -> Self::Alloc<T> {
127        GlobalAlloc::DANGLING
128    }
129}
130
131/// A vector-backed allocation.
132pub struct GlobalAlloc<T> {
133    /// Pointer to the allocated region.
134    data: NonNull<T>,
135    /// The size in number of `T` elements in the region.
136    size: usize,
137}
138
139impl<T> GlobalAlloc<T> {
140    /// Reallocate the region to the given capacity.
141    ///
142    /// # Safety
143    ///
144    /// The caller must ensure that the new capacity is valid per [`Layout`].
145    #[must_use = "allocating is fallible and must be checked"]
146    fn alloc() -> Result<Self, AllocError> {
147        if size_of::<T>() == 0 {
148            return Ok(Self {
149                data: NonNull::dangling(),
150                size: 1,
151            });
152        }
153
154        unsafe {
155            let data = alloc::alloc(Layout::new::<T>());
156
157            if data.is_null() {
158                return Err(AllocError);
159            }
160
161            Ok(Self {
162                data: NonNull::new_unchecked(data).cast(),
163                size: 1,
164            })
165        }
166    }
167}
168
169unsafe impl<T> Send for GlobalAlloc<T> where T: Send {}
170unsafe impl<T> Sync for GlobalAlloc<T> where T: Sync {}
171
172impl<T> Alloc<T> for GlobalAlloc<T> {
173    #[inline]
174    fn as_ptr(&self) -> *const T {
175        self.data.as_ptr().cast_const().cast()
176    }
177
178    #[inline]
179    fn as_mut_ptr(&mut self) -> *mut T {
180        self.data.as_ptr().cast()
181    }
182
183    #[inline]
184    fn capacity(&self) -> usize {
185        if size_of::<T>() == 0 {
186            usize::MAX
187        } else {
188            self.size
189        }
190    }
191
192    #[inline]
193    fn resize(&mut self, len: usize, additional: usize) -> Result<(), AllocError> {
194        if size_of::<T>() == 0 {
195            return Ok(());
196        }
197
198        if !self.reserve(len, additional) {
199            return Err(AllocError);
200        }
201
202        Ok(())
203    }
204
205    #[inline]
206    fn try_merge<B>(&mut self, _: usize, other: B, _: usize) -> Result<(), B>
207    where
208        B: Alloc<T>,
209    {
210        if size_of::<T>() == 0 {
211            return Ok(());
212        }
213
214        Err(other)
215    }
216}
217
218impl<T> GlobalAlloc<T> {
219    const MIN_NON_ZERO_CAP: usize = if size_of::<T>() == 1 {
220        8
221    } else if size_of::<T>() <= 1024 {
222        4
223    } else {
224        1
225    };
226
227    const DANGLING: Self = Self {
228        data: NonNull::dangling(),
229        size: 0,
230    };
231
232    /// Reallocate the region to the given capacity.
233    ///
234    /// # Safety
235    ///
236    /// The caller must ensure that the new capacity is valid per [`Layout`].
237    #[must_use = "allocating is fallible and must be checked"]
238    fn realloc(&mut self, new_layout: Layout) -> bool {
239        unsafe {
240            let data = {
241                if self.size > 0 {
242                    let old_layout = Layout::from_size_align_unchecked(
243                        self.size.wrapping_mul(size_of::<T>()),
244                        align_of::<T>(),
245                    );
246
247                    alloc::realloc(self.data.as_ptr().cast(), old_layout, new_layout.size())
248                } else {
249                    alloc::alloc(new_layout)
250                }
251            };
252
253            if data.is_null() {
254                return false;
255            }
256
257            self.data = NonNull::new_unchecked(data).cast();
258        }
259
260        true
261    }
262
263    #[must_use = "allocating is fallible and must be checked"]
264    fn reserve(&mut self, len: usize, additional: usize) -> bool {
265        debug_assert_ne!(size_of::<T>(), 0, "ZSTs should not get here");
266
267        let Some(required_cap) = len.checked_add(additional) else {
268            return false;
269        };
270
271        if self.size >= required_cap {
272            return true;
273        }
274
275        let cap = cmp::max(self.size * 2, required_cap);
276        let cap = cmp::max(Self::MIN_NON_ZERO_CAP, cap);
277
278        let Ok(new_layout) = Layout::array::<T>(cap) else {
279            return false;
280        };
281
282        if !self.realloc(new_layout) {
283            return false;
284        }
285
286        self.size = cap;
287        true
288    }
289
290    fn free(&mut self) {
291        if size_of::<T>() == 0 || self.size == 0 {
292            return;
293        }
294
295        // SAFETY: Layout assumptions are correctly encoded in the type as
296        // it was being allocated or grown.
297        unsafe {
298            let layout =
299                Layout::from_size_align_unchecked(self.size * size_of::<T>(), align_of::<T>());
300            alloc::dealloc(self.data.as_ptr().cast(), layout);
301            self.data = NonNull::dangling();
302            self.size = 0;
303        }
304    }
305}
306
307impl<T> Drop for GlobalAlloc<T> {
308    #[inline]
309    fn drop(&mut self) {
310        self.free();
311    }
312}