Skip to main content

bump_scope/traits/
bump_allocator_core.rs

1use core::{alloc::Layout, ops::Range, ptr::NonNull};
2
3use crate::{
4    BaseAllocator, Bump, BumpScope, Checkpoint, WithoutDealloc, WithoutShrink,
5    alloc::{AllocError, Allocator},
6    layout::CustomLayout,
7    raw_bump::RawChunk,
8    settings::BumpAllocatorSettings,
9    stats::AnyStats,
10    traits::{assert_dyn_compatible, assert_implements},
11};
12
13pub trait Sealed {}
14
15impl<B: Sealed + ?Sized> Sealed for &B {}
16impl<B: Sealed + ?Sized> Sealed for &mut B {}
17impl<B: Sealed> Sealed for WithoutDealloc<B> {}
18impl<B: Sealed> Sealed for WithoutShrink<B> {}
19
20impl<A, S> Sealed for Bump<A, S>
21where
22    A: BaseAllocator<S::GuaranteedAllocated>,
23    S: BumpAllocatorSettings,
24{
25}
26
27impl<A, S> Sealed for BumpScope<'_, A, S>
28where
29    A: BaseAllocator<S::GuaranteedAllocated>,
30    S: BumpAllocatorSettings,
31{
32}
33
34/// A bump allocator.
35///
36/// This trait provides additional methods and guarantees on top of an [`Allocator`].
37///
38/// A `BumpAllocatorCore` has laxer safety conditions when using `Allocator` methods:
39/// - You can call `grow*`, `shrink` and `deallocate` with pointers that came from a different `BumpAllocatorCore`.
40/// - Memory blocks can be split.
41/// - `shrink` never errors unless the new alignment is greater
42/// - `deallocate` may always be called when the pointer address is less than 16 and the size is 0
43///
44/// Those invariants are used here:
45/// - Handling of foreign pointers is necessary for implementing [`BumpVec::from_parts`], [`BumpBox::into_box`] and [`Bump(Scope)::dealloc`][Bump::dealloc].
46/// - Memory block splitting is necessary for [`split_off`] and [`split_at`].
47/// - The non-erroring behavior of `shrink` is necessary for [`BumpAllocatorTyped::shrink_slice`]
48/// - `deallocate` with a dangling pointer is used in the drop implementation of [`BumpString`]
49///
50/// # Safety
51///
52/// An implementor must support the conditions described above.
53///
54/// [`BumpVec::from_parts`]: crate::BumpVec::from_parts
55/// [`BumpBox::into_box`]: crate::BumpBox::into_box
56/// [`split_off`]: crate::BumpVec::split_off
57/// [`split_at`]: crate::BumpBox::split_at
58/// [`BumpVec`]: crate::BumpVec
59/// [`BumpAllocatorTyped::shrink_slice`]: crate::traits::BumpAllocatorTyped::shrink_slice
60/// [`BumpString`]: crate::BumpString
61pub unsafe trait BumpAllocatorCore: Allocator + Sealed {
62    /// Returns a type which provides statistics about the memory usage of the bump allocator.
63    #[must_use]
64    fn any_stats(&self) -> AnyStats<'_>;
65
66    /// Creates a checkpoint of the current bump position.
67    ///
68    /// The bump position can be reset to this checkpoint with [`reset_to`].
69    ///
70    /// [`reset_to`]: BumpAllocatorCore::reset_to
71    #[must_use]
72    fn checkpoint(&self) -> Checkpoint;
73
74    /// Resets the bump position to a previously created checkpoint.
75    ///
76    /// The memory that has been allocated since then will be reused by future allocations.
77    ///
78    /// # Safety
79    ///
80    /// - the checkpoint must have been created by this bump allocator
81    /// - the bump allocator must not have been [`reset`] since creation of this checkpoint
82    /// - there must be no references to allocations made since creation of this checkpoint
83    /// - the checkpoint must not have been created by a `!GUARANTEED_ALLOCATED` when self is `GUARANTEED_ALLOCATED`
84    /// - the bump allocator must be [unclaimed] at the time the checkpoint is created and when this function is called
85    ///
86    /// [`reset`]: crate::Bump::reset
87    /// [unclaimed]: crate::traits::BumpAllocatorScope::claim
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # use bump_scope::Bump;
93    /// let bump: Bump = Bump::new();
94    /// let checkpoint = bump.checkpoint();
95    ///
96    /// {
97    ///     let hello = bump.alloc_str("hello");
98    ///     assert_eq!(bump.stats().allocated(), 5);
99    ///     # _ = hello;
100    /// }
101    ///
102    /// unsafe { bump.reset_to(checkpoint); }
103    /// assert_eq!(bump.stats().allocated(), 0);
104    /// ```
105    unsafe fn reset_to(&self, checkpoint: Checkpoint);
106
107    /// Returns true if the bump allocator is currently [claimed].
108    ///
109    /// [claimed]: crate::traits::BumpAllocatorScope::claim
110    #[must_use]
111    fn is_claimed(&self) -> bool;
112
113    /// Returns a pointer range of free space in the bump allocator with a size of at least `layout.size()`.
114    ///
115    /// The start of the range is aligned to `layout.align()`.
116    ///
117    /// The pointer range takes up as much of the free space of the chunk as possible while satisfying the other conditions.
118    ///
119    /// # Errors
120    /// Errors if the allocation fails.
121    fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError>;
122
123    /// Allocate part of the free space returned from a [`prepare_allocation`] call.
124    ///
125    /// # Safety
126    /// - `range` must have been returned from a call to [`prepare_allocation`]
127    /// - no allocation, grow, shrink or deallocate must have taken place since then
128    /// - no resets must have taken place since then
129    /// - `layout` must be less than or equal to the `layout` used when calling
130    ///   [`prepare_allocation`], both in size and alignment
131    /// - the bump allocator must be [unclaimed] at the time [`prepare_allocation`] was called and when calling this function
132    ///
133    /// [`prepare_allocation`]: BumpAllocatorCore::prepare_allocation
134    /// [unclaimed]: crate::traits::BumpAllocatorScope::claim
135    unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8>;
136
137    /// Returns a pointer range of free space in the bump allocator with a size of at least `layout.size()`.
138    ///
139    /// The end of the range is aligned to `layout.align()`.
140    ///
141    /// The pointer range takes up as much of the free space of the chunk as possible while satisfying the other conditions.
142    ///
143    /// # Errors
144    /// Errors if the allocation fails.
145    fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError>;
146
147    /// Allocate part of the free space returned from a [`prepare_allocation_rev`] call starting at the end.
148    ///
149    /// # Safety
150    /// - `range` must have been returned from a call to [`prepare_allocation_rev`]
151    /// - no allocation, grow, shrink or deallocate must have taken place since then
152    /// - no resets must have taken place since then
153    /// - `layout` must be less than or equal to the `layout` used when calling
154    ///   [`prepare_allocation_rev`], both in size and alignment
155    /// - the bump allocator must be [unclaimed] at the time [`prepare_allocation_rev`] was called and when calling this function
156    ///
157    /// [`prepare_allocation_rev`]: BumpAllocatorCore::prepare_allocation_rev
158    /// [unclaimed]: crate::traits::BumpAllocatorScope::claim
159    unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8>;
160}
161
162assert_dyn_compatible!(BumpAllocatorCore);
163
164assert_implements! {
165    [BumpAllocatorCore + ?Sized]
166
167    Bump
168    &Bump
169    &mut Bump
170
171    BumpScope
172    &BumpScope
173    &mut BumpScope
174
175    dyn BumpAllocatorCore
176    &dyn BumpAllocatorCore
177    &mut dyn BumpAllocatorCore
178
179    dyn BumpAllocatorCoreScope
180    &dyn BumpAllocatorCoreScope
181    &mut dyn BumpAllocatorCoreScope
182
183    dyn MutBumpAllocatorCore
184    &dyn MutBumpAllocatorCore
185    &mut dyn MutBumpAllocatorCore
186
187    dyn MutBumpAllocatorCoreScope
188    &dyn MutBumpAllocatorCoreScope
189    &mut dyn MutBumpAllocatorCoreScope
190}
191
192macro_rules! impl_for_ref {
193    ($($ty:ty)*) => {
194        $(
195            unsafe impl<B: BumpAllocatorCore + ?Sized> BumpAllocatorCore for $ty {
196                #[inline(always)]
197                fn any_stats(&self) -> AnyStats<'_> {
198                    B::any_stats(self)
199                }
200
201                #[inline(always)]
202                fn checkpoint(&self) -> Checkpoint {
203                    B::checkpoint(self)
204                }
205
206                #[inline(always)]
207                unsafe fn reset_to(&self, checkpoint: Checkpoint) {
208                    unsafe { B::reset_to(self, checkpoint) };
209                }
210
211                #[inline(always)]
212                fn is_claimed(&self) -> bool {
213                    B::is_claimed(self)
214                }
215
216                #[inline(always)]
217                fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
218                    B::prepare_allocation(self, layout)
219                }
220
221                #[inline(always)]
222                unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
223                    unsafe { B::allocate_prepared(self, layout, range) }
224                }
225
226                #[inline(always)]
227                fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
228                    B::prepare_allocation_rev(self, layout)
229                }
230
231                #[inline(always)]
232                unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
233                    unsafe { B::allocate_prepared_rev(self, layout, range) }
234                }
235            }
236        )*
237    };
238}
239
240impl_for_ref! {
241    &B
242    &mut B
243}
244
245unsafe impl<B: BumpAllocatorCore> BumpAllocatorCore for WithoutDealloc<B> {
246    #[inline(always)]
247    fn any_stats(&self) -> AnyStats<'_> {
248        B::any_stats(&self.0)
249    }
250
251    #[inline(always)]
252    fn checkpoint(&self) -> Checkpoint {
253        B::checkpoint(&self.0)
254    }
255
256    #[inline(always)]
257    unsafe fn reset_to(&self, checkpoint: Checkpoint) {
258        unsafe { B::reset_to(&self.0, checkpoint) };
259    }
260
261    #[inline(always)]
262    fn is_claimed(&self) -> bool {
263        B::is_claimed(&self.0)
264    }
265
266    #[inline(always)]
267    fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
268        B::prepare_allocation(&self.0, layout)
269    }
270
271    #[inline(always)]
272    unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
273        unsafe { B::allocate_prepared(&self.0, layout, range) }
274    }
275
276    #[inline(always)]
277    fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
278        B::prepare_allocation_rev(&self.0, layout)
279    }
280
281    #[inline(always)]
282    unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
283        unsafe { B::allocate_prepared_rev(&self.0, layout, range) }
284    }
285}
286
287unsafe impl<B: BumpAllocatorCore> BumpAllocatorCore for WithoutShrink<B> {
288    #[inline(always)]
289    fn any_stats(&self) -> AnyStats<'_> {
290        B::any_stats(&self.0)
291    }
292
293    #[inline(always)]
294    fn checkpoint(&self) -> Checkpoint {
295        B::checkpoint(&self.0)
296    }
297
298    #[inline(always)]
299    unsafe fn reset_to(&self, checkpoint: Checkpoint) {
300        unsafe { B::reset_to(&self.0, checkpoint) };
301    }
302
303    #[inline(always)]
304    fn is_claimed(&self) -> bool {
305        B::is_claimed(&self.0)
306    }
307
308    #[inline(always)]
309    fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
310        B::prepare_allocation(&self.0, layout)
311    }
312
313    #[inline(always)]
314    unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
315        unsafe { B::allocate_prepared(&self.0, layout, range) }
316    }
317
318    #[inline(always)]
319    fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
320        B::prepare_allocation_rev(&self.0, layout)
321    }
322
323    #[inline(always)]
324    unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
325        unsafe { B::allocate_prepared_rev(&self.0, layout, range) }
326    }
327}
328
329unsafe impl<A, S> BumpAllocatorCore for Bump<A, S>
330where
331    A: BaseAllocator<S::GuaranteedAllocated>,
332    S: BumpAllocatorSettings,
333{
334    #[inline(always)]
335    fn any_stats(&self) -> AnyStats<'_> {
336        self.as_scope().any_stats()
337    }
338
339    #[inline(always)]
340    fn checkpoint(&self) -> Checkpoint {
341        self.as_scope().checkpoint()
342    }
343
344    #[inline(always)]
345    unsafe fn reset_to(&self, checkpoint: Checkpoint) {
346        unsafe { self.as_scope().reset_to(checkpoint) };
347    }
348
349    #[inline(always)]
350    fn is_claimed(&self) -> bool {
351        self.as_scope().is_claimed()
352    }
353
354    #[inline(always)]
355    fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
356        self.as_scope().prepare_allocation(layout)
357    }
358
359    #[inline(always)]
360    unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
361        unsafe { self.as_scope().allocate_prepared(layout, range) }
362    }
363
364    #[inline(always)]
365    fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
366        self.as_scope().prepare_allocation_rev(layout)
367    }
368
369    #[inline(always)]
370    unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
371        unsafe { self.as_scope().allocate_prepared_rev(layout, range) }
372    }
373}
374
375unsafe impl<A, S> BumpAllocatorCore for BumpScope<'_, A, S>
376where
377    A: BaseAllocator<S::GuaranteedAllocated>,
378    S: BumpAllocatorSettings,
379{
380    #[inline(always)]
381    fn any_stats(&self) -> AnyStats<'_> {
382        self.stats().into()
383    }
384
385    #[inline(always)]
386    fn checkpoint(&self) -> Checkpoint {
387        self.raw.checkpoint()
388    }
389
390    #[inline]
391    unsafe fn reset_to(&self, checkpoint: Checkpoint) {
392        unsafe { self.raw.reset_to(checkpoint) }
393    }
394
395    #[inline(always)]
396    fn is_claimed(&self) -> bool {
397        self.raw.is_claimed()
398    }
399
400    #[inline(always)]
401    fn prepare_allocation(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
402        #[cold]
403        #[inline(never)]
404        unsafe fn prepare_allocation_in_another_chunk<A, S>(
405            this: &BumpScope<'_, A, S>,
406            layout: Layout,
407        ) -> Result<Range<NonNull<u8>>, AllocError>
408        where
409            A: BaseAllocator<S::GuaranteedAllocated>,
410            S: BumpAllocatorSettings,
411        {
412            unsafe {
413                this.raw
414                    .in_another_chunk(CustomLayout(layout), RawChunk::prepare_allocation_range)
415            }
416        }
417
418        match self.raw.chunk.get().prepare_allocation_range(CustomLayout(layout)) {
419            Some(ptr) => Ok(ptr),
420            None => unsafe { prepare_allocation_in_another_chunk(self, layout) },
421        }
422    }
423
424    #[inline(always)]
425    unsafe fn allocate_prepared(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
426        debug_assert_eq!(range.start.addr().get() % layout.align(), 0);
427        debug_assert_eq!(range.end.addr().get() % layout.align(), 0);
428        debug_assert_eq!(layout.size() % layout.align(), 0);
429
430        unsafe {
431            // a successful `prepare_allocation` guarantees a non-dummy-chunk
432            let chunk = self.raw.chunk.get().as_non_dummy_unchecked();
433
434            if S::UP {
435                let end = range.start.add(layout.size());
436                chunk.set_pos_addr_and_align(end.addr().get());
437                range.start
438            } else {
439                let src = range.start;
440                let dst_end = range.end;
441                let dst = dst_end.sub(layout.size());
442                src.copy_to(dst, layout.size());
443                chunk.set_pos_addr_and_align(dst.addr().get());
444                dst
445            }
446        }
447    }
448
449    #[inline(always)]
450    fn prepare_allocation_rev(&self, layout: Layout) -> Result<Range<NonNull<u8>>, AllocError> {
451        // for now the implementation for both methods is the same
452        self.prepare_allocation(layout)
453    }
454
455    #[inline(always)]
456    unsafe fn allocate_prepared_rev(&self, layout: Layout, range: Range<NonNull<u8>>) -> NonNull<u8> {
457        debug_assert_eq!(range.start.addr().get() % layout.align(), 0);
458        debug_assert_eq!(range.end.addr().get() % layout.align(), 0);
459        debug_assert_eq!(layout.size() % layout.align(), 0);
460
461        unsafe {
462            // a successful `prepare_allocation` guarantees a non-dummy-chunk
463            let chunk = self.raw.chunk.get().as_non_dummy_unchecked();
464
465            if S::UP {
466                let dst = range.start;
467                let dst_end = dst.add(layout.size());
468
469                let src_end = range.end;
470                let src = src_end.sub(layout.size());
471
472                src.copy_to(dst, layout.size());
473
474                chunk.set_pos_addr_and_align(dst_end.addr().get());
475
476                dst
477            } else {
478                let dst_end = range.end;
479                let dst = dst_end.sub(layout.size());
480                chunk.set_pos_addr_and_align(dst.addr().get());
481                dst
482            }
483        }
484    }
485}