Skip to main content

static_alloc/
allocator_api2.rs

1use crate::{
2    bump::{Bump, BumpSlice, BumpView, Level},
3    unsync,
4};
5
6use allocator_api2::alloc::{AllocError, Allocator, Layout};
7use core::{cell, ptr::NonNull};
8
9unsafe impl<T> Allocator for Bump<T> {
10    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
11        Allocator::allocate(&self.as_view(), layout)
12    }
13
14    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
15
16    unsafe fn shrink(
17        &self,
18        ptr: NonNull<u8>,
19        old_layout: Layout,
20        new_layout: Layout,
21    ) -> Result<NonNull<[u8]>, AllocError> {
22        // Safety: passing along requirements. These two allocators serve the same allocations, a
23        // property we permit for these two of our own types.
24        unsafe { Allocator::shrink(&self.as_view(), ptr, old_layout, new_layout) }
25    }
26}
27
28unsafe impl Allocator for BumpSlice {
29    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
30        Allocator::allocate(&self.as_view(), layout)
31    }
32
33    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
34
35    unsafe fn shrink(
36        &self,
37        ptr: NonNull<u8>,
38        old_layout: Layout,
39        new_layout: Layout,
40    ) -> Result<NonNull<[u8]>, AllocError> {
41        // Safety: passing along requirements. These two allocators serve the same allocations, a
42        // property we permit for these two of our own types.
43        unsafe { Allocator::shrink(&self.as_view(), ptr, old_layout, new_layout) }
44    }
45}
46
47unsafe impl Allocator for BumpView<'_> {
48    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
49        let len = layout.size();
50        match self.get_layout(layout) {
51            None => Err(AllocError),
52            Some(allocation) => Ok(NonNull::slice_from_raw_parts(allocation.ptr, len)),
53        }
54    }
55
56    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
57
58    unsafe fn shrink(
59        &self,
60        ptr: NonNull<u8>,
61        old_layout: Layout,
62        new_layout: Layout,
63    ) -> Result<NonNull<[u8]>, AllocError> {
64        // Safety: Caller guarantees `ptr` was allocated from `self` (or equivalent, for transitive
65        // use of this) which requires it to be valid and described by `old_layout`.
66        unsafe { shrink_in_place(ptr, old_layout, new_layout) }
67    }
68}
69
70unsafe impl<T> Allocator for unsync::Bump<T> {
71    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
72        <unsync::BumpSlice as Allocator>::allocate(self, layout)
73    }
74
75    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
76
77    unsafe fn shrink(
78        &self,
79        ptr: NonNull<u8>,
80        old_layout: Layout,
81        new_layout: Layout,
82    ) -> Result<NonNull<[u8]>, AllocError> {
83        // Safety: passing along requirements. These two allocators serve the same allocations, a
84        // property we permit for these two of our own types.
85        unsafe { <unsync::BumpSlice as Allocator>::shrink(self, ptr, old_layout, new_layout) }
86    }
87}
88
89unsafe impl Allocator for unsync::BumpSlice {
90    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
91        let len = layout.size();
92        match self.alloc(layout) {
93            None => Err(AllocError),
94            Some(allocation) => Ok(NonNull::slice_from_raw_parts(allocation, len)),
95        }
96    }
97
98    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
99
100    unsafe fn shrink(
101        &self,
102        ptr: NonNull<u8>,
103        old_layout: Layout,
104        new_layout: Layout,
105    ) -> Result<NonNull<[u8]>, AllocError> {
106        // Safety: Caller guarantees `ptr` was allocated from `self` (or equivalent, for transitive
107        // use of this) which requires it to be valid and described by `old_layout`.
108        unsafe { shrink_in_place(ptr, old_layout, new_layout) }
109    }
110}
111
112/// Safety: caller must only call this on `ptr` point to a valid allocation with the fitting layout
113/// `old_layout`. Returns a derived pointer into the same allocation on success.
114unsafe fn shrink_in_place(
115    ptr: NonNull<u8>,
116    old_layout: Layout,
117    new_layout: Layout,
118) -> Result<NonNull<[u8]>, AllocError> {
119    debug_assert!(new_layout.size() <= old_layout.size());
120    let len = new_layout.size();
121
122    let offset = ptr.align_offset(new_layout.align());
123
124    if offset > 0 {
125        if old_layout
126            .size()
127            .checked_sub(offset)
128            .is_none_or(|n| n < len)
129        {
130            // Won't fit in-place. Sorry.
131            return Err(AllocError);
132        }
133
134        // Safety: in-bounds as we just verified that old layout has at least as many bytes as
135        // offset, and the caller was required to pass a live allocation with corresponding
136        // layout; implying that it also has that many bytes.
137        let dst = unsafe { ptr.byte_add(offset) };
138        // Safety: just verified that layout has at least `len` bytes after the offset so `dst`
139        // also has provenance according to the caller's requirements.
140        unsafe { ptr.copy_to(dst, len) };
141        dst
142    } else {
143        ptr
144    };
145
146    Ok(NonNull::slice_from_raw_parts(ptr, len))
147}
148
149/// Wrapper around an allocator, that gathers stats about the underlying store while allocating.
150///
151/// The `allocator_api2` design intentionally avoids generics in its result types. Allocations in
152/// the bump arena however provide some auxiliary information such as the usage level of the bump
153/// before and after an allocation (at least a snapshot of it for a Sync allocator). This
154/// information can be especially useful in case of an allocation failure but gather it separately
155/// would be racy. Allocating with this wrapper avoids the race.
156///
157/// For these use cases you can replace the allocator handle with this wrapper. The wrapper is
158/// [equivalent] to the underlying allocator.
159///
160/// [equivalent]: https://doc.rust-lang.org/stable/std/alloc/trait.Allocator.html#equivalent-allocators
161///
162/// # Usage
163///
164/// ```
165/// use static_alloc::allocator_api2::WithAllocationInfo;
166/// use static_alloc::bump::Bump;
167/// ```
168///
169/// # Design
170///
171/// Interestingly, we could also provide per-call allocator configuration. For a `Bump` shared
172/// between threads for instance there is an inner loop around a fallible CAS. A parameter could
173/// constrain this loop to exit early on a case-by-case basis. It does not make too much sense to
174/// enforce it for all allocations in the arena. You would often prefer to use a non-racy
175/// thread-local arena in those cases (please open an issue if you have a use case). A handle
176/// wrapper on the other hand would provide surgical amounts of control.
177pub struct WithAllocationInfo<A> {
178    /// The placement of the last allocation, as byte offset in the arena.
179    allocation: cell::Cell<Level>,
180    /// The observed usage of the allocator.
181    observed_level: cell::Cell<Level>,
182    /// We could have a type-erased other type where this is a direct `BumpView` instead. Making
183    /// this work would require complicated generics though.
184    inner: A,
185}
186
187impl<'lt, A: ?Sized> WithAllocationInfo<&'lt A> {
188    /// Wrap an arena to gather stats.
189    ///
190    /// This does not constrain the type `A`. However, it can only be sensibly used with a
191    /// [`Bump`](`crate::bump::Bump`) or [`BumpSlice`](`crate::bump::BumpSlice`) or the unsync
192    /// [`BumpSlice`](`crate::unsync::BumpSlice`). It does *not* work with unsync's `Bump` due to
193    /// current design differences. (It is implemented with Deref into a `BumpSlice` instead of a
194    /// view used for both. This will post-monomorphization error which we do not want to hide).
195    pub fn new(arena: &'lt A) -> Self {
196        WithAllocationInfo {
197            allocation: cell::Cell::new(Level(0)),
198            observed_level: cell::Cell::new(Level(0)),
199            inner: arena,
200        }
201    }
202}
203
204impl<A> WithAllocationInfo<A> {
205    /// Query the consumed level observed by the last allocation call.
206    ///
207    /// The value is updated both on success and failure.
208    ///
209    /// # Usage
210    ///
211    /// You will get information about the consumed bytes with each attempted allocation.
212    ///
213    /// ```
214    /// use static_alloc::allocator_api2::WithAllocationInfo;
215    /// use static_alloc::bump::Bump;
216    /// use allocator_api2::vec::Vec;
217    ///
218    /// let arena =  Bump::<[usize; 4]>::uninit();
219    /// let hdl = WithAllocationInfo::new(&arena);
220    ///
221    /// let mut vec = Vec::<u32, _>::with_capacity_in(1, hdl);
222    /// assert_eq!(vec.allocator().last_observed_level().byte_offset(), 4);
223    ///
224    /// // This will not fit, and fail to allocate.
225    /// vec.try_reserve(32);
226    /// assert_eq!(vec.allocator().last_observed_level().byte_offset(), 4);
227    ///
228    /// // This will, reallocating the vector.
229    /// vec.try_reserve_exact(2);
230    /// assert_eq!(vec.allocator().last_observed_level().byte_offset(), 12);
231    /// ```
232    ///
233    /// The information is updated also when the arena is used concurrently for another purpose,
234    /// too.
235    ///
236    /// ```
237    /// use static_alloc::allocator_api2::WithAllocationInfo;
238    /// use static_alloc::bump::Bump;
239    /// use allocator_api2::vec::Vec;
240    ///
241    /// let arena =  Bump::<[usize; 4]>::uninit();
242    /// let hdl = WithAllocationInfo::new(&arena);
243    ///
244    /// let mut vec = Vec::<u32, _>::new_in(hdl);
245    /// let _ = arena.leak(0u32);
246    ///
247    /// // Afterwards exactly three `u32` allocator, the one leaked and the 2 reserved.
248    /// vec.try_reserve_exact(2);
249    /// assert_eq!(vec.allocator().last_observed_level().byte_offset(), 12);
250    /// ```
251    pub fn last_observed_level(&self) -> Level {
252        self.observed_level.get()
253    }
254
255    /// Get the [`Level`] at which the last successful allocation sits.
256    ///
257    /// This is provided for all allocations, sized and unsized.
258    ///
259    /// # Usage
260    ///
261    /// ```
262    /// use static_alloc::allocator_api2::WithAllocationInfo;
263    /// use static_alloc::bump::Bump;
264    /// use allocator_api2::vec::Vec;
265    ///
266    /// let arena =  Bump::<[usize; 4]>::uninit();
267    /// let hdl = WithAllocationInfo::new(&arena);
268    ///
269    /// let mut vec = Vec::<u32, _>::with_capacity_in(1, hdl);
270    /// assert_eq!(vec.allocator().last_allocation_start().byte_offset(), 0);
271    ///
272    /// // This will reallocate the vector, after the previous one.
273    /// vec.try_reserve_exact(2);
274    /// assert_eq!(vec.allocator().last_allocation_start().byte_offset(), 4);
275    /// ```
276    pub fn last_allocation_start(&self) -> Level {
277        self.allocation.get()
278    }
279}
280
281unsafe impl<T> Allocator for WithAllocationInfo<&'_ Bump<T>> {
282    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
283        let len = layout.size();
284        match self.inner.as_view().try_alloc(layout) {
285            Ok(allocation) => {
286                self.allocation.set(Level(allocation.level.0 - len));
287                self.observed_level.set(allocation.level);
288                Ok(NonNull::slice_from_raw_parts(allocation.ptr, len))
289            }
290            Err(level) => {
291                self.observed_level.set(level);
292                Err(AllocError)
293            }
294        }
295    }
296
297    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
298
299    unsafe fn shrink(
300        &self,
301        ptr: NonNull<u8>,
302        old_layout: Layout,
303        new_layout: Layout,
304    ) -> Result<NonNull<[u8]>, AllocError> {
305        // Safety: forwarding arguments, i.e  requirements, to an equivalent allocator.
306        unsafe { Allocator::shrink(&self.inner, ptr, old_layout, new_layout) }
307    }
308}
309
310unsafe impl Allocator for WithAllocationInfo<&'_ BumpSlice> {
311    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
312        let len = layout.size();
313        match self.inner.as_view().try_alloc(layout) {
314            Ok(allocation) => {
315                self.allocation.set(Level(allocation.level.0 - len));
316                self.observed_level.set(allocation.level);
317                Ok(NonNull::slice_from_raw_parts(allocation.ptr, len))
318            }
319            Err(level) => {
320                self.observed_level.set(level);
321                Err(AllocError)
322            }
323        }
324    }
325
326    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
327
328    unsafe fn shrink(
329        &self,
330        ptr: NonNull<u8>,
331        old_layout: Layout,
332        new_layout: Layout,
333    ) -> Result<NonNull<[u8]>, AllocError> {
334        // Safety: forwarding arguments, i.e  requirements, to an equivalent allocator.
335        unsafe { Allocator::shrink(&self.inner, ptr, old_layout, new_layout) }
336    }
337}
338
339// Not implemented for `unsync::Bump<T>` due to having to deref internally then.
340unsafe impl Allocator for WithAllocationInfo<&'_ unsync::BumpSlice> {
341    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
342        let len = layout.size();
343        match self.inner.try_alloc(layout) {
344            Ok(allocation) => {
345                self.allocation.set(Level(allocation.level.0 - len));
346                self.observed_level.set(allocation.level);
347                Ok(NonNull::slice_from_raw_parts(allocation.ptr, len))
348            }
349            Err(level) => {
350                self.observed_level.set(level);
351                Err(AllocError)
352            }
353        }
354    }
355
356    unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
357
358    unsafe fn shrink(
359        &self,
360        ptr: NonNull<u8>,
361        old_layout: Layout,
362        new_layout: Layout,
363    ) -> Result<NonNull<[u8]>, AllocError> {
364        // Safety: forwarding arguments, i.e  requirements, to an equivalent allocator.
365        unsafe { Allocator::shrink(&self.inner, ptr, old_layout, new_layout) }
366    }
367}