divvy_core/
lib.rs

1#![deny(unsafe_op_in_unsafe_fn)]
2#![cfg_attr(feature = "nightly", feature(allocator_api))]
3
4use core::{
5    alloc::Layout,
6    fmt::Display,
7    num::NonZeroUsize,
8    ptr::{self, NonNull},
9};
10
11/// An error occurred during allocation, and the requested memory blocks could not
12/// be returned.
13#[derive(Debug)]
14pub struct AllocError;
15
16impl Display for AllocError {
17    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
18        f.write_str("allocation failed")
19    }
20}
21
22#[cfg(feature = "std")]
23impl std::error::Error for AllocError {}
24
25/// A `Layout` such that the size is never zero.
26///
27/// The distinction between zero sized layouts and any other layout is made because it
28/// makes little sense for an allocator to concern itself with zero sized allocations.
29/// That should be managed by the user of an allocator. This also saves a branch as
30/// the allocator doesn't need to special case a zero sized allocation.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct NonZeroLayout {
33    layout: Layout,
34}
35
36impl NonZeroLayout {
37    pub fn new(layout: Layout) -> Option<Self> {
38        if layout.size() == 0 {
39            None
40        } else {
41            Some(Self { layout })
42        }
43    }
44
45    pub fn nonzero_size(&self) -> NonZeroUsize {
46        let size = self.layout.size();
47        unsafe { NonZeroUsize::new_unchecked(size) }
48    }
49
50    pub fn size(&self) -> usize {
51        self.nonzero_size().get()
52    }
53
54    pub fn align(&self) -> usize {
55        self.get().align()
56    }
57
58    pub fn get(&self) -> Layout {
59        self.layout
60    }
61}
62
63/// A `Deallocator` can be used to deallocate or shrink an allocation described by a
64/// `NonZeroLayout`.
65pub trait Deallocator {
66    /// Deallocates the memory referenced by `ptr`.
67    ///
68    /// # Safety
69    /// - The pointer must be valid and the same as given by a previous call to
70    /// `allocate`.
71    /// - The layout must be identical to that used when allocating the pointer.
72    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout);
73
74    /// Attempt to shrink a block of memory in-place.
75    ///
76    /// # Safety
77    /// - The pointer must be valid and the same as returned by a previous call to
78    /// `allocate`.
79    /// - The old layout must be identical to that used when allocating the pointer
80    /// - The new layout must have a size and alignment such that new_size <= old_size
81    /// and new_align <= old_align.
82    unsafe fn try_shrink(
83        &self,
84        ptr: NonNull<u8>,
85        old_layout: NonZeroLayout,
86        new_layout: NonZeroLayout,
87    ) -> Result<(), AllocError> {
88        let _ = ptr;
89        let _ = old_layout;
90        let _ = new_layout;
91        Err(AllocError)
92    }
93
94    /// Creates a “by reference” adapter for this instance of `Dellocator`.
95    /// The returned adapter also implements `Deallocator` and will simply borrow this.
96    fn by_ref(&self) -> &Self
97    where
98        Self: Sized,
99    {
100        self
101    }
102}
103
104/// # Safety
105///
106/// Allocator implementations require that allocated pointers are not invalidated
107/// by moving the allocator. In other words, it is not valid to return a pointer
108/// that references the address of self, such as returning the address of a member
109/// array of bytes.
110///
111/// Allocators must ensure that allocated blocks do not overlap and remain valid for
112/// the lifetime of the allocator or until a call to deallocate.
113///
114/// Allocators must also guarantee that returned memory blocks fit the requested layout.
115pub unsafe trait Allocator: Deallocator {
116    /// Allocate a new block of memory that fits the provided layout.
117    fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<u8>, AllocError>;
118
119    /// Allocate a new block of zeroed memory that fits the provided layout.
120    #[inline]
121    fn allocate_zeroed(&self, layout: NonZeroLayout) -> Result<NonNull<u8>, AllocError> {
122        let ptr = self.allocate(layout)?;
123        unsafe { ptr.as_ptr().write_bytes(0, layout.size()) };
124        Ok(ptr)
125    }
126
127    /// Grow a previously allocated block of memory. If this call succeeds, the old
128    /// pointer must not be used. If this call fails, the old pointer remains valid.
129    ///
130    /// For an alternative that guarantees that the pointer remains the same, see
131    /// [try_grow](Self::try_grow).
132    ///
133    /// # Safety
134    /// - The pointer must be valid and the same as returned by a previous call to
135    /// `allocate`.
136    /// - The old layout must be identical to that used when allocating the pointer
137    /// - The new layout must have a size and alignment such that new_size >= old_size.
138    unsafe fn grow(
139        &self,
140        ptr: NonNull<u8>,
141        old_layout: NonZeroLayout,
142        new_layout: NonZeroLayout,
143    ) -> Result<NonNull<u8>, AllocError> {
144        unsafe {
145            if self.try_grow(ptr, old_layout, new_layout).is_ok() {
146                return Ok(ptr);
147            }
148
149            let new = self.allocate(new_layout)?;
150            ptr::copy_nonoverlapping(ptr.as_ptr(), new.as_ptr(), old_layout.size());
151            self.deallocate(ptr, old_layout);
152
153            Ok(new)
154        }
155    }
156
157    /// Grow a previously allocated block of memory, zeroing the newly allocated region.
158    /// If this call succeeds, the old pointer must not be used. If this call fails, the
159    /// old pointer remains valid.
160    ///
161    /// For an alternative that guarantees that the pointer remains the same, see
162    /// [try_grow_zeroed](Self::try_grow_zeroed).
163    ///
164    /// # Safety
165    /// - The pointer must be valid and the same as returned by a previous call to
166    /// `allocate`.
167    /// - The old layout must be identical to that used when allocating the pointer
168    /// - The new layout must have a size and alignment such that new_size >= old_size.
169    unsafe fn grow_zeroed(
170        &self,
171        ptr: NonNull<u8>,
172        old_layout: NonZeroLayout,
173        new_layout: NonZeroLayout,
174    ) -> Result<NonNull<u8>, AllocError> {
175        unsafe {
176            if self.try_grow_zeroed(ptr, old_layout, new_layout).is_ok() {
177                return Ok(ptr);
178            }
179
180            let new = self.allocate(new_layout)?;
181            ptr::copy_nonoverlapping(ptr.as_ptr(), new.as_ptr(), old_layout.size());
182            self.deallocate(ptr, old_layout);
183
184            ptr.as_ptr()
185                .add(old_layout.size())
186                .write_bytes(0, new_layout.size() - old_layout.size());
187
188            Ok(new)
189        }
190    }
191
192    /// Shrink a previously allocated block of memory. If this call succeeds, the old
193    /// pointer must not be used. If this call fails, the old pointer remains valid.
194    ///
195    /// For an alternative that guarantees that the pointer remains the same, see
196    /// [try_shrink](Self::try_shrink).
197    ///
198    /// # Safety
199    /// - The pointer must be valid and the same as returned by a previous call to
200    /// `allocate`.
201    /// - The old layout must be identical to that used when allocating the pointer
202    /// - The new layout must have a size and alignment such that new_size >= old_size.
203    unsafe fn shrink(
204        &self,
205        ptr: NonNull<u8>,
206        old_layout: NonZeroLayout,
207        new_layout: NonZeroLayout,
208    ) -> Result<NonNull<u8>, AllocError> {
209        unsafe {
210            if self.try_shrink(ptr, old_layout, new_layout).is_ok() {
211                return Ok(ptr);
212            }
213
214            let new = self.allocate(new_layout)?;
215            ptr::copy_nonoverlapping(ptr.as_ptr(), new.as_ptr(), new_layout.size());
216            self.deallocate(ptr, old_layout);
217
218            Ok(new)
219        }
220    }
221
222    /// Attempt to grow a block of memory in-place.
223    ///
224    /// # Safety
225    /// - The pointer must be valid and the same as returned by a previous call to
226    /// `allocate`.
227    /// - The old layout must be identical to that used when allocating the pointer
228    /// - The new layout must have a size and alignment such that new_size >= old_size.
229    #[inline]
230    unsafe fn try_grow(
231        &self,
232        ptr: NonNull<u8>,
233        old_layout: NonZeroLayout,
234        new_layout: NonZeroLayout,
235    ) -> Result<(), AllocError> {
236        let _ = ptr;
237        let _ = old_layout;
238        let _ = new_layout;
239        Err(AllocError)
240    }
241
242    /// Attempt to grow a block of memory in-place, zeroing the newly allocated portion.
243    ///
244    /// # Safety
245    /// - The pointer must be valid and the same as returned by a previous call to
246    /// `allocate`.
247    /// - The old layout must be identical to that used when allocating the pointer
248    /// - The new layout must have a size and alignment such that new_size >= old_size.
249    #[inline]
250    unsafe fn try_grow_zeroed(
251        &self,
252        ptr: NonNull<u8>,
253        old_layout: NonZeroLayout,
254        new_layout: NonZeroLayout,
255    ) -> Result<(), AllocError> {
256        unsafe {
257            self.try_grow(ptr, old_layout, new_layout)?;
258
259            ptr.as_ptr()
260                .add(old_layout.size())
261                .write_bytes(0, new_layout.size() - old_layout.size());
262        }
263        Ok(())
264    }
265}
266
267impl<'a, A> Deallocator for &'a A
268where
269    A: Deallocator + ?Sized,
270{
271    #[inline]
272    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout) {
273        unsafe { (**self).deallocate(ptr, layout) }
274    }
275
276    #[inline]
277    unsafe fn try_shrink(
278        &self,
279        ptr: NonNull<u8>,
280        old_layout: NonZeroLayout,
281        new_layout: NonZeroLayout,
282    ) -> Result<(), AllocError> {
283        unsafe { (**self).try_shrink(ptr, old_layout, new_layout) }
284    }
285}
286
287unsafe impl<'a, A> Allocator for &'a A
288where
289    A: Allocator + ?Sized,
290{
291    #[inline]
292    fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<u8>, AllocError> {
293        (**self).allocate(layout)
294    }
295
296    #[inline]
297    fn allocate_zeroed(&self, layout: NonZeroLayout) -> Result<NonNull<u8>, AllocError> {
298        (**self).allocate_zeroed(layout)
299    }
300
301    #[inline]
302    unsafe fn grow(
303        &self,
304        ptr: NonNull<u8>,
305        old_layout: NonZeroLayout,
306        new_layout: NonZeroLayout,
307    ) -> Result<NonNull<u8>, AllocError> {
308        unsafe { (**self).grow(ptr, old_layout, new_layout) }
309    }
310
311    #[inline]
312    unsafe fn grow_zeroed(
313        &self,
314        ptr: NonNull<u8>,
315        old_layout: NonZeroLayout,
316        new_layout: NonZeroLayout,
317    ) -> Result<NonNull<u8>, AllocError> {
318        unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) }
319    }
320
321    #[inline]
322    unsafe fn shrink(
323        &self,
324        ptr: NonNull<u8>,
325        old_layout: NonZeroLayout,
326        new_layout: NonZeroLayout,
327    ) -> Result<NonNull<u8>, AllocError> {
328        unsafe { (**self).shrink(ptr, old_layout, new_layout) }
329    }
330
331    #[inline]
332    unsafe fn try_grow(
333        &self,
334        ptr: NonNull<u8>,
335        old_layout: NonZeroLayout,
336        new_layout: NonZeroLayout,
337    ) -> Result<(), AllocError> {
338        unsafe { (**self).try_grow(ptr, old_layout, new_layout) }
339    }
340
341    #[inline]
342    unsafe fn try_grow_zeroed(
343        &self,
344        ptr: NonNull<u8>,
345        old_layout: NonZeroLayout,
346        new_layout: NonZeroLayout,
347    ) -> Result<(), AllocError> {
348        unsafe { (**self).try_grow_zeroed(ptr, old_layout, new_layout) }
349    }
350}