From 23baa327ee84cd9e333ab153d56bdd1fc18628b4 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sat, 29 Aug 2026 03:26:18 +0200 Subject: [PATCH 1/2] Add optional allocator_api2, the std one --- static-alloc/Cargo.toml | 1 + static-alloc/src/allocator_api2.rs | 90 ++++++++++++++++++++++++++++++ static-alloc/src/bump.rs | 6 +- static-alloc/src/lib.rs | 3 + 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 static-alloc/src/allocator_api2.rs diff --git a/static-alloc/Cargo.toml b/static-alloc/Cargo.toml index 7c7b087..50579da 100644 --- a/static-alloc/Cargo.toml +++ b/static-alloc/Cargo.toml @@ -18,6 +18,7 @@ all-features = true [dependencies] alloc-traits = { path = "../alloc-traits", version = "0.1.0" } portable-atomic = { version = "1", optional = true, default-features = false } +allocator-api2 = { version = "0.4", optional = true, default-features = false } [features] alloc = [] diff --git a/static-alloc/src/allocator_api2.rs b/static-alloc/src/allocator_api2.rs new file mode 100644 index 0000000..5e319fe --- /dev/null +++ b/static-alloc/src/allocator_api2.rs @@ -0,0 +1,90 @@ +use crate::bump::{Bump, BumpSlice, BumpView}; + +use allocator_api2::alloc::{AllocError, Allocator, Layout}; +use core::ptr::NonNull; + +unsafe impl Allocator for Bump { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + Allocator::allocate(&self.as_view(), layout) + } + + unsafe fn deallocate(&self, _: NonNull, _: Layout) {} + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, AllocError> { + // Safety: passing along requirements. These two allocators serve the same allocations, a + // property we permit for these two of our own types. + unsafe { Allocator::shrink(&self.as_view(), ptr, old_layout, new_layout) } + } +} + +unsafe impl Allocator for BumpSlice { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + Allocator::allocate(&self.as_view(), layout) + } + + unsafe fn deallocate(&self, _: NonNull, _: Layout) {} + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, AllocError> { + // Safety: passing along requirements. These two allocators serve the same allocations, a + // property we permit for these two of our own types. + unsafe { Allocator::shrink(&self.as_view(), ptr, old_layout, new_layout) } + } +} + +unsafe impl Allocator for BumpView<'_> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let len = layout.size(); + match self.get_layout(layout) { + None => Err(AllocError), + Some(allocation) => Ok(NonNull::slice_from_raw_parts(allocation.ptr, len)), + } + } + + unsafe fn deallocate(&self, _: NonNull, _: Layout) {} + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, AllocError> { + debug_assert!(new_layout.size() <= old_layout.size()); + let len = new_layout.size(); + + let offset = ptr.align_offset(new_layout.align()); + + if offset > 0 { + if old_layout + .size() + .checked_sub(offset) + .is_none_or(|n| n < len) + { + // Won't fit in-place. Sorry. + return Err(AllocError); + } + + // Safety: in-bounds as we just verified that old layout has at least as many bytes as + // offset, and the caller was required to pass a live allocation with corresponding + // layout; implying that it also has that many bytes. + let dst = unsafe { ptr.byte_add(offset) }; + // Safety: just verified that layout has at least `len` bytes after the offset so `dst` + // also has provenance according to the caller's requirements. + unsafe { ptr.copy_to(dst, len) }; + dst + } else { + ptr + }; + + Ok(NonNull::slice_from_raw_parts(ptr, len)) + } +} diff --git a/static-alloc/src/bump.rs b/static-alloc/src/bump.rs index 39525ea..d4feeba 100644 --- a/static-alloc/src/bump.rs +++ b/static-alloc/src/bump.rs @@ -183,7 +183,7 @@ pub struct BumpSlice { /// /// Note: You might think that we can #[derive(Clone, Copy)] -struct BumpView<'lt> { +pub(crate) struct BumpView<'lt> { header: &'lt Header, storage: &'lt UnsafeCell<[MaybeUninit]>, } @@ -564,7 +564,7 @@ impl Bump { self.header = Header::empty(); } - fn as_view(&self) -> BumpView<'_> { + pub(crate) fn as_view(&self) -> BumpView<'_> { BumpView { header: &self.header, storage: { @@ -1028,7 +1028,7 @@ impl BumpSlice { self.header = Header::empty(); } - fn as_view(&self) -> BumpView<'_> { + pub(crate) fn as_view(&self) -> BumpView<'_> { BumpView { header: &self.header, storage: &self.storage, diff --git a/static-alloc/src/lib.rs b/static-alloc/src/lib.rs index d64f6a5..ea41cbd 100644 --- a/static-alloc/src/lib.rs +++ b/static-alloc/src/lib.rs @@ -47,6 +47,9 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "allocator-api2")] +mod allocator_api2; + pub mod bump; pub use bump::Bump; pub mod leaked; From da4071cc6f2f019d61f262f6c2a6f0779cd1a53b Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:37:05 +0200 Subject: [PATCH 2/2] Rudimentary tests for allocator-api2 --- static-alloc/Cargo.toml | 8 ++ static-alloc/src/allocator_api2.rs | 115 ++++++++++++++++++++------- static-alloc/tests/allocator-api2.rs | 38 +++++++++ 3 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 static-alloc/tests/allocator-api2.rs diff --git a/static-alloc/Cargo.toml b/static-alloc/Cargo.toml index 50579da..736c190 100644 --- a/static-alloc/Cargo.toml +++ b/static-alloc/Cargo.toml @@ -20,6 +20,9 @@ alloc-traits = { path = "../alloc-traits", version = "0.1.0" } portable-atomic = { version = "1", optional = true, default-features = false } allocator-api2 = { version = "0.4", optional = true, default-features = false } +[dev-dependencies] +allocator-api2 = { version = "0.4", features = ["std"] } + [features] alloc = [] # Enables the `unsync::Chain` module. Note that this is explicitly outside the @@ -53,3 +56,8 @@ path = "tests/unsync.rs" name = "chain" path = "tests/chain.rs" required-features = ["nightly_chain"] + +[[test]] +name = "allocator-api2" +path = "tests/allocator-api2.rs" +required-features = ["allocator-api2"] diff --git a/static-alloc/src/allocator_api2.rs b/static-alloc/src/allocator_api2.rs index 5e319fe..d52ffd9 100644 --- a/static-alloc/src/allocator_api2.rs +++ b/static-alloc/src/allocator_api2.rs @@ -1,4 +1,7 @@ -use crate::bump::{Bump, BumpSlice, BumpView}; +use crate::{ + bump::{Bump, BumpSlice, BumpView}, + unsync, +}; use allocator_api2::alloc::{AllocError, Allocator, Layout}; use core::ptr::NonNull; @@ -58,33 +61,87 @@ unsafe impl Allocator for BumpView<'_> { old_layout: Layout, new_layout: Layout, ) -> Result, AllocError> { - debug_assert!(new_layout.size() <= old_layout.size()); - let len = new_layout.size(); - - let offset = ptr.align_offset(new_layout.align()); - - if offset > 0 { - if old_layout - .size() - .checked_sub(offset) - .is_none_or(|n| n < len) - { - // Won't fit in-place. Sorry. - return Err(AllocError); - } - - // Safety: in-bounds as we just verified that old layout has at least as many bytes as - // offset, and the caller was required to pass a live allocation with corresponding - // layout; implying that it also has that many bytes. - let dst = unsafe { ptr.byte_add(offset) }; - // Safety: just verified that layout has at least `len` bytes after the offset so `dst` - // also has provenance according to the caller's requirements. - unsafe { ptr.copy_to(dst, len) }; - dst - } else { - ptr - }; - - Ok(NonNull::slice_from_raw_parts(ptr, len)) + // Safety: Caller guarantees `ptr` was allocated from `self` (or equivalent, for transitive + // use of this) which requires it to be valid and described by `old_layout`. + unsafe { shrink_in_place(ptr, old_layout, new_layout) } } } + +unsafe impl Allocator for unsync::Bump { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + ::allocate(self, layout) + } + + unsafe fn deallocate(&self, _: NonNull, _: Layout) {} + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, AllocError> { + // Safety: passing along requirements. These two allocators serve the same allocations, a + // property we permit for these two of our own types. + unsafe { ::shrink(self, ptr, old_layout, new_layout) } + } +} + +unsafe impl Allocator for unsync::BumpSlice { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let len = layout.size(); + match self.alloc(layout) { + None => Err(AllocError), + Some(allocation) => Ok(NonNull::slice_from_raw_parts(allocation, len)), + } + } + + unsafe fn deallocate(&self, _: NonNull, _: Layout) {} + + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, AllocError> { + // Safety: Caller guarantees `ptr` was allocated from `self` (or equivalent, for transitive + // use of this) which requires it to be valid and described by `old_layout`. + unsafe { shrink_in_place(ptr, old_layout, new_layout) } + } +} + +/// Safety: caller must only call this on `ptr` point to a valid allocation with the fitting layout +/// `old_layout`. Returns a derived pointer into the same allocation on success. +unsafe fn shrink_in_place( + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, +) -> Result, AllocError> { + debug_assert!(new_layout.size() <= old_layout.size()); + let len = new_layout.size(); + + let offset = ptr.align_offset(new_layout.align()); + + if offset > 0 { + if old_layout + .size() + .checked_sub(offset) + .is_none_or(|n| n < len) + { + // Won't fit in-place. Sorry. + return Err(AllocError); + } + + // Safety: in-bounds as we just verified that old layout has at least as many bytes as + // offset, and the caller was required to pass a live allocation with corresponding + // layout; implying that it also has that many bytes. + let dst = unsafe { ptr.byte_add(offset) }; + // Safety: just verified that layout has at least `len` bytes after the offset so `dst` + // also has provenance according to the caller's requirements. + unsafe { ptr.copy_to(dst, len) }; + dst + } else { + ptr + }; + + Ok(NonNull::slice_from_raw_parts(ptr, len)) +} diff --git a/static-alloc/tests/allocator-api2.rs b/static-alloc/tests/allocator-api2.rs new file mode 100644 index 0000000..6802bb7 --- /dev/null +++ b/static-alloc/tests/allocator-api2.rs @@ -0,0 +1,38 @@ +use allocator_api2 as astd; +use static_alloc::{Bump, unsync}; + +#[test] +fn local_vector() { + let storage = Bump::<[u8; 128]>::uninit(); + + let mut v = astd::vec::Vec::::new_in(&storage); + assert_eq!(v.len(), 0); + v.extend(0..64); + assert_eq!(v.len(), 64); + + assert!( + v.try_reserve(64).is_err(), + "Reserved more space than available" + ); + + let _ = v.push_within_capacity(0); + assert!(v.capacity() <= 128); +} + +#[test] +fn unsync_vector() { + let storage = unsync::Bump::<[u8; 128]>::uninit(); + + let mut v = astd::vec::Vec::::new_in(&storage); + assert_eq!(v.len(), 0); + v.extend(0..64); + assert_eq!(v.len(), 64); + + assert!( + v.try_reserve(64).is_err(), + "Reserved more space than available" + ); + + let _ = v.push_within_capacity(0); + assert!(v.capacity() <= 128); +}