From 0a0dd02d4da313906ceec7570ddae224bc090c93 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:18:54 +0200 Subject: [PATCH 1/5] Fix Readme reference Turns out, using a workspace attribute for `Readme` is also a relative path to that other virtual manifest. So we get the repository readme and not the file specifically for our crate. --- static-alloc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static-alloc/Cargo.toml b/static-alloc/Cargo.toml index 7225020..b840bb4 100644 --- a/static-alloc/Cargo.toml +++ b/static-alloc/Cargo.toml @@ -9,8 +9,8 @@ rust-version = "1.85" authors.workspace = true license.workspace = true +readme = "Readme.md" repository.workspace = true -readme.workspace = true [package.metadata.docs.rs] all-features = true From ad392d02bb8986b08f13d4e7775ec79888eb8ad7 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:22:06 +0200 Subject: [PATCH 2/5] Add utility constructors from memory --- static-alloc/src/bump.rs | 137 ++++++++++++++++++++++++++++++++ static-alloc/src/unsync/bump.rs | 44 +++++++++- 2 files changed, 178 insertions(+), 3 deletions(-) diff --git a/static-alloc/src/bump.rs b/static-alloc/src/bump.rs index 9a14802..7011a4b 100644 --- a/static-alloc/src/bump.rs +++ b/static-alloc/src/bump.rs @@ -188,6 +188,13 @@ struct BumpView<'lt> { storage: &'lt UnsafeCell<[MaybeUninit]>, } +/// NOTE: see the problem of freely dereferencing a `Bump` into `BumpSlice` where the offset of +/// `storage` must not be affected. This is caused by the align of `T` exceeding that of the header. +/// If instead we included such information into the header we could fully support this +/// dereferencing again, at the cost of a few bits. There is no need to actually read those bits +/// when we're using it in a `Bump` as we only need to reconstruct the right `storage` slice when +/// used as a `BumpSlice`. The extra struct padding will only appear as more MaybeUninit data in the +/// unsized type. #[repr(C)] struct Header { consumed: AtomicUsize, @@ -285,6 +292,14 @@ pub struct Level(pub(crate) usize); /// A successful allocation and current [`Level`]. /// /// [`Level`]: struct.Level.html +/// +/// ## Design notes +/// +/// The `ptr` field is, when returned by an allocator, always a valid pointer. However, we can not +/// express this as a mutable reference to `MaybeUninit` for unsized types. Instead, a pointer is +/// needed to provide address, provenance, and pointer metadata. The cost of this is that we discard +/// the validity information which has to be unsafely re-applied by the user (of course, verifying +/// that the point actually is valid since this type is fully public). #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Allocation<'a, T: ?Sized = u8> { /// Pointer to the uninitialized region with specified layout. @@ -433,6 +448,79 @@ impl Bump { Some(unsafe { &mut *(mem as *mut BumpSlice) }) } + /// Construct a bump allocator into an uninitialized memory location. + /// + /// This fills in only a constant sized header. The rest of the allocation is left-as, i.e. if + /// remains initialized exactly in those spots the caller may have initialized with external + /// means. + /// + /// Note that this method is `const` (though this is not particularly useful yet as of `0.3.0`). + /// + /// # Usage + /// + /// This method allows `Bump` to be used together with interfaces that require an outer + /// `MaybeUninit` for their safety proofs, e.g. [`Box::new_uninit_slice`]. + /// + /// ``` + /// # use static_alloc::bump::Bump; + /// type Allocator = Bump<[u32; 128]>; + /// + /// # let num_components = 4; + /// // 4 independent allocators, e.g. for four components of your software. + /// // Still guaranteed to live in consecutive memory. + /// let mut allocators = Box::<[Allocator]>::new_uninit_slice(num_components); + /// + /// // The index here might be a runtime address. + /// // Now this arena can be used without initializing the others already. + /// let c0 = Bump::from_maybe_uninit(&mut allocators[0]); + /// // Etc. Use this temporary stack allocator. + /// let _ = c0.leak_box(0xdead_beefusize); + /// ``` + pub const fn from_maybe_uninit(data: &mut MaybeUninit) -> &'_ mut Self { + // Safety: dereferencing a pointer into a `&mut MaybeUninit`. + let header = unsafe { &raw mut (*data.as_mut_ptr()).header }; + // Safety: pointer points into a `MaybeUninit` which we have derived a mutable provenance + // pointer into. + unsafe { core::ptr::write(header, Header::empty()) }; + // Safety: only the header field requires initialization. The storage is a no-op. + unsafe { data.assume_init_mut() } + } + + /// Construct a bump allocator into an existing dynamically sized arena of memory. + /// + /// Note that this method also exists for a dynamically sized + /// [`BumpSlice`][`BumpSlice::from_memory`] which can make use of almost arbitrarily sized + /// blocks of data. In comparison, this method requires that at least `size_of::` data at + /// an aligned location in the slice is available. + /// + /// Returns `None` if there are not enough bytes beyond the first aligned offset to hold a value + /// of `Self` type. + /// + /// # Usage + /// + /// This way you may re-use storage from some arbitrary existing span of memory, provided it has + /// at least enough room to hold an aligned header. + /// + /// ``` + /// use core::mem::MaybeUninit; + /// use static_alloc::bump::Bump; + /// # fn example() -> Option<()> { + /// + /// let mut buffer = MaybeUninit::<[u8; 256]>::uninit(); + /// let bump = Bump::<[u8; 200]>::from_memory(buffer.as_mut())?; + /// + /// // Slightly less than 256 free bytes of memory to use. + /// // Exact number is unstable and depends on the align of `buffer`. + /// let allocated_slice = bump.get_slice::(50)?; + /// + /// # Some(()) } + /// ``` + pub fn from_memory(data: &mut [MaybeUninit]) -> Option<&'_ mut Self> { + // Safety: `MaybeUninit` is always valid. + let (_, usable, _) = unsafe { data.align_to_mut::>() }; + usable.first_mut().map(Self::from_maybe_uninit) + } + /// Reset the bump allocator. /// /// Requires a mutable reference, as no allocations can be active when doing it. This behaves @@ -827,6 +915,55 @@ impl Bump { } impl BumpSlice { + /// Construct a bump allocator into an existing dynamically sized arena of memory. + /// + /// # Usage + /// + /// This way you may re-use storage from some arbitrary existing span of memory, provided it has + /// at least enough room to hold an aligned header. + /// + /// ``` + /// use core::mem::MaybeUninit; + /// use static_alloc::bump::BumpSlice; + /// # fn example() -> Option<()> { + /// + /// let mut buffer = MaybeUninit::<[u8; 256]>::uninit(); + /// let bump = BumpSlice::from_memory(buffer.as_mut())?; + /// + /// // Slightly less than 256 free bytes of memory to use. + /// // Exact number is unstable and depends on the align of `buffer`. + /// let allocated_slice = bump.get_slice::(50)?; + /// + /// # Some(()) } + /// ``` + pub fn from_memory(data: &mut [MaybeUninit]) -> Option<&'_ mut Self> { + // First we must write a `Header` structure, the available storage then follows it. To do + // this we create a temporary bump allocator with an external header. + let start_addr = { + let tmp_header = Header::empty(); + let data = UnsafeCell::from_mut(data); + + let alloc = BumpView { + header: &tmp_header, + storage: &*data, + }; + + // Initialize a header at some valid location in this data. + let mut initialized_header = alloc.leak_box::
(Header::empty())?; + <*mut Header>::addr(&mut *initialized_header) + }; + + // Time to drop the temporary allocator. + let offset = start_addr - <*mut [_]>::addr(data); + // This has the right address and provenance, but wrong len metadata for a `BumpSlice`. + let bump_slice = &mut data[offset..]; + + let len = bump_slice.len() - core::mem::size_of::
(); + let data = core::ptr::slice_from_raw_parts_mut(bump_slice.as_mut_ptr(), len); + + Some(unsafe { &mut *(data as *mut BumpSlice) }) + } + /// Reset the bump allocator. /// /// Requires a mutable reference, as no allocations can be active when doing it. This behaves diff --git a/static-alloc/src/unsync/bump.rs b/static-alloc/src/unsync/bump.rs index a1e1798..4bdcfcc 100644 --- a/static-alloc/src/unsync/bump.rs +++ b/static-alloc/src/unsync/bump.rs @@ -89,7 +89,7 @@ use crate::leaked::LeakBox; #[repr(C)] pub struct Bump { /// The index used in allocation. - _index: Cell, + header: Header, /// The backing storage for raw allocated data. _data: UnsafeCell>, // Warning: when changing the data layout, you must change `BumpSlice` as well. @@ -123,7 +123,7 @@ impl Bump { /// All allocations coming from the allocator will need to be initialized manually. pub fn uninit() -> Self { Bump { - _index: Cell::new(0), + header: Header::empty(), _data: UnsafeCell::new(MaybeUninit::uninit()), } } @@ -133,10 +133,48 @@ impl Bump { /// The caller can rely on all allocations to be zeroed. pub fn zeroed() -> Self { Bump { - _index: Cell::new(0), + header: Header::empty(), _data: UnsafeCell::new(MaybeUninit::zeroed()), } } + + /// Construct a bump allocator into an uninitialized memory location. + /// + /// This fills in only a constant sized header. The rest of the allocation is left-as, i.e. if + /// remains initialized exactly in those spots the caller may have initialized with external + /// means. + /// + /// Note that this method is `const` (though this is not particularly useful yet as of `0.3.0`). + /// + /// # Usage + /// + /// This method allows `Bump` to be used together with interfaces that require an outer + /// `MaybeUninit` for their safety proofs, e.g. [`Box::new_uninit_slice`]. + /// + /// ``` + /// # use static_alloc::unsync::Bump; + /// type Allocator = Bump<[u32; 128]>; + /// + /// # let num_components = 4; + /// // 4 independent allocators, e.g. for four components of your software. + /// // Still guaranteed to live in consecutive memory. + /// let mut allocators = Box::<[Allocator]>::new_uninit_slice(num_components); + /// + /// // The index here might be a runtime address. + /// // Now this arena can be used without initializing the others already. + /// let c0 = Bump::from_maybe_uninit(&mut allocators[0]); + /// // Etc. Use this temporary stack allocator. + /// let _ = c0.bump_box::(); + /// ``` + pub const fn from_maybe_uninit(data: &mut MaybeUninit) -> &'_ mut Self { + // Safety: dereferencing a pointer into a `&mut MaybeUninit`. + let header = unsafe { &raw mut (*data.as_mut_ptr()).header }; + // Safety: pointer points into a `MaybeUninit` which we have derived a mutable provenance + // pointer into. + unsafe { core::ptr::write(header, Header::empty()) }; + // Safety: only the header field requires initialization. The storage is a no-op. + unsafe { data.assume_init_mut() } + } } #[cfg(feature = "alloc")] From f77d59881df066b5db03bc287fa4ce1ceeab031c Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Wed, 26 Aug 2026 23:22:47 +0200 Subject: [PATCH 3/5] Complement utilities from unsync to Sync bump --- static-alloc/src/bump.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/static-alloc/src/bump.rs b/static-alloc/src/bump.rs index 7011a4b..61b4caf 100644 --- a/static-alloc/src/bump.rs +++ b/static-alloc/src/bump.rs @@ -577,6 +577,25 @@ impl Bump { } } + /// Returns capacity of this allocator. + /// + /// This is how many *bytes* can be allocated within this allocator in total, with no + /// information about the currently consumed count. + pub const fn capacity(&self) -> usize { + mem::size_of::() + } + + /// Get a raw pointer to the data. + /// + /// Note that *any* use of the pointer must be done with extreme care as it may invalidate + /// existing references into the allocated region. Furthermore, bytes may not be initialized. + /// The length of the valid region is [`BumpSlice::capacity`]. + /// + /// Prefer [`Self::get_unchecked`] for reconstructing a prior allocation. + pub fn data_ptr(&self) -> NonNull { + NonNull::from(&self.storage).cast() + } + /// Allocate a region of memory. /// /// This is a safe alternative to [GlobalAlloc::alloc](#impl-GlobalAlloc). @@ -1016,6 +1035,25 @@ impl BumpSlice { } } + /// Returns capacity of this allocator. + /// + /// This is how many *bytes* can be allocated within this allocator in total, with no + /// information about the currently consumed count. + pub const fn capacity(&self) -> usize { + self.storage.get().len() + } + + /// Get a raw pointer to the data. + /// + /// Note that *any* use of the pointer must be done with extreme care as it may invalidate + /// existing references into the allocated region. Furthermore, bytes may not be initialized. + /// The length of the valid region is [`BumpSlice::capacity`]. + /// + /// Prefer [`Self::get_unchecked`] for reconstructing a prior allocation. + pub fn data_ptr(&self) -> NonNull { + NonNull::from(&self.storage).cast() + } + /// Allocate a region of memory. /// /// This is a safe alternative to [GlobalAlloc::alloc](#impl-GlobalAlloc). From 730e8f5ad40e00b0ff31dc09dfabf46485f3c40e Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:05:51 +0200 Subject: [PATCH 4/5] Fix rust-version 1.85 compatibility --- static-alloc/src/bump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static-alloc/src/bump.rs b/static-alloc/src/bump.rs index 61b4caf..39525ea 100644 --- a/static-alloc/src/bump.rs +++ b/static-alloc/src/bump.rs @@ -1478,7 +1478,7 @@ impl<'lt> BumpView<'lt> { return Some(Allocation::for_empty_slice(self.level())); } - let (layout, _) = Layout::new::().repeat(len).ok()?; + let layout = Layout::array::(len).ok()?; if layout.size() == 0 { // Synthesize the slice for this ZST. From 25a3f0fefa9b2fd51605d4ab7c75bc22f5c1d543 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:24:25 +0200 Subject: [PATCH 5/5] Update release notes for 0.3.1 --- static-alloc/Cargo.toml | 2 +- static-alloc/Changes.md | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/static-alloc/Cargo.toml b/static-alloc/Cargo.toml index b840bb4..7c7b087 100644 --- a/static-alloc/Cargo.toml +++ b/static-alloc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "static-alloc" -version = "0.3.0" +version = "0.3.1" description = "A bump allocator on static memory for the alloc-traits crate" edition = "2024" documentation = "https://docs.rs/static-alloc" diff --git a/static-alloc/Changes.md b/static-alloc/Changes.md index 87b6759..cf4d3d5 100644 --- a/static-alloc/Changes.md +++ b/static-alloc/Changes.md @@ -1,4 +1,18 @@ -# v0.3. +# v0.3.1 + +Structural: +- Fix unconditionally enabled MSRV 1.85 incompatible code. + +Feature changes: +- Add `{Bump,BumpSlice}::capacity` for synchronized allocators. +- Add `{Bump,BumpSlice}::data_ptr` for synchronized allocators. +- Add `{Bump,unsync::Bump}::from_maybe_uninit` to initialize an allocator + in-place of an existing reserved space for it. +- Add `{Bump,BumpSlice}::from_memory` to initialize an allocator in-place of a + potentially fitting memory location. + + +# v0.3.0 Structural: - MSRV is now 1.85.