diff --git a/static-alloc/Cargo.toml b/static-alloc/Cargo.toml index 7c7b087..736c190 100644 --- a/static-alloc/Cargo.toml +++ b/static-alloc/Cargo.toml @@ -18,6 +18,10 @@ 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 } + +[dev-dependencies] +allocator-api2 = { version = "0.4", features = ["std"] } [features] alloc = [] @@ -52,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 new file mode 100644 index 0000000..d52ffd9 --- /dev/null +++ b/static-alloc/src/allocator_api2.rs @@ -0,0 +1,147 @@ +use crate::{ + bump::{Bump, BumpSlice, BumpView}, + unsync, +}; + +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> { + // 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/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; 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); +}