diff --git a/static-alloc/src/bump.rs b/static-alloc/src/bump.rs index dcbcfc0..df48785 100644 --- a/static-alloc/src/bump.rs +++ b/static-alloc/src/bump.rs @@ -7,7 +7,7 @@ use core::alloc::{GlobalAlloc, Layout}; use core::cell::UnsafeCell; use core::mem::{self, MaybeUninit}; -use core::ptr::{NonNull, null_mut}; +use core::ptr::{null_mut, NonNull}; #[cfg(not(feature = "polyfill"))] use core::sync::atomic::{AtomicUsize, Ordering}; @@ -205,12 +205,13 @@ pub struct LeakError { /// }; /// /// let mut level: Level = BUMP.level(); -/// let mut begin: *mut u64; +/// let mut start: Level = BUMP.level(); /// let mut count; /// /// match BUMP.leak_at(first, level) { -/// Ok((first, first_level)) => { -/// begin = first; +/// Ok((_first, first_level)) => { +/// // Note we must throw away the first pointer. Its provenance does not +/// // cover the other fields. Only its level can be used. /// level = first_level; /// count = 1; /// }, @@ -227,10 +228,12 @@ pub struct LeakError { /// }); /// /// unsafe { +/// // Safety: we have an allocation here +/// let begin = BUMP.get_unchecked(start); /// // SAFETY: all `count` allocations are contiguous, begin is well aligned and no /// // reference is currently pointing at any of the values. The lifetime is `'static` as /// // the BUMP itself is static. -/// slice::from_raw_parts_mut(begin, count) +/// slice::from_raw_parts_mut(begin.ptr.as_ptr(), count) /// } /// } /// @@ -247,7 +250,7 @@ pub struct Level(pub(crate) usize); /// /// [`Level`]: struct.Level.html #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Allocation<'a, T=u8> { +pub struct Allocation<'a, T = u8> { /// Pointer to the uninitialized region with specified layout. pub ptr: NonNull, @@ -372,10 +375,12 @@ impl Bump { /// /// # Panics /// This function may panic if the provided `level` is from a different slab. - pub fn alloc_at(&self, layout: Layout, level: Level) - -> Result - { - let Allocation { ptr, lifetime, level } = self.try_alloc_at(layout, level.0)?; + pub fn alloc_at(&self, layout: Layout, level: Level) -> Result { + let Allocation { + ptr, + lifetime, + level, + } = self.try_alloc_at(layout, level.0)?; Ok(Allocation { ptr: ptr.cast(), @@ -390,9 +395,7 @@ impl Bump { /// bound by the lifetime of the reference to the allocator. /// /// [`Uninit`]: ../uninit/struct.Uninit.html - pub fn get_layout(&self, layout: Layout) - -> Option> - { + pub fn get_layout(&self, layout: Layout) -> Option> { self.try_alloc(layout) } @@ -405,9 +408,7 @@ impl Bump { /// this allocation with the preceding or succeeding one. /// /// [`Uninit`]: ../uninit/struct.Uninit.html - pub fn get_layout_at(&self, layout: Layout, at: Level) - -> Result, Failure> - { + pub fn get_layout_at(&self, layout: Layout, at: Level) -> Result, Failure> { self.try_alloc_at(layout, at.0) } @@ -438,7 +439,11 @@ impl Bump { } let layout = Layout::new::(); - let Allocation { ptr, lifetime, level, } = self.try_alloc(layout)?; + let Allocation { + ptr, + lifetime, + level, + } = self.try_alloc(layout)?; Some(Allocation { ptr: ptr.cast(), @@ -465,7 +470,11 @@ impl Bump { } let layout = Layout::new::(); - let Allocation { ptr, lifetime, level, } = self.try_alloc_at(layout, level.0)?; + let Allocation { + ptr, + lifetime, + level, + } = self.try_alloc_at(layout, level.0)?; Ok(Allocation { // It has exactly size and alignment for `V` as requested. @@ -531,9 +540,7 @@ impl Bump { /// ``` pub fn leak_box(&self, val: V) -> Option> { let Allocation { ptr, lifetime, .. } = self.get::()?; - Some(unsafe { - LeakBox::new_from_raw_non_null(ptr, val, lifetime) - }) + Some(unsafe { LeakBox::new_from_raw_non_null(ptr, val, lifetime) }) } /// Move a value into an owned allocation. @@ -543,9 +550,7 @@ impl Bump { /// [`leak_box`]: #method.leak_box pub fn leak_box_at(&self, val: V, level: Level) -> Result, Failure> { let Allocation { ptr, lifetime, .. } = self.get_at::(level)?; - Ok(unsafe { - LeakBox::new_from_raw_non_null(ptr, val, lifetime) - }) + Ok(unsafe { LeakBox::new_from_raw_non_null(ptr, val, lifetime) }) } /// Observe the current level. @@ -558,16 +563,51 @@ impl Bump { Level(self.consumed.load(Ordering::SeqCst)) } - fn try_alloc(&self, layout: Layout) - -> Option> - { + /// Get a pointer to an existing allocation at a specific level. + /// + /// The resulting pointer may be used to access an arbitrary allocation starting at the pointer + /// (i.e. including additional allocations immediately afterwards) but the caller is + /// responsible for ensuring that these accesses do not overlap other accesses. There must be + /// no more life [`LeakBox`] to any allocation being accessed this way. + /// + /// # Safety + /// + /// - The level must refer to an existing allocation, i.e. it must previously have been + /// returned in [`Allocation::level`]. + /// - As a corollary, particular it must be in-bounds of the allocator's memory. + /// - Another consequence, the result pointer must be aligned for the requested type. + pub unsafe fn get_unchecked(&self, level: Level) -> Allocation<'_, V> { + debug_assert!(level.0 <= mem::size_of_val(&self.storage)); + + debug_assert!( + level <= self.level(), + "Tried to access an allocation that does not yet exist" + ); + + let base_ptr = self.storage.get() as *mut T as *mut u8; + let alloc = base_ptr.add(level.0); + let ptr = NonNull::new_unchecked(alloc).cast::(); + + debug_assert!( + ptr.as_ptr().is_aligned(), + "Tried to access an allocation with improper type" + ); + + Allocation { + level, + lifetime: AllocTime::default(), + ptr, + } + } + + fn try_alloc(&self, layout: Layout) -> Option> { // Guess zero, this will fail when we try to access it and it isn't. let mut consumed = 0; loop { match self.try_alloc_at(layout, consumed) { Ok(alloc) => return Some(alloc), Err(Failure::Exhausted) => return None, - Err(Failure::Mismatch{ observed }) => consumed = observed.0, + Err(Failure::Mismatch { observed }) => consumed = observed.0, } } } @@ -580,14 +620,14 @@ impl Bump { /// /// # Panics /// This function panics if `expect_consumed` is larger than `length`. - fn try_alloc_at(&self, layout: Layout, expect_consumed: usize) - -> Result, Failure> - { + fn try_alloc_at( + &self, + layout: Layout, + expect_consumed: usize, + ) -> Result, Failure> { assert!(layout.size() > 0); let length = mem::size_of::(); - let base_ptr = self.storage.get() - as *mut T - as *mut u8; + let base_ptr = self.storage.get() as *mut T as *mut u8; let alignment = layout.align(); let requested = layout.size(); @@ -620,8 +660,10 @@ impl Bump { Ok(()) => (), Err(observed) => { // Someone else was faster, if you want it then recalculate again. - return Err(Failure::Mismatch { observed: Level(observed) }); - }, + return Err(Failure::Mismatch { + observed: Level(observed), + }); + } } let aligned = unsafe { @@ -741,9 +783,7 @@ impl Bump { /// resource on failure. /// // #[deprecated = "Use leak_box_at and initialize it with the value. This does not move the value in the failure case."] - pub fn leak_at(&self, val: V, level: Level) - -> Result<(&mut V, Level), LeakError> - { + pub fn leak_at(&self, val: V, level: Level) -> Result<(&mut V, Level), LeakError> { let alloc = match self.get_at::(level) { Ok(alloc) => alloc, Err(err) => return Err(LeakError::new(val, err)), @@ -777,12 +817,14 @@ impl Bump { assert!(expect_consumed <= new_consumed); assert!(new_consumed <= mem::size_of::()); - self.consumed.compare_exchange( - expect_consumed, - new_consumed, - Ordering::SeqCst, - Ordering::SeqCst, - ).map(drop) + self.consumed + .compare_exchange( + expect_consumed, + new_consumed, + Ordering::SeqCst, + Ordering::SeqCst, + ) + .map(drop) } } @@ -844,14 +886,14 @@ impl<'alloc, T> Allocation<'alloc, T> { Allocation { ptr: NonNull::from(alloc).cast(), lifetime: AllocTime::default(), - level: level, + level, } } } impl LeakError { fn new(val: T, failure: Failure) -> Self { - LeakError { val, failure, } + LeakError { val, failure } } /// Inspect the cause of this error. @@ -866,7 +908,7 @@ impl LeakError { } // SAFETY: at most one thread gets a pointer to each chunk of data. -unsafe impl Sync for Bump { } +unsafe impl Sync for Bump {} unsafe impl GlobalAlloc for Bump { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { @@ -875,12 +917,7 @@ unsafe impl GlobalAlloc for Bump { .unwrap_or_else(null_mut) } - unsafe fn realloc( - &self, - ptr: *mut u8, - current: Layout, - new_size: usize, - ) -> *mut u8 { + unsafe fn realloc(&self, ptr: *mut u8, current: Layout, new_size: usize) -> *mut u8 { let current = NonZeroLayout::from_layout(current.into()).unwrap(); // As guaranteed, `new_size` is greater than 0. let new_size = core::num::NonZeroUsize::new_unchecked(new_size); @@ -907,9 +944,10 @@ unsafe impl GlobalAlloc for Bump { } } -fn layout_reallocated(layout: NonZeroLayout, target: core::num::NonZeroUsize) - -> Option -{ +fn layout_reallocated( + layout: NonZeroLayout, + target: core::num::NonZeroUsize, +) -> Option { // This may not be a valid layout. let layout = Layout::from_size_align(target.get(), layout.align()).ok()?; // This must succeed though, as the size was non-zero. @@ -917,13 +955,11 @@ fn layout_reallocated(layout: NonZeroLayout, target: core::num::NonZeroUsize) } unsafe impl<'alloc, T> LocalAlloc<'alloc> for Bump { - fn alloc(&'alloc self, layout: NonZeroLayout) - -> Option> - { + fn alloc(&'alloc self, layout: NonZeroLayout) -> Option> { let raw_alloc = Bump::get_layout(self, layout.into())?; Some(alloc_traits::Allocation { ptr: raw_alloc.ptr, - layout: layout, + layout, lifetime: AllocTime::default(), }) } @@ -945,8 +981,7 @@ unsafe impl<'alloc, T> LocalAlloc<'alloc> for Bump { alloc: alloc_traits::Allocation<'alloc>, layout: NonZeroLayout, ) -> Option> { - if alloc.ptr.as_ptr() as usize % layout.align() == 0 - && alloc.layout.size() >= layout.size() + if alloc.ptr.as_ptr() as usize % layout.align() == 0 && alloc.layout.size() >= layout.size() { // Obvious fit, nothing to do. return Some(alloc_traits::Allocation { @@ -963,7 +998,8 @@ unsafe impl<'alloc, T> LocalAlloc<'alloc> for Bump { core::ptr::copy_nonoverlapping( alloc.ptr.as_ptr(), new_alloc.ptr.as_ptr(), - layout.size().min(alloc.layout.size()).into()); + layout.size().min(alloc.layout.size()).into(), + ); // No dealloc. return Some(new_alloc); } diff --git a/static-alloc/src/unsync/bump.rs b/static-alloc/src/unsync/bump.rs index 23171e7..14a0575 100644 --- a/static-alloc/src/unsync/bump.rs +++ b/static-alloc/src/unsync/bump.rs @@ -370,7 +370,9 @@ impl MemBump { /// # Ok::<_, static_alloc::bump::Failure>(()) /// ``` /// - /// Critically, you can rely on *other* allocations to stay valid. + /// Crucially, you can rely on *other* allocations to stay valid. The caller is responsible of + /// using the returning pointer to only refer to allocations that are not referenced through + /// any other way. /// /// ``` /// # use core::mem::MaybeUninit; @@ -387,16 +389,28 @@ impl MemBump { /// assert_eq!(*other_val, 0); // Not UB! /// # Ok::<_, static_alloc::bump::Failure>(()) /// ``` - pub unsafe fn get_unchecked(&self, level: Level) -> Allocation { + pub unsafe fn get_unchecked(&self, level: Level) -> Allocation<'_, V> { debug_assert!(level.0 < self.capacity()); + + debug_assert!( + level <= self.level(), + "Tried to access an allocation that does not yet exist" + ); + let ptr = self.data_ptr().as_ptr(); // Safety: guaranteed by the caller. - let alloc = ptr.offset(level.0 as isize) as *mut V; + let alloc = ptr.add(level.0); + let ptr = NonNull::new_unchecked(alloc).cast::(); + + debug_assert!( + ptr.as_ptr().is_aligned(), + "Tried to access an allocation with improper type" + ); Allocation { level, lifetime: AllocTime::default(), - ptr: NonNull::new_unchecked(alloc), + ptr, } }