untyped_box/impl.rs
1use core::{alloc::Layout, any::type_name, mem::MaybeUninit, ptr::NonNull};
2
3use crate::alloc_shim::{AllocError, Allocator, Global};
4
5/// An allocation is management representation of some allocated memory.
6///
7/// For the most part, this behaves like a lower-level (untyped) cousin of a `Box`.
8/// The memory backing this allocation is deallocated when the allocation is dropped.
9/// In contrast, no validity or initialization state of the memory is implied by
10/// existance of an [Allocation].
11pub struct Allocation<A: Allocator = Global> {
12 // TODO: should be a Unique pointer!
13 ptr: NonNull<u8>,
14 layout: Layout,
15 alloc: A,
16}
17
18// TODO: There is a bit of a mismatch here. In essence, we are losing information.
19// For example, requesting an allocation for some `Layout::new::<T>()` that results in the allocator
20// giving us more memory than we asked for might make later checks when trying to convert to a `Box`
21// fail on size mismatch.
22// We might have to blow up the allocation struct to reconstruct [Memory fitting] information.
23// [Memory fitting]: https://doc.rust-lang.org/nightly/alloc/alloc/trait.Allocator.html#memory-fitting
24fn match_allocated_size(ptr: NonNull<[u8]>, layout: Layout) -> (NonNull<u8>, Layout) {
25 let actual_layout = unsafe { Layout::from_size_align_unchecked(ptr.len(), layout.align()) };
26 debug_assert!(actual_layout.size() >= layout.size());
27 (ptr.cast(), actual_layout)
28}
29fn allocate(alloc: &impl Allocator, layout: Layout) -> Result<(NonNull<u8>, Layout), AllocError> {
30 let ptr = alloc.allocate(layout)?;
31 Ok(match_allocated_size(ptr, layout))
32}
33fn allocate_zeroed(
34 alloc: &impl Allocator,
35 layout: Layout,
36) -> Result<(NonNull<u8>, Layout), AllocError> {
37 let ptr = alloc.allocate_zeroed(layout)?;
38 Ok(match_allocated_size(ptr, layout))
39}
40unsafe fn grow(
41 alloc: &impl Allocator,
42 ptr: NonNull<u8>,
43 old_layout: Layout,
44 new_layout: Layout,
45) -> Result<(NonNull<u8>, Layout), AllocError> {
46 let ptr = alloc.grow(ptr, old_layout, new_layout)?;
47 Ok(match_allocated_size(ptr, new_layout))
48}
49unsafe fn grow_zeroed(
50 alloc: &impl Allocator,
51 ptr: NonNull<u8>,
52 old_layout: Layout,
53 new_layout: Layout,
54) -> Result<(NonNull<u8>, Layout), AllocError> {
55 let ptr = alloc.grow_zeroed(ptr, old_layout, new_layout)?;
56 Ok(match_allocated_size(ptr, new_layout))
57}
58unsafe fn shrink(
59 alloc: &impl Allocator,
60 ptr: NonNull<u8>,
61 old_layout: Layout,
62 new_layout: Layout,
63) -> Result<(NonNull<u8>, Layout), AllocError> {
64 let ptr = alloc.shrink(ptr, old_layout, new_layout)?;
65 Ok(match_allocated_size(ptr, new_layout))
66}
67
68/// Methods for the global allocator
69impl Allocation {
70 /// Allocate new memory for the given layout.
71 ///
72 /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
73 /// memory region does not alias any other existing allocation.
74 ///
75 /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
76 /// lax when a small alignment is requested.
77 ///
78 /// Memory is not initialized or zeroed, try [`Self::zeroed`] instead.
79 ///
80 /// # Panics
81 ///
82 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
83 /// See [`Self::try_new_in`] for a version that returns an error instead.
84 // Forwards to alloc, handles layout.size() == 0 with a dangling ptr
85 pub fn new(layout: Layout) -> Self {
86 Self::new_in(layout, Global)
87 }
88 /// Allocate new memory for the given layout.
89 ///
90 /// Same as [`Self::new`] but returns an error when memory could not be allocated.
91 pub fn try_new(layout: Layout) -> Result<Self, AllocError> {
92 Self::try_new_in(layout, Global)
93 }
94 /// Allocate new zeroed-out memory for the given layout.
95 ///
96 /// # Panics
97 ///
98 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
99 /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
100 pub fn zeroed(layout: Layout) -> Self {
101 Self::zeroed_in(layout, Global)
102 }
103 /// Allocate new zeroed-out memory for the given layout.
104 ///
105 /// Same as [`Self::zeroed`] but returns an error when memory could not be allocated.
106 pub fn try_zeroed(layout: Layout) -> Result<Self, AllocError> {
107 Self::try_zeroed_in(layout, Global)
108 }
109 /// Split the allocation into its raw parts.
110 ///
111 /// Deallocating the allocation is the responsibility of the caller. The returned
112 /// pointer can be passed to [`alloc::alloc::dealloc`] if the returned layout indicates `size() > 0`.
113 /// If the allocated memory is 0 sized, the pointer does not need to be deallocated.
114 ///
115 /// See also [`Self::into_parts_with_alloc`] for an allocator-aware version.
116 pub fn into_parts(self) -> (NonNull<u8>, Layout) {
117 let (ptr, layout, _) = Self::into_parts_with_alloc(self);
118 (ptr, layout)
119 }
120 /// Constructs an [`Allocation`] from a pointer and layout information.
121 ///
122 /// # Safety
123 ///
124 /// The pointer must point to [*currently-allocated*] memory from the global allocator, and `layout`
125 /// was used to allocate that memory.
126 ///
127 /// [*currently-allocated*]: Allocator#currently-allocated-memory
128 pub unsafe fn from_parts(ptr: NonNull<u8>, layout: Layout) -> Self {
129 Self::from_parts_in(ptr, layout, Global)
130 }
131}
132/// Common methods
133impl<A: Allocator> Allocation<A> {
134 /// Gets a pointer to the allocation.
135 ///
136 /// The pointer is always aligned to the alignment of the layout indicated by [Self::layout] or the requested layout
137 /// indicated on allocation, whichever is more strict.
138 ///
139 /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
140 /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
141 ///
142 /// In particular, the pointer does not in itself materialize a reference to the underlying storage for the purpose of the aliasing model.
143 pub fn as_ptr<T>(&self) -> NonNull<T> {
144 self.ptr.cast()
145 }
146 /// View the underlying storage as a possibly uninitialized `T`.
147 ///
148 /// # Panics
149 ///
150 /// If the allocation is too small, or not aligned enough to contain a `T`.
151 pub fn as_uninit_ref<T>(&self) -> &MaybeUninit<T> {
152 assert!(
153 self.layout.size() >= size_of::<T>(),
154 "allocation too small to represent a {}",
155 type_name::<T>()
156 );
157 assert!(
158 self.layout.align() >= align_of::<T>(),
159 "allocation not aligned for a {}",
160 type_name::<T>()
161 );
162 unsafe { &*self.ptr.as_ptr().cast() }
163 }
164 /// View the underlying storage as a possibly uninitialized `T`.
165 ///
166 /// # Panics
167 ///
168 /// If the allocation is too small, or not aligned enough to contain a `T`.
169 pub fn as_uninit_mut<T>(&mut self) -> &mut MaybeUninit<T> {
170 assert!(
171 self.layout.size() >= size_of::<T>(),
172 "allocation too small to represent a {}",
173 type_name::<T>()
174 );
175 assert!(
176 self.layout.align() >= align_of::<T>(),
177 "allocation not aligned for a {}",
178 type_name::<T>()
179 );
180 unsafe { &mut *self.ptr.as_ptr().cast() }
181 }
182 /// View the allocation as a pointer to a slice of possibly uninitialized bytes.
183 ///
184 /// The caller is responsible for checking lifetimes when convert to a reference.
185 ///
186 /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
187 /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
188 ///
189 /// Like [`as_ptr`](Self::as_ptr), this does not materialize a reference to the underlying storage for the purpose of the aliasing model.
190 /// Hence, these two methods can be intermixed.
191 pub fn as_slice(&self) -> NonNull<[MaybeUninit<u8>]> {
192 let ptr = core::ptr::slice_from_raw_parts_mut(
193 self.ptr.as_ptr().cast::<MaybeUninit<u8>>(),
194 self.layout.size(),
195 );
196 unsafe { NonNull::new_unchecked(ptr) }
197 }
198 /// Reallocates memory to a new layout.
199 ///
200 /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
201 /// Newly allocated bytes are uninitialized.
202 ///
203 /// Any pointers to the managed memory are invalidated on return.
204 ///
205 /// # Panics
206 ///
207 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
208 /// See [`Self::try_realloc`] for a version that returns an error instead.
209 // Calls either grow or shrink, compares against stored layout
210 pub fn realloc(&mut self, new_layout: Layout) {
211 let () = self
212 .try_realloc(new_layout)
213 .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
214 }
215 /// Reallocates memory to a new layout.
216 ///
217 /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
218 /// Newly allocated bytes are zeroed.
219 ///
220 /// Any pointers to the managed memory are invalidated on return.
221 ///
222 /// # Panics
223 ///
224 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
225 /// See [`Self::try_realloc_zeroed`] for a version that returns an error instead.
226 pub fn realloc_zeroed(&mut self, new_layout: Layout) {
227 let () = self
228 .try_realloc_zeroed(new_layout)
229 .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
230 }
231 /// Get the layout of the underlying allocation.
232 ///
233 /// This layout is guaranteed to be at least as large as previously requested from [`new`](Self::new) or [`realloc`](Self::realloc) and
234 /// at least as strictly aligned, but might indicate more available memory.
235 pub fn layout(&self) -> Layout {
236 self.layout
237 }
238}
239/// Methods using the allocator-api or shim
240impl<A: Allocator> Allocation<A> {
241 /// Allocate new memory for the given layout in a given allocator.
242 ///
243 /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
244 /// memory region does not alias any other existing allocation.
245 ///
246 /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
247 /// lax when a small alignment is requested.
248 ///
249 /// Memory is not initialized or zeroed, try [`Self::zeroed_in`] instead.
250 ///
251 /// # Panics
252 ///
253 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
254 /// See [`Self::try_new_in`] for a version that returns an error instead.
255 pub fn new_in(layout: Layout, alloc: A) -> Self {
256 Self::try_new_in(layout, alloc)
257 .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
258 }
259 /// Allocate new memory for the given layout in a given allocator.
260 ///
261 /// Returns an error when no memory could be allocated.
262 pub fn try_new_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
263 let (ptr, layout) = allocate(&alloc, layout)?;
264 Ok(Self { ptr, layout, alloc })
265 }
266 /// Allocate new zeroed-out memory for the given layout in a given allocator.
267 ///
268 /// # Panics
269 ///
270 /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
271 /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
272 pub fn zeroed_in(layout: Layout, alloc: A) -> Self {
273 Self::try_zeroed_in(layout, alloc)
274 .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
275 }
276 /// Allocate new zeroed-out memory for the given layout in a given allocator.
277 ///
278 /// Returns an error when no memory could be allocated.
279 pub fn try_zeroed_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
280 let (ptr, layout) = allocate_zeroed(&alloc, layout)?;
281 Ok(Self { ptr, layout, alloc })
282 }
283 /// Split the allocation into its raw parts including the allocator.
284 ///
285 /// Deallocating the allocation is the responsibility of the caller. The returned
286 /// pointer can be passed to `alloc.deallocate()`.
287 pub fn into_parts_with_alloc(self) -> (NonNull<u8>, Layout, A) {
288 let me = core::mem::ManuallyDrop::new(self);
289 let alloc = unsafe { core::ptr::read(&me.alloc) };
290 (me.ptr, me.layout, alloc)
291 }
292 /// Constructs an [`Allocation`] from a pointer and layout information in the given allocator.
293 ///
294 /// # Safety
295 ///
296 /// The pointer must point to [*currently-allocated*] memory from the given allocator, and `layout`
297 /// [*fits*] that memory.
298 ///
299 /// [*currently-allocated*]: Allocator#currently-allocated-memory
300 /// [*fits*]: Allocator#memory-fitting
301 pub unsafe fn from_parts_in(ptr: NonNull<u8>, layout: Layout, alloc: A) -> Self {
302 Self { ptr, layout, alloc }
303 }
304 /// Reallocates memory to a new layout.
305 ///
306 /// Returns an error when the memory could not be reallocated. In this case, any previously derived
307 /// pointers remain valid and no memory is deallocated.
308 ///
309 /// # See also
310 ///
311 /// [`Self::realloc`] for more disuccion about the memory contents after reallocation.
312 pub fn try_realloc(&mut self, new_layout: Layout) -> Result<(), AllocError> {
313 if new_layout == self.layout {
314 return Ok(());
315 }
316 // Prefer grow to shrink when all we do is change alignment
317 if new_layout.size() >= self.layout.size() {
318 (self.ptr, self.layout) =
319 unsafe { grow(&self.alloc, self.ptr, self.layout, new_layout)? };
320 Ok(())
321 } else {
322 (self.ptr, self.layout) =
323 unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
324 Ok(())
325 }
326 }
327 /// Reallocates memory to a new layout.
328 ///
329 /// Returns an error when the memory could not be reallocated. In this case, any previously derived
330 /// pointers remain valid and no memory is deallocated.
331 ///
332 /// # See also
333 ///
334 /// [`Self::realloc_zeroed`] for more disuccion about the memory contents after reallocation.
335 pub fn try_realloc_zeroed(&mut self, new_layout: Layout) -> Result<(), AllocError> {
336 if new_layout == self.layout {
337 return Ok(());
338 }
339 // Prefer grow to shrink when all we do is change alignment
340 if new_layout.size() >= self.layout.size() {
341 (self.ptr, self.layout) =
342 unsafe { grow_zeroed(&self.alloc, self.ptr, self.layout, new_layout)? };
343 Ok(())
344 } else {
345 (self.ptr, self.layout) =
346 unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
347 Ok(())
348 }
349 }
350}
351
352impl<A: Allocator> Drop for Allocation<A> {
353 fn drop(&mut self) {
354 unsafe {
355 self.alloc.deallocate(self.ptr, self.layout);
356 }
357 }
358}
359
360unsafe impl<A: Allocator + Sync> Sync for Allocation<A> {}
361unsafe impl<A: Allocator + Send> Send for Allocation<A> {}