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