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}