From a75eb23a19cb9d776041d5409e18fc36fffbdabc Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 10:03:21 +0100 Subject: [PATCH 1/9] tests: write allocation tests; feat: small refactors needed for the tests and implementation of *_parts, also for the allocator counterparts. replaced *_raw_parts --- src/errors.rs | 2 +- src/lib.rs | 288 ++++++++++++++++---- tests/allocation.rs | 622 ++++++++++++++++++++++++++++++++++++++++++++ tests/main.rs | 11 +- 4 files changed, 865 insertions(+), 58 deletions(-) create mode 100644 tests/allocation.rs diff --git a/src/errors.rs b/src/errors.rs index b470e07d..74ff1d60 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,7 +12,7 @@ use { } }; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SmallVecError { CapacityOverflow, AllocationError(Layout) diff --git a/src/lib.rs b/src/lib.rs index 9f391826..0a28428b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,7 @@ use defmt::{ write as dewrite }; pub use errors::SmallVecError; +use core::ptr; #[cfg(feature = "std")] use std::io; use { @@ -265,35 +266,94 @@ impl SmallVec { } } - /// Creates a `SmallVec` directly from the raw components of another - /// `SmallVec`. + /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, and + /// a capacity. /// /// # Safety /// /// This is highly unsafe, due to the number of invariants that aren’t /// checked: /// - /// - `ptr` needs to have been previously allocated via `SmallVec` from its - /// spilled storage (at least, it’s highly likely to be incorrect if it - /// wasn’t). - /// - `ptr`’s `T` type needs to be the same size and alignment that it was - /// allocated with + /// - `ptr` must be [*currently allocated*] via the global allocator. + /// As such, if it belonged to a `SmallVec`, it must have been spilled to + /// the heap. + /// - `T` needs to have the same alignment as what `ptr` was allocated with. + /// (`T` having a less strict alignment is not sufficient, the alignment really + /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be + /// allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs + /// to be the same size as the pointer was allocated with. (Because similar to + /// alignment, [`dealloc`] must be called with the same layout `size`.) /// - `length` needs to be less than or equal to `capacity`. + /// - The first `length` values must be properly initialized values of type `T`. /// - `capacity` needs to be the capacity that the pointer was allocated /// with. + /// - The allocated size in bytes must be no larger than `isize::MAX`. + /// See the safety documentation of [`std::pointer::offset`]. + /// + /// These requirements are always upheld by any `ptr` that has been allocated + /// via any `SmallVec`. Other allocation sources are allowed + /// if the invariants are upheld. + /// + /// Additionally, `capacity` must be greater than `N`; that is, the new + /// `SmallVec` must need to spill over into heap allocated storage. This + /// condition is asserted against. /// /// Violating these may cause problems like corrupting the allocator’s /// internal data structures. /// + /// The ownership of `ptr` is effectively transferred to the `SmallVec` + /// which may then deallocate, reallocate or change the contents of memory + /// pointed to by the pointer at will. Ensure that nothing else uses the + /// pointer after calling this function. + /// + /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory + /// + /// # Examples + /// + /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, a capacity, + /// and an allocator. + /// + /// # Safety + /// + /// This is highly unsafe, due to the number of invariants that aren’t + /// checked: + /// + /// - `ptr` must be [*currently allocated*] via the given allocator `alloc`. + /// If it belonged to a `SmallVec`, it must have been spilled to + /// the heap. + /// - `T` needs to have the same alignment as what `ptr` was allocated with. + /// (`T` having a less strict alignment is not sufficient, the alignment really + /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be + /// allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs + /// to be the same size as the pointer was allocated with. (Because similar to + /// alignment, [`dealloc`] must be called with the same layout `size`.) + /// - `length` needs to be less than or equal to `capacity`. + /// - The first `length` values must be properly initialized values of type `T`. + /// - `capacity` needs to [*fit*] the layout size that the pointer was allocated with. + /// - The allocated size in bytes must be no larger than `isize::MAX`. + /// See the safety documentation of [`std::pointer::offset`]. + /// + /// These requirements are always upheld by any `ptr` that has been allocated + /// via any `SmallVec`. Other allocation sources are allowed + /// if the invariants are upheld. + /// /// Additionally, `capacity` must be greater than `N`; that is, the new /// `SmallVec` must need to spill over into heap allocated storage. This /// condition is asserted against. /// - /// The ownership of `ptr` is effectively transferred to the `SmallVec` + /// Violating these may cause problems like corrupting the allocator’s + /// internal data structures. + /// + /// The ownership of `ptr` is effectively transferred to the `SmallVec` /// which may then deallocate, reallocate or change the contents of memory /// pointed to by the pointer at will. Ensure that nothing else uses the /// pointer after calling this function. /// + /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory + /// [*fit*]: std::alloc::Allocator#memory-fitting + /// /// # Examples /// /// ``` @@ -301,17 +361,11 @@ impl SmallVec { /// /// let mut v: SmallVec<_, 1> = SmallVec::from([1, 2, 3]); /// - /// // Pull out the important parts of `v`. - /// let p = v.as_mut_ptr(); - /// let length = v.len(); - /// let cap = v.capacity(); + /// // Pull out the important parts of `v` /// let spilled = v.spilled(); + /// let (ptr, length, capacity) = v.into_parts(); /// /// unsafe { - /// // Forget all about `v`. The heap allocation that stored the - /// // three values won't be deallocated. - /// std::mem::forget(v); - /// /// // Overwrite memory with [4, 5, 6]. /// // /// // This is only safe if `spilled` is true! Otherwise, we are @@ -319,35 +373,70 @@ impl SmallVec { /// // stack. /// assert!(spilled); /// for i in 0..length { - /// std::ptr::write(p.add(i), 4 + i); + /// ptr.add(i).write(4 + i); /// } /// /// // Put everything back together into a SmallVec with a different - /// // amount of inline storage, but which is still less than `cap`. - /// let rebuilt = SmallVec::<_, 2>::from_raw_parts(p, length, cap); - /// assert_eq!(&*rebuilt, &[4, 5, 6]); + /// // amount of inline storage, but which is still less than `capacity`. + /// let rebuilt = SmallVec::<_, 2>::from_parts(ptr, length, capacity); + /// assert_eq!(rebuilt, &[4, 5, 6]); /// } /// ``` #[inline] - pub unsafe fn from_raw_parts( - ptr: *mut T, + pub unsafe fn from_parts( + ptr: NonNull, length: usize, capacity: usize ) -> SmallVec { - assert!(!Self::IS_ZST); - - // SAFETY: We require caller to provide same ptr as we alloc - // and we never alloc null pointer. - let ptr = unsafe { - debug_assert!(!ptr.is_null(), "Called `from_raw_parts` with null pointer."); - NonNull::new_unchecked(ptr) - }; + unsafe { Self::from_parts_in(ptr, length, capacity, Global) } + } - SmallVec { - length: LocatedLength::new(length, true), - raw: RawSmallVec::new_heap(ptr, capacity), - allocator: Global - } + /// Decomposes a `SmallVec` into its raw components: + /// `(NonNull pointer, length, capacity)`. + /// + /// Returns the `NonNull` pointer to the underlying data, the length of + /// the vector (in elements), and the allocated capacity of the + /// data (in elements). These are the same arguments + /// in the same order as the arguments to [`from_parts`]. + /// + /// After calling this function, the caller is responsible for the + /// memory previously managed by the `SmallVec`. Most often, one does + /// this by converting the raw pointer, length, and capacity back + /// into a `SmallVec` with the [`from_parts`] function, allowing the + /// destructor to perform the cleanup; more generally, + /// if `T` is non-zero-sized and the capacity is non-zero, one may use + /// any method that calls [`dealloc`] with a layout of + /// `Layout::array::(capacity)`; if `T` is zero-sized or the + /// capacity is zero, nothing needs to be done. + /// + /// [`from_parts`]: SmallVec::from_parts + /// [`dealloc`]: alloc::alloc::GlobalAlloc::dealloc + /// + /// # Panics + /// + /// This function will panic if the contents are not spilled on the heap. + /// + /// # Examples + /// + /// ``` + /// # use smallvec::SmallVec; + /// let v: SmallVec = SmallVec::from([-1, 0, 1]); + /// + /// let (ptr, length, cap) = v.into_parts(); + /// + /// let rebuilt = unsafe { + /// // We can now make changes to the components, such as + /// // transmuting the raw pointer to a compatible type. + /// let ptr = ptr.cast::(); + /// + /// SmallVec::::from_parts(ptr, length, cap) + /// }; + /// assert_eq!(rebuilt, [4294967295, 0, 1]); + /// ``` + #[inline] + pub fn into_parts(self) -> (NonNull, usize, usize) { + let (ptr, len, capacity, _) = self.into_parts_with_allocator(); + (ptr, len, capacity) } } @@ -1203,25 +1292,112 @@ impl SmallVec { } } - /// Decomposes a `SmallVec` into its raw components: - /// `(pointer, length, capacity)`. + /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, a capacity, + /// and an allocator. /// - /// Returns the raw pointer to the underlying data, the length of - /// the vector (in elements), and the allocated capacity of the - /// data (in elements). These are the same arguments in the same - /// order as the arguments to [`from_raw_parts`]. + /// # Safety + /// + /// This is highly unsafe, due to the number of invariants that aren’t + /// checked: + /// + /// - `ptr` must be [*currently allocated*] via the given allocator `alloc`. + /// If it belonged to a `SmallVec`, it must have been spilled to + /// the heap. + /// - `T` needs to have the same alignment as what `ptr` was allocated with. + /// (`T` having a less strict alignment is not sufficient, the alignment really + /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be + /// allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs + /// to be the same size as the pointer was allocated with. (Because similar to + /// alignment, [`dealloc`] must be called with the same layout `size`.) + /// - `length` needs to be less than or equal to `capacity`. + /// - The first `length` values must be properly initialized values of type `T`. + /// - `capacity` needs to [*fit*] the layout size that the pointer was allocated with. + /// - The allocated size in bytes must be no larger than `isize::MAX`. + /// See the safety documentation of [`std::pointer::offset`]. + /// + /// These requirements are always upheld by any `ptr` that has been allocated + /// via any `SmallVec`. Other allocation sources are allowed + /// if the invariants are upheld. + /// + /// Additionally, `capacity` must be greater than `N`; that is, the new + /// `SmallVec` must need to spill over into heap allocated storage. This + /// condition is asserted against. + /// + /// Violating these may cause problems like corrupting the allocator’s + /// internal data structures. + /// + /// The ownership of `ptr` is effectively transferred to the `SmallVec` + /// which may then deallocate, reallocate or change the contents of memory + /// pointed to by the pointer at will. Ensure that nothing else uses the + /// pointer after calling this function. + /// + /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory + /// [*fit*]: std::alloc::Allocator#memory-fitting + /// + /// # Examples + /// + /// ``` + /// use smallvec::SmallVec; + /// + /// let mut v: SmallVec<_, 1> = SmallVec::from([1, 2, 3]); + /// + /// // Pull out the important parts of `v` + /// let spilled = v.spilled(); + /// let (ptr, length, capacity) = v.into_parts(); + /// + /// unsafe { + /// // Overwrite memory with [4, 5, 6]. + /// // + /// // This is only safe if `spilled` is true! Otherwise, we are + /// // writing into the old `SmallVec`'s inline storage on the + /// // stack. + /// assert!(spilled); + /// for i in 0..length { + /// ptr.add(i).write(4 + i); + /// } + /// + /// // Put everything back together into a SmallVec with a different + /// // amount of inline storage, but which is still less than `capacity`. + /// let rebuilt = SmallVec::<_, 2>::from_parts(ptr, length, capacity); + /// assert_eq!(rebuilt, &[4, 5, 6]); + /// } + /// ``` + #[inline] + pub unsafe fn from_parts_in( + ptr: NonNull, + length: usize, + capacity: usize, + allocator: A + ) -> SmallVec { + assert!(!Self::IS_ZST); + + SmallVec { + length: LocatedLength::new(length, true), + raw: RawSmallVec::new_heap(ptr, capacity), + allocator + } + } + + /// Decomposes a `SmallVec` into its raw components: + /// `(NonNull pointer, length, capacity, allocator)`. + /// + /// Returns the `NonNull` pointer to the underlying data, the length of + /// the vector (in elements), the allocated capacity of the + /// data (in elements), and the allocator. These are the same arguments + /// in the same order as the arguments to [`from_parts_in`]. /// /// After calling this function, the caller is responsible for the /// memory previously managed by the `SmallVec`. Most often, one does /// this by converting the raw pointer, length, and capacity back - /// into a `SmallVec` with the [`from_raw_parts`] function; more generally, - /// if `T` is non-zero-sized and the capacity is nonzero, one may use - /// any method that calls [`dealloc`] with a layout of + /// into a `SmallVec` with the [`from_parts_in`] function, allowing the + /// destructor to perform the cleanup; more generally, + /// if `T` is non-zero-sized and the capacity is non-zero, one may use + /// any method that calls [`A::dealloc`] with a layout of /// `Layout::array::(capacity)`; if `T` is zero-sized or the /// capacity is zero, nothing needs to be done. /// - /// [`from_raw_parts`]: SmallVec::from_raw_parts - /// [`dealloc`]: alloc::alloc::GlobalAlloc::dealloc + /// [`from_parts_in`]: SmallVec::from_parts_in /// /// # Panics /// @@ -1233,25 +1409,25 @@ impl SmallVec { /// # use smallvec::SmallVec; /// let v: SmallVec = SmallVec::from([-1, 0, 1]); /// - /// let (ptr, length, cap) = v.into_raw_parts(); + /// let (ptr, length, cap, alloc) = v.into_parts_with_allocator(); /// /// let rebuilt = unsafe { /// // We can now make changes to the components, such as /// // transmuting the raw pointer to a compatible type. - /// let ptr = ptr as *mut u32; + /// let ptr = ptr.cast::(); /// - /// SmallVec::::from_raw_parts(ptr, length, cap) + /// SmallVec::::from_parts_in(ptr, length, cap, alloc) /// }; /// assert_eq!(rebuilt, [4294967295, 0, 1]); /// ``` #[inline] - pub fn into_raw_parts(self) -> (*mut T, usize, usize) { + pub fn into_parts_with_allocator(self) -> (NonNull, usize, usize, A) { #[cold] #[inline(never)] #[track_caller] fn assert_failed() -> ! { panic!( - "SmallVec::into_raw_parts() called on inline (stack) SmallVec, \ + "into_parts called on inline (stack) SmallVec, \ which cannot be safely leaked" ); } @@ -1260,7 +1436,15 @@ impl SmallVec { } let mut me = ManuallyDrop::new(self); - (me.as_mut_ptr(), me.len(), me.capacity()) + // SAFETY: The `SmallVec` is spilled. + unsafe { + ( + NonNull::new_unchecked(me.as_mut_ptr()), + me.len(), + me.capacity(), + ptr::read(&me.allocator), + ) + } } #[inline] diff --git a/tests/allocation.rs b/tests/allocation.rs new file mode 100644 index 00000000..18913d71 --- /dev/null +++ b/tests/allocation.rs @@ -0,0 +1,622 @@ +#![cfg(all( + any(feature = "allocator-api", feature = "allocator-api2"), + feature = "std" // `std` is necessary for the base `System` allocator +))] + +#[cfg(not(feature = "allocator-api2"))] +extern crate alloc; +#[cfg(not(feature = "allocator-api2"))] +use alloc::alloc::{ + AllocError, + Allocator, + Layout +}; +#[cfg(feature = "allocator-api2")] +use allocator_api2::alloc::{ + AllocError, + Allocator, + Layout +}; +#[cfg(feature = "allocator-api2")] +use std::alloc::Allocator as _; +use { + core::{ + cell::{ + Cell, + RefCell + }, + ptr::NonNull + }, + smallvec::{ + SmallVec, + SmallVecError + }, + std::{ + alloc::System, + assert_matches + } +}; + +#[derive(Clone, Copy, Debug, Default)] +struct AllocStats { + pub alloc_calls: usize, + pub dealloc_calls: usize, + pub grow_calls: usize, + pub shrink_calls: usize, + pub allocated_bytes: isize, + pub alloc_align: usize, + pub dealloc_align: usize +} + +#[derive(Clone, Debug, Default)] +struct TestAlloc { + stats: RefCell +} + +impl TestAlloc { + #[inline] + fn new() -> Self { + Self::default() + } + + #[inline] + fn stats(&self) -> AllocStats { + *self.stats.borrow() + } + + #[inline] + fn is_clean(&self) -> bool { + let s = self.stats.borrow(); + s.alloc_calls == s.dealloc_calls && s.allocated_bytes == 0 + } +} + +unsafe impl Allocator for TestAlloc { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let ptr = System.allocate(layout).map_err(|_| AllocError)?; + let mut s = self.stats.borrow_mut(); + s.alloc_calls += 1; + s.allocated_bytes += layout.size() as isize; + s.alloc_align = layout.align(); + Ok(ptr) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + let mut s = self.stats.borrow_mut(); + s.dealloc_calls += 1; + s.allocated_bytes -= layout.size() as isize; + s.dealloc_align = layout.align(); + unsafe { System.deallocate(ptr, layout) }; + } + + unsafe fn grow( + &self, + ptr: NonNull, + old: Layout, + new: Layout + ) -> Result, AllocError> { + let out = unsafe { System.grow(ptr, old, new) }.map_err(|_| AllocError)?; + let mut s = self.stats.borrow_mut(); + s.grow_calls += 1; + s.allocated_bytes += new.size() as isize - old.size() as isize; + Ok(out) + } + + unsafe fn shrink( + &self, + ptr: NonNull, + old: Layout, + new: Layout + ) -> Result, AllocError> { + let out = unsafe { System.shrink(ptr, old, new) }.map_err(|_| AllocError)?; + let mut s = self.stats.borrow_mut(); + s.shrink_calls += 1; + s.allocated_bytes -= old.size() as isize - new.size() as isize; + Ok(out) + } +} + +// An allocator that delegates to [`TestAlloc`] but fails once it has handed +// out `remaining` successful allocations. Used to verify the `try_*` +// recovery paths. +#[derive(Clone, Debug, Default)] +struct FailingAlloc { + inner: TestAlloc, + remaining: Cell +} + +impl FailingAlloc { + #[inline] + fn new(remaining: usize) -> Self { + Self { + inner: TestAlloc::new(), + remaining: Cell::new(remaining) + } + } + + #[inline] + fn stats(&self) -> AllocStats { + self.inner.stats() + } + + #[inline] + fn is_clean(&self) -> bool { + self.inner.is_clean() + } +} + +unsafe impl Allocator for FailingAlloc { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let remaining = self.remaining.get(); + if remaining == 0 { + return Err(AllocError); + } + self.remaining.set(remaining - 1); + self.inner.allocate(layout) + } + + #[inline] + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { self.inner.deallocate(ptr, layout) } + } +} + +#[test] +fn test_inline_ops_do_not_allocate() { + const INLINE_SIZE: usize = 8; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + vec.extend(0..INLINE_SIZE as i32); + assert!(!vec.spilled()); + + vec.pop(); + vec.push(69); + vec.remove(2); + vec.insert(0, 2); + vec.swap_remove(0); + vec.fill(420); + vec.retain(|&x| x % 2 == 0); + vec.truncate(3); + drop(vec.drain(..1)); + vec.clear(); + + // `grow` within the inline capacity must not allocate + vec.grow(INLINE_SIZE); + + // `split_off` that stays inline must not allocate either + vec.extend(0..6); + let tail = vec.split_off(3); + assert_eq!(vec.as_slice(), &[0, 1, 2]); + assert_eq!(tail.as_slice(), &[3, 4, 5]); + + drop(vec); + drop(tail); + + assert_eq!(alloc.stats().alloc_calls, 0); + assert_eq!(alloc.stats().dealloc_calls, 0); + assert!(alloc.is_clean()); +} + +#[test] +fn test_spill_then_drop() { + const INLINE_SIZE: usize = 8; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..INLINE_SIZE as _ { + vec.push(i); + } + + // spill + vec.push(42); + // dealloc + drop(vec); + + assert_eq!(alloc.stats().alloc_calls, 1); + assert!(alloc.is_clean()); +} + +#[test] +fn test_with_capacity_in_allocates_properly() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + // allocate eagerly above inline size + let vec = SmallVec::::with_capacity_in(INLINE_SIZE + 1, &alloc); + drop(vec); + + assert_eq!(alloc.stats().alloc_calls, 1); + assert!(alloc.is_clean()); + + let vec = SmallVec::::with_capacity_in(INLINE_SIZE, &alloc); + drop(vec); + + assert_eq!(alloc.stats().alloc_calls, 1); + assert!(alloc.is_clean()); +} + +#[test] +fn test_moving_does_not_deallocate() { + const INLINE_SIZE: usize = 4; + + let alloc_a = TestAlloc::new(); + let alloc_b = TestAlloc::new(); + + let vec = SmallVec::::with_capacity_in(INLINE_SIZE + 1, &alloc_a); + let (ptr, len, cap, allocator) = vec.into_parts_with_allocator(); + + // moving to a different inline size must not deallocate + let vec = unsafe { + SmallVec::::from_parts_in(ptr, len, cap, allocator) + }; + drop(vec); + + // and the deallocation must go through `alloc_a`, not `alloc_b` + assert_eq!(alloc_a.stats().alloc_calls, 1); + assert_eq!(alloc_a.stats().dealloc_calls, 1); + assert!(alloc_a.is_clean()); + assert_eq!(alloc_b.stats().alloc_calls, 0); + assert_eq!(alloc_b.stats().dealloc_calls, 0); +} + +#[test] +fn test_unspill_deallocates() { + const INLINE_SIZE: usize = 2; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..(INLINE_SIZE + 1) as _ { + vec.push(i); + } + + assert!(vec.spilled()); + + vec.remove(1); + vec.shrink_to_fit(); + + assert!(!vec.spilled()); + assert_eq!(alloc.stats().alloc_calls, 1); + assert_eq!(alloc.stats().dealloc_calls, 1); + + drop(vec); + + assert_eq!(alloc.stats().dealloc_calls, 1); + assert!(alloc.is_clean()); +} + +#[test] +fn test_capacity_overflow_preserves_vec() { + const INLINE_SIZE: usize = 6; + + let alloc = TestAlloc::new(); + + // `try_grow` overflow on a spilled vec + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..(INLINE_SIZE + 1) as _ { + vec.push(i); + } + assert_eq!(alloc.stats().alloc_calls, 1); + + assert_eq!( + vec.try_grow((isize::MAX as usize) + 4), + Err(SmallVecError::CapacityOverflow) + ); + assert_eq!( + vec.as_slice(), + core::array::from_fn::(|i| i as _) + ); + assert_eq!(alloc.stats().alloc_calls, 1); + + drop(vec); + + // `try_reserve` overflow on an inline vec + let mut vec = SmallVec::::new_in(&alloc); + vec.push(1); + assert_eq!( + vec.try_reserve(usize::MAX), + Err(SmallVecError::CapacityOverflow) + ); + assert_eq!(vec.as_slice(), &[1]); + assert_eq!(alloc.stats().alloc_calls, 1); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_no_alloc_on_zst() { + const INLINE_SIZE: usize = 2; + + let alloc = TestAlloc::new(); + + // pushing past the inline capacity must not allocate + let mut vec = SmallVec::<(), INLINE_SIZE, &TestAlloc>::new_in(&alloc); + for _ in 0..(INLINE_SIZE + 1) as _ { + vec.push(()); + } + drop(vec); + + // neither must requesting a large capacity + let vec = SmallVec::<(), INLINE_SIZE, &TestAlloc>::with_capacity_in(1024, &alloc); + assert_eq!(vec.capacity(), usize::MAX); + drop(vec); + + assert_eq!(alloc.stats().alloc_calls, 0); + assert_eq!(alloc.stats().dealloc_calls, 0); + assert!(alloc.is_clean()); +} + +#[test] +fn test_grow_reuses_allocation() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..(INLINE_SIZE + 1) as i32 { + vec.push(i); + } + assert_eq!(alloc.stats().alloc_calls, 1); + + // growing an already-spilled vec must realloc, not allocate a fresh block + vec.reserve(INLINE_SIZE * 4); + + assert_eq!(alloc.stats().alloc_calls, 1); + assert_eq!(alloc.stats().grow_calls, 1); + assert_eq!(alloc.stats().dealloc_calls, 0); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_shrink_to_fit_reuses_allocation() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::with_capacity_in(64, &alloc); + vec.extend(0..8); // still above `INLINE_SIZE` + + vec.shrink_to_fit(); + + let s = alloc.stats(); + assert_eq!(s.shrink_calls, 1); + assert_eq!(s.alloc_calls, 1); + assert_eq!(s.dealloc_calls, 0); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_shrink_to_unspills() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::with_capacity_in(16, &alloc); + vec.extend(0..2); + assert!(vec.spilled()); + + // should be at most 2, so it should inline again. + vec.shrink_to(0); + + assert!(!vec.spilled()); + assert_eq!(vec.as_slice(), &[0, 1]); + assert_eq!(alloc.stats().alloc_calls, 1); + assert_eq!(alloc.stats().dealloc_calls, 1); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_shrink_to_reallocates() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::with_capacity_in(64, &alloc); + vec.extend(0..8); + assert!(vec.spilled()); + + // should be at most 8, so it should remain spilled and shrink. + vec.shrink_to(8); + + assert!(vec.spilled()); + assert_eq!(vec.capacity(), 8); + assert_eq!(vec.as_slice(), &[0, 1, 2, 3, 4, 5, 6, 7]); + assert_eq!(alloc.stats().shrink_calls, 1); + assert_eq!(alloc.stats().alloc_calls, 1); + assert_eq!(alloc.stats().dealloc_calls, 0); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_shrink_to_noop() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::with_capacity_in(8, &alloc); + vec.extend(0..8); + + let before = alloc.stats(); + vec.shrink_to(8); // capacity == min, so no-op + vec.shrink_to(100); // capacity < min, so no-op + let after = alloc.stats(); + + assert_eq!(before.alloc_calls, after.alloc_calls); + assert_eq!(before.dealloc_calls, after.dealloc_calls); + assert_eq!(after.shrink_calls, 0); + assert_eq!(vec.capacity(), 8); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_growth_is_amortized() { + const INLINE_SIZE: usize = 4; + const N: usize = 1024; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..N as i32 { + vec.push(i); + } + assert_eq!(vec.len(), N); + + // only the initial spill allocates; every later growth reallocates + assert_eq!(alloc.stats().alloc_calls, 1); + // doubling growth here means about less than 16 allocations + assert!(alloc.stats().grow_calls <= 16); + + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_clone_allocates_and_clone_from_reuses() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + vec.extend(0..8); + assert!(vec.spilled()); + assert_eq!(alloc.stats().alloc_calls, 1); + + // cloning a spilled vec allocates + let clone = vec.clone(); + assert_eq!(clone.as_slice(), vec.as_slice()); + assert_eq!(alloc.stats().alloc_calls, 2); + + // clone_from into a vec with enough capacity must not allocate + let mut target = SmallVec::::with_capacity_in(8, &alloc); + let before = alloc.stats().alloc_calls; + target.clone_from(&vec); + assert_eq!(target.as_slice(), vec.as_slice()); + assert_eq!(alloc.stats().alloc_calls, before); + + drop(clone); + drop(target); + drop(vec); + assert!(alloc.is_clean()); +} + +#[test] +fn test_drop_correctness() { + use core::cell::Cell; + + struct DropCounter<'a>(&'a Cell); + impl Drop for DropCounter<'_> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1); + } + } + + const INLINE_SIZE: usize = 2; + + let alloc = TestAlloc::new(); + let counter = Cell::new(0); + + { + let mut vec = SmallVec::, INLINE_SIZE, &TestAlloc>::new_in(&alloc); + for _ in 0..6 { + vec.push(DropCounter(&counter)); + } + assert!(vec.spilled()); + assert_eq!(counter.get(), 0); + + vec.truncate(4); + assert_eq!(counter.get(), 2); + + vec.clear(); + assert_eq!(counter.get(), 6); + } + + assert_eq!(counter.get(), 6); + assert!(alloc.is_clean()); +} + +#[test] +fn test_alignment_is_respected() { + #[repr(align(64))] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct Aligned(u8); + + const INLINE_SIZE: usize = 2; + + let alloc = TestAlloc::new(); + + let mut vec = SmallVec::::new_in(&alloc); + for _ in 0..(INLINE_SIZE + 1) { + vec.push(Aligned(1)); + } + assert!(vec.spilled()); + assert_eq!(vec[0].0, 1); + + assert_eq!(alloc.stats().alloc_align, 64); + + drop(vec); + assert_eq!(alloc.stats().dealloc_align, 64); + assert!(alloc.is_clean()); +} + +#[test] +#[should_panic] +fn test_into_parts_with_allocator_inline_panics() { + const INLINE_SIZE: usize = 4; + + let alloc = TestAlloc::new(); + + let vec = SmallVec::::new_in(&alloc); + let _ = vec.into_parts_with_allocator(); +} + +#[test] +fn test_try_with_capacity_in_failure() { + const INLINE_SIZE: usize = 4; + + let alloc = FailingAlloc::new(0); + + let result = SmallVec::::try_with_capacity_in(8, &alloc); + assert_matches!(result, Err(SmallVecError::AllocationError(_))); + assert!(alloc.is_clean()); +} + +#[test] +fn test_allocation_failure_preserves_vec() { + const INLINE_SIZE: usize = 4; + + // allow the spill, fail the next allocation + let alloc = FailingAlloc::new(1); + + let mut vec = SmallVec::::new_in(&alloc); + for i in 0..(INLINE_SIZE + 1) as i32 { + vec.push(i); + } + assert!(vec.spilled()); + assert_eq!(alloc.stats().alloc_calls, 1); + + // both entry points must fail without touching the vec + assert_matches!(vec.try_grow(64), Err(SmallVecError::AllocationError(_))); + assert_matches!(vec.try_reserve(100), Err(SmallVecError::AllocationError(_))); + + assert_eq!(vec.as_slice(), &[0, 1, 2, 3, 4]); + assert!(vec.spilled()); + assert_eq!(alloc.stats().alloc_calls, 1); + + drop(vec); + assert!(alloc.is_clean()); +} diff --git a/tests/main.rs b/tests/main.rs index 22ecfb9f..5cae8385 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -773,20 +773,21 @@ fn shrink_after_from_empty_vec() { #[test] #[should_panic] -fn into_raw_parts_inline() { +fn into_parts_inline() { let v: SmallVec = SmallVec::from([1, 2, 3]); - v.into_raw_parts(); + v.into_parts(); } #[test] -fn into_raw_parts_heap() { +fn into_parts_heap() { let v: SmallVec = SmallVec::from([1, 2, 3]); - let (ptr, length, capacity) = v.into_raw_parts(); + let (ptr, length, capacity) = v.into_parts(); // It should be safe to create a standard `Vec` from the result. // SAFETY: allocated using `Global`, no changes to the element type. unsafe { - Vec::from_raw_parts(ptr, length, capacity); + // `allocator_api2::vec::Vec` doesn't expose `from_parts` yet + Vec::from_raw_parts(ptr.as_ptr(), length, capacity); } } From 56a755c599e243f71ce76b3f92b16ab30148cafe Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 10:09:41 +0100 Subject: [PATCH 2/9] fix: small name fix --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a28428b..b55cec17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -435,7 +435,7 @@ impl SmallVec { /// ``` #[inline] pub fn into_parts(self) -> (NonNull, usize, usize) { - let (ptr, len, capacity, _) = self.into_parts_with_allocator(); + let (ptr, len, capacity, _) = self.into_parts_with_alloc(); (ptr, len, capacity) } } @@ -1409,7 +1409,7 @@ impl SmallVec { /// # use smallvec::SmallVec; /// let v: SmallVec = SmallVec::from([-1, 0, 1]); /// - /// let (ptr, length, cap, alloc) = v.into_parts_with_allocator(); + /// let (ptr, length, cap, alloc) = v.into_parts_with_alloc(); /// /// let rebuilt = unsafe { /// // We can now make changes to the components, such as @@ -1421,7 +1421,7 @@ impl SmallVec { /// assert_eq!(rebuilt, [4294967295, 0, 1]); /// ``` #[inline] - pub fn into_parts_with_allocator(self) -> (NonNull, usize, usize, A) { + pub fn into_parts_with_alloc(self) -> (NonNull, usize, usize, A) { #[cold] #[inline(never)] #[track_caller] From 93822f7d03815b1ebf629df1e6398565071cc817 Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 10:14:57 +0100 Subject: [PATCH 3/9] fix: small name fix, missed the tests --- tests/allocation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/allocation.rs b/tests/allocation.rs index 18913d71..5038e9f5 100644 --- a/tests/allocation.rs +++ b/tests/allocation.rs @@ -247,7 +247,7 @@ fn test_moving_does_not_deallocate() { let alloc_b = TestAlloc::new(); let vec = SmallVec::::with_capacity_in(INLINE_SIZE + 1, &alloc_a); - let (ptr, len, cap, allocator) = vec.into_parts_with_allocator(); + let (ptr, len, cap, allocator) = vec.into_parts_with_alloc(); // moving to a different inline size must not deallocate let vec = unsafe { @@ -575,13 +575,13 @@ fn test_alignment_is_respected() { #[test] #[should_panic] -fn test_into_parts_with_allocator_inline_panics() { +fn test_into_parts_with_alloc_inline_panics() { const INLINE_SIZE: usize = 4; let alloc = TestAlloc::new(); let vec = SmallVec::::new_in(&alloc); - let _ = vec.into_parts_with_allocator(); + let _ = vec.into_parts_with_alloc(); } #[test] From 8de655380389c7c99d28a139aa2321be1cb39945 Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 10:16:54 +0100 Subject: [PATCH 4/9] practices: fix cargo fmt for encase impl --- Cargo.toml | 1 + src/lib.rs | 130 +++++++++++++++++++++++++++++------------------------ 2 files changed, 72 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c38b5f6..124c5aa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ rayon = ["std", "dep:rayon"] serde = ["dep:serde_core"] specialization = [] std = [] +default = ["encase"] [dependencies] allocator-api2 = { version = "0.4", optional = true, features = ["alloc"], default-features = false } diff --git a/src/lib.rs b/src/lib.rs index b55cec17..204ad9e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,6 @@ use defmt::{ write as dewrite }; pub use errors::SmallVecError; -use core::ptr; #[cfg(feature = "std")] use std::io; use { @@ -78,6 +77,7 @@ use { size_of }, ptr::{ + self, NonNull, copy, copy_nonoverlapping, @@ -266,8 +266,8 @@ impl SmallVec { } } - /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, and - /// a capacity. + /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, + /// and a capacity. /// /// # Safety /// @@ -278,22 +278,24 @@ impl SmallVec { /// As such, if it belonged to a `SmallVec`, it must have been spilled to /// the heap. /// - `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) + /// (`T` having a less strict alignment is not sufficient, the alignment + /// really needs to be equal to satisfy the [`dealloc`] requirement that + /// memory must be allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in + /// bytes) needs to be the same size as the pointer was allocated with. + /// (Because similar to alignment, [`dealloc`] must be called with the + /// same layout `size`.) /// - `length` needs to be less than or equal to `capacity`. - /// - The first `length` values must be properly initialized values of type `T`. + /// - The first `length` values must be properly initialized values of type + /// `T`. /// - `capacity` needs to be the capacity that the pointer was allocated /// with. - /// - The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`std::pointer::offset`]. + /// - The allocated size in bytes must be no larger than `isize::MAX`. See + /// the safety documentation of [`std::pointer::offset`]. /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via any `SmallVec`. Other allocation sources are allowed - /// if the invariants are upheld. + /// These requirements are always upheld by any `ptr` that has been + /// allocated via any `SmallVec`. Other allocation sources are + /// allowed if the invariants are upheld. /// /// Additionally, `capacity` must be greater than `N`; that is, the new /// `SmallVec` must need to spill over into heap allocated storage. This @@ -302,17 +304,17 @@ impl SmallVec { /// Violating these may cause problems like corrupting the allocator’s /// internal data structures. /// - /// The ownership of `ptr` is effectively transferred to the `SmallVec` - /// which may then deallocate, reallocate or change the contents of memory - /// pointed to by the pointer at will. Ensure that nothing else uses the - /// pointer after calling this function. + /// The ownership of `ptr` is effectively transferred to the `SmallVec` which may then deallocate, reallocate or change the contents of + /// memory pointed to by the pointer at will. Ensure that nothing else + /// uses the pointer after calling this function. /// /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory /// /// # Examples /// - /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, a capacity, - /// and an allocator. + /// Creates a `SmallVec` directly from a `NonNull` pointer, a + /// length, a capacity, and an allocator. /// /// # Safety /// @@ -323,21 +325,24 @@ impl SmallVec { /// If it belonged to a `SmallVec`, it must have been spilled to /// the heap. /// - `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) + /// (`T` having a less strict alignment is not sufficient, the alignment + /// really needs to be equal to satisfy the [`dealloc`] requirement that + /// memory must be allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in + /// bytes) needs to be the same size as the pointer was allocated with. + /// (Because similar to alignment, [`dealloc`] must be called with the + /// same layout `size`.) /// - `length` needs to be less than or equal to `capacity`. - /// - The first `length` values must be properly initialized values of type `T`. - /// - `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// - The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`std::pointer::offset`]. + /// - The first `length` values must be properly initialized values of type + /// `T`. + /// - `capacity` needs to [*fit*] the layout size that the pointer was + /// allocated with. + /// - The allocated size in bytes must be no larger than `isize::MAX`. See + /// the safety documentation of [`std::pointer::offset`]. /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via any `SmallVec`. Other allocation sources are allowed - /// if the invariants are upheld. + /// These requirements are always upheld by any `ptr` that has been + /// allocated via any `SmallVec`. Other allocation sources are + /// allowed if the invariants are upheld. /// /// Additionally, `capacity` must be greater than `N`; that is, the new /// `SmallVec` must need to spill over into heap allocated storage. This @@ -346,10 +351,10 @@ impl SmallVec { /// Violating these may cause problems like corrupting the allocator’s /// internal data structures. /// - /// The ownership of `ptr` is effectively transferred to the `SmallVec` - /// which may then deallocate, reallocate or change the contents of memory - /// pointed to by the pointer at will. Ensure that nothing else uses the - /// pointer after calling this function. + /// The ownership of `ptr` is effectively transferred to the `SmallVec` which may then deallocate, reallocate or change the contents of + /// memory pointed to by the pointer at will. Ensure that nothing else + /// uses the pointer after calling this function. /// /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory /// [*fit*]: std::alloc::Allocator#memory-fitting @@ -1292,8 +1297,8 @@ impl SmallVec { } } - /// Creates a `SmallVec` directly from a `NonNull` pointer, a length, a capacity, - /// and an allocator. + /// Creates a `SmallVec` directly from a `NonNull` pointer, a + /// length, a capacity, and an allocator. /// /// # Safety /// @@ -1304,21 +1309,24 @@ impl SmallVec { /// If it belonged to a `SmallVec`, it must have been spilled to /// the heap. /// - `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// - The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) + /// (`T` having a less strict alignment is not sufficient, the alignment + /// really needs to be equal to satisfy the [`dealloc`] requirement that + /// memory must be allocated and deallocated with the same layout.) + /// - The size of `T` times the `capacity` (i.e. the allocated size in + /// bytes) needs to be the same size as the pointer was allocated with. + /// (Because similar to alignment, [`dealloc`] must be called with the + /// same layout `size`.) /// - `length` needs to be less than or equal to `capacity`. - /// - The first `length` values must be properly initialized values of type `T`. - /// - `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// - The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`std::pointer::offset`]. + /// - The first `length` values must be properly initialized values of type + /// `T`. + /// - `capacity` needs to [*fit*] the layout size that the pointer was + /// allocated with. + /// - The allocated size in bytes must be no larger than `isize::MAX`. See + /// the safety documentation of [`std::pointer::offset`]. /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via any `SmallVec`. Other allocation sources are allowed - /// if the invariants are upheld. + /// These requirements are always upheld by any `ptr` that has been + /// allocated via any `SmallVec`. Other allocation sources are + /// allowed if the invariants are upheld. /// /// Additionally, `capacity` must be greater than `N`; that is, the new /// `SmallVec` must need to spill over into heap allocated storage. This @@ -1327,10 +1335,10 @@ impl SmallVec { /// Violating these may cause problems like corrupting the allocator’s /// internal data structures. /// - /// The ownership of `ptr` is effectively transferred to the `SmallVec` - /// which may then deallocate, reallocate or change the contents of memory - /// pointed to by the pointer at will. Ensure that nothing else uses the - /// pointer after calling this function. + /// The ownership of `ptr` is effectively transferred to the `SmallVec` which may then deallocate, reallocate or change the contents of + /// memory pointed to by the pointer at will. Ensure that nothing else + /// uses the pointer after calling this function. /// /// [*currently allocated*]: std::alloc::Allocator#currently-allocated-memory /// [*fit*]: std::alloc::Allocator#memory-fitting @@ -1442,7 +1450,7 @@ impl SmallVec { NonNull::new_unchecked(me.as_mut_ptr()), me.len(), me.capacity(), - ptr::read(&me.allocator), + ptr::read(&me.allocator) ) } } @@ -2093,4 +2101,8 @@ impl Format for SmallVec { } #[cfg(feature = "encase")] -encase::rts_array::impl_rts_array!(SmallVec; (T, const N: usize, A: Allocator); using len truncate); +encase::rts_array::impl_rts_array!( + SmallVec; + (T, const N: usize, A: Allocator); + using len truncate +); From 2851fc162d4f5e5ddcf4fbbf1ae96b4de48d37d2 Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 13:05:01 +0100 Subject: [PATCH 5/9] fix: remove default feature 'encase' again Signed-off-by: Pedro Nobre --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 124c5aa8..6c38b5f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ rayon = ["std", "dep:rayon"] serde = ["dep:serde_core"] specialization = [] std = [] -default = ["encase"] [dependencies] allocator-api2 = { version = "0.4", optional = true, features = ["alloc"], default-features = false } From a956f0667468c128b9d184486c8a48e3386d7749 Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 7 Oct 2026 13:07:42 +0100 Subject: [PATCH 6/9] test: removed redundant `allocator-api2` feature requirement from `allocation.rs` Signed-off-by: Pedro Nobre --- tests/allocation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/allocation.rs b/tests/allocation.rs index 5038e9f5..6e539217 100644 --- a/tests/allocation.rs +++ b/tests/allocation.rs @@ -1,5 +1,5 @@ #![cfg(all( - any(feature = "allocator-api", feature = "allocator-api2"), + feature = "allocator-api", feature = "std" // `std` is necessary for the base `System` allocator ))] From 1e7b2a23d8b3e5b434d1d4cb6991e5420836d11b Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Thu, 8 Oct 2026 17:16:30 +0100 Subject: [PATCH 7/9] test: on `allocation`, swap `std`'s `System` for `alloc`'s `Global`. stop depending on `std`. switch `TestAlloc`'s `RefCell` for `Cell` --- Cargo.toml | 1 + tests/allocation.rs | 67 ++++++++++++++++++++++----------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c38b5f6..fa0774ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ rayon = ["std", "dep:rayon"] serde = ["dep:serde_core"] specialization = [] std = [] +default = ["allocator-api"] [dependencies] allocator-api2 = { version = "0.4", optional = true, features = ["alloc"], default-features = false } diff --git a/tests/allocation.rs b/tests/allocation.rs index 6e539217..ffabfe68 100644 --- a/tests/allocation.rs +++ b/tests/allocation.rs @@ -1,9 +1,5 @@ -#![cfg(all( - feature = "allocator-api", - feature = "std" // `std` is necessary for the base `System` allocator -))] +#![cfg(feature = "allocator-api")] -#[cfg(not(feature = "allocator-api2"))] extern crate alloc; #[cfg(not(feature = "allocator-api2"))] use alloc::alloc::{ @@ -20,20 +16,15 @@ use allocator_api2::alloc::{ #[cfg(feature = "allocator-api2")] use std::alloc::Allocator as _; use { + alloc::alloc::Global, core::{ - cell::{ - Cell, - RefCell - }, + assert_matches, + cell::Cell, ptr::NonNull }, smallvec::{ SmallVec, SmallVecError - }, - std::{ - alloc::System, - assert_matches } }; @@ -50,7 +41,7 @@ struct AllocStats { #[derive(Clone, Debug, Default)] struct TestAlloc { - stats: RefCell + stats: Cell } impl TestAlloc { @@ -61,32 +52,36 @@ impl TestAlloc { #[inline] fn stats(&self) -> AllocStats { - *self.stats.borrow() + self.stats.get() } #[inline] fn is_clean(&self) -> bool { - let s = self.stats.borrow(); + let s = self.stats.get(); s.alloc_calls == s.dealloc_calls && s.allocated_bytes == 0 } } unsafe impl Allocator for TestAlloc { fn allocate(&self, layout: Layout) -> Result, AllocError> { - let ptr = System.allocate(layout).map_err(|_| AllocError)?; - let mut s = self.stats.borrow_mut(); - s.alloc_calls += 1; - s.allocated_bytes += layout.size() as isize; - s.alloc_align = layout.align(); + let ptr = Global.allocate(layout).map_err(|_| AllocError)?; + self.stats.update(|mut s| { + s.alloc_calls += 1; + s.allocated_bytes += layout.size() as isize; + s.alloc_align = layout.align(); + s + }); Ok(ptr) } unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - let mut s = self.stats.borrow_mut(); - s.dealloc_calls += 1; - s.allocated_bytes -= layout.size() as isize; - s.dealloc_align = layout.align(); - unsafe { System.deallocate(ptr, layout) }; + self.stats.update(|mut s| { + s.dealloc_calls += 1; + s.allocated_bytes -= layout.size() as isize; + s.dealloc_align = layout.align(); + s + }); + unsafe { Global.deallocate(ptr, layout) }; } unsafe fn grow( @@ -95,10 +90,12 @@ unsafe impl Allocator for TestAlloc { old: Layout, new: Layout ) -> Result, AllocError> { - let out = unsafe { System.grow(ptr, old, new) }.map_err(|_| AllocError)?; - let mut s = self.stats.borrow_mut(); - s.grow_calls += 1; - s.allocated_bytes += new.size() as isize - old.size() as isize; + let out = unsafe { Global.grow(ptr, old, new) }.map_err(|_| AllocError)?; + self.stats.update(|mut s| { + s.grow_calls += 1; + s.allocated_bytes += new.size() as isize - old.size() as isize; + s + }); Ok(out) } @@ -108,10 +105,12 @@ unsafe impl Allocator for TestAlloc { old: Layout, new: Layout ) -> Result, AllocError> { - let out = unsafe { System.shrink(ptr, old, new) }.map_err(|_| AllocError)?; - let mut s = self.stats.borrow_mut(); - s.shrink_calls += 1; - s.allocated_bytes -= old.size() as isize - new.size() as isize; + let out = unsafe { Global.shrink(ptr, old, new) }.map_err(|_| AllocError)?; + self.stats.update(|mut s| { + s.shrink_calls += 1; + s.allocated_bytes -= old.size() as isize - new.size() as isize; + s + }); Ok(out) } } From c68c16acfdc703373f5feefe29b463e6c6819043 Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Thu, 8 Oct 2026 17:30:54 +0100 Subject: [PATCH 8/9] fix: revert default feature Signed-off-by: Pedro Nobre --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fa0774ce..6c38b5f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ rayon = ["std", "dep:rayon"] serde = ["dep:serde_core"] specialization = [] std = [] -default = ["allocator-api"] [dependencies] allocator-api2 = { version = "0.4", optional = true, features = ["alloc"], default-features = false } From 64ca4a206da2808ab8efe5f7060d1d351c65169d Mon Sep 17 00:00:00 2001 From: Pedro Nobre Date: Wed, 21 Oct 2026 12:36:47 +0100 Subject: [PATCH 9/9] refactor: remove core::ptr::self use --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 204ad9e6..df2359fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,6 @@ use { size_of }, ptr::{ - self, NonNull, copy, copy_nonoverlapping, @@ -1450,7 +1449,7 @@ impl SmallVec { NonNull::new_unchecked(me.as_mut_ptr()), me.len(), me.capacity(), - ptr::read(&me.allocator) + (&raw const me.allocator).read() ) } }