From 69e3047506dc0680e2bba51e7d6df6858492145d Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 7 Mar 2022 21:02:41 -0800 Subject: [PATCH 1/6] initial updates for owned storage, reallocating vec Signed-off-by: Andrew Whitehead --- benches/maps.rs | 6 +- src/collections/list_map.rs | 2 + src/collections/mod.rs | 5 + src/collections/pool/direct.rs | 2 + src/collections/pool/packed.rs | 2 + src/collections/vec.rs | 342 +++++++++++++++++++-------------- src/storage.rs | 185 +++++++++++++++++- 7 files changed, 387 insertions(+), 157 deletions(-) diff --git a/benches/maps.rs b/benches/maps.rs index 0d3f3f4..b8c0726 100644 --- a/benches/maps.rs +++ b/benches/maps.rs @@ -55,7 +55,7 @@ macro_rules! removals { #[bench] fn $fnn(b: &mut Bencher) { let mut rng = SmallRng::seed_from_u64(0x5432_1012_3454_3210); - let mut pairs = coca::AllocVec::<(u32, u32), usize>::with_capacity($n); + let mut pairs = coca::collections::AllocVec::<(u32, u32), usize>::with_capacity($n); for _ in 0..$n { pairs.push((rng.next_u32(), rng.next_u32())); } @@ -77,7 +77,7 @@ macro_rules! removals { mod unordered { use super::*; - use coca::AllocVec; + use coca::collections::AllocVec; use std::collections::HashMap as StdHashMap; #[allow(unconditional_recursion)] // false positive! @@ -167,7 +167,7 @@ mod unordered { mod ordered { use super::*; - use coca::AllocVec; + use coca::collections::AllocVec; use std::collections::BTreeMap; impl Map for BTreeMap diff --git a/src/collections/list_map.rs b/src/collections/list_map.rs index a0f0a94..ca3fe92 100644 --- a/src/collections/list_map.rs +++ b/src/collections/list_map.rs @@ -14,6 +14,8 @@ use self::Entry::{Occupied, Vacant}; /// The [`LayoutSpec`] for a [`ListMap`]. pub struct ListMapLayout(PhantomData<(K, V)>); impl LayoutSpec for ListMapLayout { + type Item = (K, V); + fn layout_with_capacity(items: usize) -> Result { let keys_array = Layout::array::(items)?; let values_array = Layout::array::(items)?; diff --git a/src/collections/mod.rs b/src/collections/mod.rs index ce0ee4d..1c64658 100644 --- a/src/collections/mod.rs +++ b/src/collections/mod.rs @@ -596,6 +596,11 @@ pub type ArenaVec<'a, T, I = usize> = Vec>, I /// ``` pub type AllocVec = Vec>, I>; +#[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] +/// A heap-allocated vector which automatically reallocates. +pub type ReallocVec = Vec>, I>; + /// A vector using an inline array for storage. /// /// # Examples diff --git a/src/collections/pool/direct.rs b/src/collections/pool/direct.rs index b431f6b..8fee82e 100644 --- a/src/collections/pool/direct.rs +++ b/src/collections/pool/direct.rs @@ -21,6 +21,8 @@ union Slot { /// The [`LayoutSpec`] for a [`DirectPool`]. pub struct DirectPoolLayout(PhantomData<(T, H)>); impl LayoutSpec for DirectPoolLayout { + type Item = (T, H::Index, u32); + fn layout_with_capacity(items: usize) -> Result { let item_array = Layout::array::>(items)?; let gen_count_array = Layout::array::(items)?; diff --git a/src/collections/pool/packed.rs b/src/collections/pool/packed.rs index 1484515..8c4090d 100644 --- a/src/collections/pool/packed.rs +++ b/src/collections/pool/packed.rs @@ -17,6 +17,8 @@ use crate::storage::{Capacity, LayoutSpec, Storage}; /// The [`LayoutSpec`] for a [`PackedPool`]. pub struct PackedPoolLayout(PhantomData<(T, H)>); impl LayoutSpec for PackedPoolLayout { + type Item = (T, H, u32, H::Index); + fn layout_with_capacity(items: usize) -> Result { let values_array = Layout::array::(items)?; let handles_array = Layout::array::(items)?; diff --git a/src/collections/vec.rs b/src/collections/vec.rs index 4025a93..ae40522 100644 --- a/src/collections/vec.rs +++ b/src/collections/vec.rs @@ -10,8 +10,8 @@ //! (c) 2019 by Daniel "Lokathor" Gee). use crate::storage::{ - buffer_too_large_for_index_type, mut_ptr_at_index, normalize_range, ptr_at_index, ArrayLayout, - Capacity, InlineStorage, Storage, + buffer_too_large_for_index_type, cast_capacity, mut_ptr_at_index, normalize_range, + ptr_at_index, ArrayLayout, Capacity, DefaultStorage, OwnedStorage, Storage, }; use crate::CapacityError; @@ -46,7 +46,7 @@ impl>, I: Capacity> From for Vec { } Vec { - len: I::from_usize(0), + len: I::ZERO, buf, elem: PhantomData, } @@ -368,7 +368,9 @@ impl>, I: Capacity> Vec { #[inline] pub fn try_push(&mut self, value: T) -> Result<(), T> { if self.is_full() { - return Err(value); + if let Err(_) = self.try_grow(None) { + return Err(value); + } } let len = self.len(); @@ -436,7 +438,7 @@ impl>, I: Capacity> Vec { /// Equivalent to `s.truncate(0)`. #[inline] pub fn clear(&mut self) { - self.truncate(I::from_usize(0)); + self.truncate(I::ZERO); } /// Swaps two elements in the vector. @@ -512,7 +514,7 @@ impl>, I: Capacity> Vec { #[cold] #[inline(never)] fn assert_failed() -> ! { - panic!("vector is already at capacity") + panic!("vector is at capacity and cannot be expanded") } let result = self.try_insert(index, element); @@ -550,7 +552,9 @@ impl>, I: Capacity> Vec { } if self.is_full() { - return Err(element); + if let Err(_) = self.try_grow(None) { + return Err(element); + } } let idx = index.as_usize(); @@ -855,6 +859,19 @@ impl>, I: Capacity> Vec { target_end: end, } } + + #[inline] + /// Try to expand the backing Storage if supported. + fn try_grow(&mut self, min_capacity: Option) -> Result<(), CapacityError> { + let mut buf = self.buf.try_grow::(min_capacity)?; + let src_ptr = self.as_ptr(); + let dst_ptr = buf.get_mut_ptr().cast::(); + unsafe { + ptr::copy_nonoverlapping(src_ptr, dst_ptr, self.len()); + } + self.buf = buf; // drops previous buffer + Ok(()) + } } impl>, I: Capacity> Vec { @@ -878,7 +895,7 @@ impl>, I: Capacity> Vec { pub fn try_extend_from_slice(&mut self, other: &[T]) -> crate::Result<()> { let new_len = self.len() + other.len(); if new_len > self.capacity() { - return CapacityError::new(); + self.try_grow(Some(new_len))?; } unsafe { @@ -936,7 +953,7 @@ impl>, I: Capacity> Vec { let count = src.len(); let new_len = self.len() + count; if new_len > self.capacity() { - return CapacityError::new(); + self.try_grow(Some(new_len))?; } let idx = idx.as_usize(); @@ -996,7 +1013,7 @@ impl>, I: Capacity> Vec { let count = end - start; let new_len = self.len() + count; if new_len > self.capacity() { - return CapacityError::new(); + self.try_grow(Some(new_len))?; } unsafe { @@ -1065,8 +1082,9 @@ impl>, I: Capacity> Vec { } } else { let extra_space_needed = src_count - dst_count; - if self.len() + extra_space_needed > self.capacity() { - return CapacityError::new(); + let min_cap = self.len() + extra_space_needed; + if min_cap > self.capacity() { + self.try_grow(Some(min_cap))?; } unsafe { @@ -1104,6 +1122,127 @@ impl>, I: Capacity> Vec { } } +impl>, I: Capacity> Vec { + const UNINIT: Self = Vec { + len: I::ZERO, + buf: S::UNINIT, + elem: PhantomData, + }; + + /// Constructs a new, empty `Vec`. + /// + /// # Panics + /// Panics if `C` cannot be represented as a value of type `I`. + /// + /// # Examples + /// ``` + /// let vec = coca::collections::InlineVec::::new(); + /// assert_eq!(vec.capacity(), 6); + /// assert_eq!(vec.len(), 0); + /// ``` + #[inline] + pub fn new() -> Self { + if !S::supported_capacity::() { + buffer_too_large_for_index_type::(); + } + Self::UNINIT + } +} + +impl>, I: Capacity> Vec { + /// Constructs a new, empty Vec with the specified capacity. + /// + /// # Panics + /// Panics if the specified capacity cannot be represented by a `usize` + /// or if the capacity exceeds the maximum supported by the backing Storage. + pub fn with_capacity(capacity: I) -> Self { + if !S::supported_capacity::() { + buffer_too_large_for_index_type::(); + } + Vec { + len: I::ZERO, + buf: S::try_with_capacity(capacity.as_usize()) + .expect("exceeded maximum storage capacity"), + elem: PhantomData, + } + } +} + +impl>, I: Capacity> Default for Vec { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl>, I: Capacity> core::clone::Clone for Vec { + fn clone(&self) -> Self { + let mut ret = Self::with_capacity(self.len); + ret.clone_from(self); + ret + } + + fn clone_from(&mut self, source: &Self) { + self.clear(); + self.extend(source.iter()) + } +} + +impl>, I: Capacity> From<&[T]> for Vec { + fn from(source: &[T]) -> Self { + let cap = cast_capacity(source.len()); + let mut ret = Self::with_capacity(cap); + ret.extend(source.iter().cloned()); + ret + } +} + +impl>, I: Capacity> From<&mut [T]> for Vec { + fn from(source: &mut [T]) -> Self { + let cap = cast_capacity(source.len()); + let mut ret = Self::with_capacity(cap); + ret.extend(source.iter().cloned()); + ret + } +} + +impl>, I: Capacity, const N: usize> From<&[T; N]> + for Vec +{ + fn from(source: &[T; N]) -> Self { + let cap = cast_capacity(source.len()); + let mut ret = Self::with_capacity(cap); + ret.extend(source.iter().cloned()); + ret + } +} + +impl>, I: Capacity, const N: usize> From<[T; N]> + for Vec +{ + fn from(source: [T; N]) -> Self { + let cap = cast_capacity(N); + let mut buf = Self::with_capacity(cap); + buf.extend(core::iter::IntoIterator::into_iter(source)); + buf + } +} + +impl>, I: Capacity> core::iter::FromIterator for Vec { + /// Creates a vector from an iterator. + /// + /// # Panics + /// Panics if the iterator yields more elements than the maximum storage capacity. + fn from_iter>(iter: It) -> Self { + let iter = iter.into_iter(); + let (min_len, max_len) = iter.size_hint(); + let cap = cast_capacity(max_len.unwrap_or(min_len)); + let mut ret = Self::with_capacity(cap); + ret.extend(iter); + ret + } +} + impl>, I: Capacity> core::ops::Deref for Vec { type Target = [T]; fn deref(&self) -> &[T] { @@ -1378,7 +1517,7 @@ impl>, I: Capacity> IntoIter for Vec { core::mem::forget(self); IntoIterator { - start: I::from_usize(0), + start: I::ZERO, end, buf, elems: PhantomData, @@ -1643,126 +1782,6 @@ impl crate::collections::SliceVec<'_, T, I> { } } -#[cfg(feature = "alloc")] -#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -impl crate::collections::AllocVec { - /// Constructs a new, empty `AllocVec` with the specified capacity. - /// - /// # Panics - /// Panics if the specified capacity cannot be represented by a `usize`. - pub fn with_capacity(capacity: I) -> Self { - let cap = capacity.as_usize(); - if capacity != I::from_usize(cap) { - buffer_too_large_for_index_type::(); - } - - Vec { - len: I::from_usize(0), - buf: crate::storage::AllocStorage::with_capacity(cap), - elem: PhantomData, - } - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -impl Clone for crate::collections::AllocVec { - fn clone(&self) -> Self { - let mut result = Self::with_capacity(I::from_usize(self.capacity())); - result.extend(self.iter().cloned()); - result - } -} - -impl Vec, I> { - /// Constructs a new, empty `Vec` backed by an inline array. - /// - /// # Panics - /// Panics if `C` cannot be represented as a value of type `I`. - /// - /// # Examples - /// ``` - /// let vec = coca::collections::InlineVec::::new(); - /// assert_eq!(vec.capacity(), 6); - /// assert_eq!(vec.len(), 0); - /// ``` - #[inline] - pub fn new() -> Self { - if C > I::MAX_REPRESENTABLE { - buffer_too_large_for_index_type::(); - } - - Vec { - len: I::from_usize(0), - buf: unsafe { MaybeUninit::uninit().assume_init() }, - elem: PhantomData, - } - } -} - -impl Default for Vec, I> { - fn default() -> Self { - Self::new() - } -} - -impl core::clone::Clone for Vec, I> { - fn clone(&self) -> Self { - let mut ret = Self::new(); - ret.clone_from(self); - ret - } - - fn clone_from(&mut self, source: &Self) { - self.clear(); - for next in source { - self.push(next.clone()); - } - } -} - -impl From<&[T]> for Vec, I> { - fn from(source: &[T]) -> Self { - if C > I::MAX_REPRESENTABLE { - buffer_too_large_for_index_type::(); - } - - assert!( - source.len() <= C, - "source should not have more than {} elements (has {})", - C, - source.len() - ); - - let mut ret = Self::new(); - for next in source { - ret.push(next.clone()); - } - ret - } -} - -impl From<&mut [T]> for Vec, I> { - fn from(source: &mut [T]) -> Self { - if C > I::MAX_REPRESENTABLE { - buffer_too_large_for_index_type::(); - } - - assert!( - source.len() <= C, - "source should not have more than {} elements (has {})", - C, - source.len() - ); - - let mut ret = Self::new(); - for next in source { - ret.push(next.clone()); - } - ret - } -} - impl PartialEq> for [V; N] where V: PartialEq, @@ -1787,20 +1806,6 @@ where } } -impl core::iter::FromIterator - for Vec, I> -{ - /// Creates a vector backed by an inline array from an iterator. - /// - /// # Panics - /// Panics if the iterator yields more than `N` elements. - fn from_iter>(iter: It) -> Self { - let mut result = Self::new(); - result.extend(iter); - result - } -} - #[cfg(test)] mod tests { use super::*; @@ -1910,4 +1915,47 @@ mod tests { } } } + + #[cfg(feature = "alloc")] + #[test] + fn alloc_vec() { + use crate::collections::AllocVec; + use core::iter::FromIterator; + + let mut v = AllocVec::::with_capacity(32); + v.extend([1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(v.capacity(), 32); + + let v = AllocVec::::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(v.capacity(), 10); + + let mut v = AllocVec::::with_capacity(1); + v.push(1); + assert_eq!(v.try_push(2), Err(2)); + + let v = AllocVec::::from_iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(v.capacity(), 10); + } + + #[cfg(feature = "alloc")] + #[test] + fn realloc_vec() { + use crate::collections::ReallocVec; + use core::iter::FromIterator; + + let _v1 = ReallocVec::::new(); + let _v2 = ReallocVec::::with_capacity(32); + + let mut v = ReallocVec::::default(); + v.extend([1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(v.capacity(), 8); + v.push(9); + assert_eq!(v.capacity(), 16); + + let v = ReallocVec::::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(v.capacity(), 10); + + let v = ReallocVec::::from_iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(v.capacity(), 10); + } } diff --git a/src/storage.rs b/src/storage.rs index 8ba4088..ba9a89e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -9,6 +9,8 @@ use core::mem::MaybeUninit; use core::ops::{Range, RangeBounds}; use core::ptr::NonNull; +use crate::CapacityError; + /// Types that can be used for indexing into array-like data structures. /// /// # Safety @@ -20,6 +22,8 @@ use core::ptr::NonNull; pub unsafe trait Capacity: Copy + Debug + Eq + Hash + Ord { /// The maximum `usize` value that can safely be represented by this type. const MAX_REPRESENTABLE: usize; + /// The zero value for this type. + const ZERO: Self; /// Convert a `usize` into `Self`. fn from_usize(i: usize) -> Self; /// Convert `self` into `usize`. @@ -36,6 +40,23 @@ pub(crate) fn buffer_too_large_for_index_type() { ); } +#[cold] +#[inline(never)] +#[track_caller] +pub(crate) fn usize_exceeds_max_capacity() { + panic!( + "maximum capacity of index type {} exceeds the requested capacity", + core::any::type_name::() + ); +} + +pub(crate) fn cast_capacity(capacity: usize) -> I { + if capacity > I::MAX_REPRESENTABLE { + usize_exceeds_max_capacity::(); + } + I::from_usize(capacity) +} + pub(crate) fn normalize_range>( range: R, max_end: usize, @@ -71,6 +92,7 @@ pub(crate) fn normalize_range>( #[allow(clippy::cast_possible_truncation)] unsafe impl Capacity for u8 { const MAX_REPRESENTABLE: usize = 0xFF; + const ZERO: Self = 0u8; #[inline] fn from_usize(i: usize) -> Self { debug_assert!(>::try_into(i).is_ok()); @@ -86,6 +108,7 @@ unsafe impl Capacity for u8 { #[allow(clippy::cast_possible_truncation)] unsafe impl Capacity for u16 { const MAX_REPRESENTABLE: usize = 0xFFFF; + const ZERO: Self = 0u16; #[inline] fn from_usize(i: usize) -> Self { debug_assert!(>::try_into(i).is_ok()); @@ -101,6 +124,7 @@ unsafe impl Capacity for u16 { #[allow(clippy::cast_possible_truncation)] unsafe impl Capacity for u32 { const MAX_REPRESENTABLE: usize = 0xFFFF_FFFF; + const ZERO: Self = 0u32; #[inline] fn from_usize(i: usize) -> Self { debug_assert!(>::try_into(i).is_ok()); @@ -117,6 +141,7 @@ unsafe impl Capacity for u32 { #[allow(clippy::cast_possible_truncation)] unsafe impl Capacity for u64 { const MAX_REPRESENTABLE: usize = usize::max_value(); + const ZERO: Self = 0u64; #[inline] fn from_usize(i: usize) -> Self { debug_assert!(>::try_into(i).is_ok()); @@ -132,6 +157,7 @@ unsafe impl Capacity for u64 { unsafe impl Capacity for usize { const MAX_REPRESENTABLE: usize = usize::max_value(); + const ZERO: Self = 0usize; #[inline] fn from_usize(i: usize) -> Self { i @@ -189,6 +215,7 @@ macro_rules! index_type { unsafe impl $crate::storage::Capacity for $name { const MAX_REPRESENTABLE: usize = <$repr as $crate::storage::Capacity>::MAX_REPRESENTABLE; + const ZERO: Self = Self(<$repr as $crate::storage::Capacity>::ZERO); #[inline] #[track_caller] fn from_usize(i: usize) -> Self { @@ -213,6 +240,9 @@ macro_rules! index_type { /// Types that specify a data structure's storage layout requirements. pub trait LayoutSpec { + /// A type representing the approximate size of a single item. + type Item: Sized; + /// Constructs a [`Layout`] for a memory block capable of holding the /// specified number of items. fn layout_with_capacity(items: usize) -> Result; @@ -221,6 +251,8 @@ pub trait LayoutSpec { /// Inconstructible type to mark data structures that require an array-like storage layout. pub struct ArrayLayout(PhantomData); impl LayoutSpec for ArrayLayout { + type Item = T; + fn layout_with_capacity(items: usize) -> Result { Layout::array::(items) } @@ -251,6 +283,32 @@ pub unsafe trait Storage: Sized { /// Implementors must ensure the same value is returned every time this /// method is called throughout the block's lifetime. fn capacity(&self) -> usize; + + /// Try to grow a storage instance, reallocating when supported. + fn try_grow( + &mut self, + _min_capacity: Option, + ) -> Result { + CapacityError::new() + } + + /// Check if a Capacity exceeds the inherent bounds of the Storage. + #[inline] + fn supported_capacity() -> bool { + true + } +} + +/// An interface for storage types which are owned, not bound to an external buffer. +pub trait OwnedStorage: Storage { + /// Create a new storage buffer with a minimum capacity. + fn try_with_capacity(min_capacity: usize) -> Result; +} + +/// An interface for storage types which have a default initializer. +pub trait DefaultStorage: OwnedStorage { + /// An empty instance of the Storage. + const UNINIT: Self; } #[inline(always)] @@ -348,18 +406,75 @@ unsafe impl> Storage for &mut S { } } +#[cfg(feature = "alloc")] +/// Policy for heap storage (re)allocation. +pub unsafe trait AllocPolicy { + #[inline] + // FIXME add exact: bool parameter? + /// Try to grow an existing allocation. + fn try_grow( + _from_capacity: usize, + _min_capacity: Option, + ) -> Result<(NonNull, usize), CapacityError> { + CapacityError::new() + } +} + +#[cfg(feature = "alloc")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// An allocation policy for constant capacity storage. +pub struct NoResize; + +#[cfg(feature = "alloc")] +unsafe impl AllocPolicy for NoResize {} + +#[cfg(feature = "alloc")] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +/// An allocation policy using exponential growth. +pub struct ExpGrow; + +#[cfg(feature = "alloc")] +const fn min_non_zero_cap() -> usize { + if core::mem::size_of::() == 1 { + 8 + } else if core::mem::size_of::() <= 1024 { + 4 + } else { + 1 + } +} + +#[cfg(feature = "alloc")] +unsafe impl AllocPolicy for ExpGrow { + #[inline] + fn try_grow( + from_capacity: usize, + min_capacity: Option, + ) -> Result<(NonNull, usize), CapacityError> { + let min_cap = min_capacity.unwrap_or(min_non_zero_cap::()); + let cap = min_cap.max((from_capacity.saturating_mul(2)).min(I::MAX_REPRESENTABLE)); + if cap == from_capacity || cap < min_cap { + return CapacityError::new(); + } + + let layout = R::layout_with_capacity(cap).expect("realloc to invalid AllocStorage"); + let ptr = unsafe { alloc::alloc::alloc(layout) }; + NonNull::new(ptr).map(|ptr| (ptr, cap)).ok_or(CapacityError) + } +} + /// A fat pointer to a heap-allocated storage block conforming to a [`LayoutSpec`]. #[cfg(feature = "alloc")] #[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -pub struct AllocStorage { +pub struct AllocStorage { ptr: NonNull, cap: usize, - spec: PhantomData, + spec: PhantomData<(R, A)>, } #[cfg(feature = "alloc")] #[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -impl AllocStorage { +impl AllocStorage { /// Allocates a new storage block with the specified capacity with the /// global allocator. /// @@ -381,16 +496,19 @@ impl AllocStorage { #[cfg(feature = "alloc")] #[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -impl Drop for AllocStorage { +impl Drop for AllocStorage { fn drop(&mut self) { - let layout = R::layout_with_capacity(self.cap).expect("dropped an invalid AllocStorage"); - unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), layout) }; + if self.cap != 0 { + let layout = + R::layout_with_capacity(self.cap).expect("dropped an invalid AllocStorage"); + unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), layout) }; + } } } #[cfg(feature = "alloc")] #[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] -unsafe impl Storage for AllocStorage { +unsafe impl Storage for AllocStorage { fn get_ptr(&self) -> *const u8 { self.ptr.as_ptr() as _ } @@ -400,8 +518,43 @@ unsafe impl Storage for AllocStorage { fn capacity(&self) -> usize { self.cap } + fn try_grow( + &mut self, + min_capacity: Option, + ) -> Result { + let (ptr, cap) = A::try_grow::(self.cap, min_capacity)?; + Ok(AllocStorage { + ptr, + cap, + spec: PhantomData, + }) + } } +#[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] +impl OwnedStorage for AllocStorage { + #[inline] + fn try_with_capacity(min_capacity: usize) -> Result { + Ok(Self::with_capacity(min_capacity)) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] +impl DefaultStorage for AllocStorage { + const UNINIT: Self = AllocStorage { + ptr: NonNull::dangling(), + cap: 0, + spec: PhantomData, + }; +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] +/// Alias for a reallocating Storage type with the default behavior. +pub type ReallocStorage = AllocStorage; + #[cfg(feature = "alloc")] #[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] unsafe impl> Storage for alloc::boxed::Box { @@ -429,4 +582,22 @@ unsafe impl Storage> for InlineStorage { fn capacity(&self) -> usize { C } + fn supported_capacity() -> bool { + C <= I::MAX_REPRESENTABLE + } +} + +impl OwnedStorage> for InlineStorage { + #[inline] + fn try_with_capacity(min_capacity: usize) -> Result { + if min_capacity > C { + CapacityError::new() + } else { + unsafe { MaybeUninit::uninit().assume_init() } + } + } +} + +impl DefaultStorage> for InlineStorage { + const UNINIT: Self = unsafe { MaybeUninit::uninit().assume_init() }; } From 5ec494d5da1503f7a6227bde9413b4643a55497b Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 9 Mar 2022 15:55:17 -0800 Subject: [PATCH 2/6] replace supported_capacity with MIN_REPRESENTABLE Signed-off-by: Andrew Whitehead --- src/collections/list_map.rs | 2 ++ src/collections/pool/direct.rs | 2 ++ src/collections/pool/packed.rs | 1 + src/collections/vec.rs | 4 ++-- src/storage.rs | 22 ++++++++++++---------- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/collections/list_map.rs b/src/collections/list_map.rs index ca3fe92..c5a4494 100644 --- a/src/collections/list_map.rs +++ b/src/collections/list_map.rs @@ -849,6 +849,8 @@ pub struct InlineStorage { } unsafe impl Storage> for InlineStorage { + const MIN_REPRESENTABLE: usize = N; + fn get_ptr(&self) -> *const u8 { (self as *const Self).cast() } diff --git a/src/collections/pool/direct.rs b/src/collections/pool/direct.rs index 8fee82e..9903447 100644 --- a/src/collections/pool/direct.rs +++ b/src/collections/pool/direct.rs @@ -1135,6 +1135,8 @@ pub struct InlineStorage { unsafe impl Storage> for InlineStorage { + const MIN_REPRESENTABLE: usize = N; + #[inline] fn get_ptr(&self) -> *const u8 { (self as *const Self).cast() diff --git a/src/collections/pool/packed.rs b/src/collections/pool/packed.rs index 8c4090d..075b9c3 100644 --- a/src/collections/pool/packed.rs +++ b/src/collections/pool/packed.rs @@ -1155,6 +1155,7 @@ pub struct InlineStorage { unsafe impl Storage> for InlineStorage { + const MIN_REPRESENTABLE: usize = N; fn get_ptr(&self) -> *const u8 { (self as *const Self).cast() } diff --git a/src/collections/vec.rs b/src/collections/vec.rs index ae40522..0b28e9b 100644 --- a/src/collections/vec.rs +++ b/src/collections/vec.rs @@ -1142,7 +1142,7 @@ impl>, I: Capacity> Vec { /// ``` #[inline] pub fn new() -> Self { - if !S::supported_capacity::() { + if S::MIN_REPRESENTABLE > I::MAX_REPRESENTABLE { buffer_too_large_for_index_type::(); } Self::UNINIT @@ -1156,7 +1156,7 @@ impl>, I: Capacity> Vec { /// Panics if the specified capacity cannot be represented by a `usize` /// or if the capacity exceeds the maximum supported by the backing Storage. pub fn with_capacity(capacity: I) -> Self { - if !S::supported_capacity::() { + if S::MIN_REPRESENTABLE > I::MAX_REPRESENTABLE { buffer_too_large_for_index_type::(); } Vec { diff --git a/src/storage.rs b/src/storage.rs index ba9a89e..01110b7 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -50,6 +50,7 @@ pub(crate) fn usize_exceeds_max_capacity() { ); } +#[inline] pub(crate) fn cast_capacity(capacity: usize) -> I { if capacity > I::MAX_REPRESENTABLE { usize_exceeds_max_capacity::(); @@ -261,6 +262,8 @@ impl LayoutSpec for ArrayLayout { /// An interface to a contiguous memory block for use by data structures. #[allow(clippy::missing_safety_doc)] // individual methods _do_ have safety docs! pub unsafe trait Storage: Sized { + /// The minimum capacity which must be indexable for this storage type. + const MIN_REPRESENTABLE: usize = 0; /// Extracts a pointer to the beginning of the memory block. /// /// # Safety @@ -283,20 +286,17 @@ pub unsafe trait Storage: Sized { /// Implementors must ensure the same value is returned every time this /// method is called throughout the block's lifetime. fn capacity(&self) -> usize; - /// Try to grow a storage instance, reallocating when supported. + /// + /// # Safety + /// When supported, this method must return a new, non-overlapping + /// memory block without invalidating the current block. fn try_grow( &mut self, _min_capacity: Option, ) -> Result { CapacityError::new() } - - /// Check if a Capacity exceeds the inherent bounds of the Storage. - #[inline] - fn supported_capacity() -> bool { - true - } } /// An interface for storage types which are owned, not bound to an external buffer. @@ -380,6 +380,8 @@ unsafe impl Storage for ArenaStorage<'_, R> { } unsafe impl> Storage for crate::arena::Box<'_, S> { + const MIN_REPRESENTABLE: usize = S::MIN_REPRESENTABLE; + #[inline] fn get_ptr(&self) -> *const u8 { (**self).get_ptr() @@ -395,6 +397,8 @@ unsafe impl> Storage for crate::arena::Box<'_, S } unsafe impl> Storage for &mut S { + const MIN_REPRESENTABLE: usize = S::MIN_REPRESENTABLE; + fn get_ptr(&self) -> *const u8 { (**self).get_ptr() } @@ -573,6 +577,7 @@ unsafe impl> Storage for alloc::boxed::Box { pub type InlineStorage = [MaybeUninit; C]; unsafe impl Storage> for InlineStorage { + const MIN_REPRESENTABLE: usize = C; fn get_ptr(&self) -> *const u8 { self.as_ptr().cast() } @@ -582,9 +587,6 @@ unsafe impl Storage> for InlineStorage { fn capacity(&self) -> usize { C } - fn supported_capacity() -> bool { - C <= I::MAX_REPRESENTABLE - } } impl OwnedStorage> for InlineStorage { From 2acddf46816531c9d7bc6d3a00f0edb220c8ba84 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 9 Mar 2022 16:10:42 -0800 Subject: [PATCH 3/6] test initial capacity for ReallocVec Signed-off-by: Andrew Whitehead --- src/collections/vec.rs | 11 +++++++++++ src/storage.rs | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/collections/vec.rs b/src/collections/vec.rs index 0b28e9b..932951a 100644 --- a/src/collections/vec.rs +++ b/src/collections/vec.rs @@ -1957,5 +1957,16 @@ mod tests { let v = ReallocVec::::from_iter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); assert_eq!(v.capacity(), 10); + + // test initial capacities based on size_of + let mut v = ReallocVec::::new(); + v.push(1u8); + assert_eq!(v.capacity(), 8); + let mut v = ReallocVec::::new(); + v.push(1u32); + assert_eq!(v.capacity(), 4); + let mut v = ReallocVec::<[u8; 1025]>::new(); + v.push([1u8; 1025]); + assert_eq!(v.capacity(), 1); } } diff --git a/src/storage.rs b/src/storage.rs index 01110b7..a75e9f7 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -455,9 +455,9 @@ unsafe impl AllocPolicy for ExpGrow { from_capacity: usize, min_capacity: Option, ) -> Result<(NonNull, usize), CapacityError> { - let min_cap = min_capacity.unwrap_or(min_non_zero_cap::()); + let min_cap = min_capacity.unwrap_or(0).max(min_non_zero_cap::()); let cap = min_cap.max((from_capacity.saturating_mul(2)).min(I::MAX_REPRESENTABLE)); - if cap == from_capacity || cap < min_cap { + if cap <= from_capacity || cap < min_cap { return CapacityError::new(); } From 48f6122e2ca8f3c8a1b81d93e3fc0959633688b6 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 9 Mar 2022 17:01:35 -0800 Subject: [PATCH 4/6] documentation fixes Signed-off-by: Andrew Whitehead --- src/collections/mod.rs | 10 ++++++++++ src/storage.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/collections/mod.rs b/src/collections/mod.rs index 1c64658..e206f36 100644 --- a/src/collections/mod.rs +++ b/src/collections/mod.rs @@ -599,6 +599,16 @@ pub type AllocVec = Vec::new(); +/// vec.push('a'); +/// vec.push('b'); +/// vec.push('c'); +/// assert_eq!(vec.len(), 3); +/// assert_eq!(vec.capacity(), 4); +/// ``` pub type ReallocVec = Vec>, I>; /// A vector using an inline array for storage. diff --git a/src/storage.rs b/src/storage.rs index a75e9f7..9548251 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -411,6 +411,7 @@ unsafe impl> Storage for &mut S { } #[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] /// Policy for heap storage (re)allocation. pub unsafe trait AllocPolicy { #[inline] @@ -425,14 +426,17 @@ pub unsafe trait AllocPolicy { } #[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// An allocation policy for constant capacity storage. pub struct NoResize; #[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] unsafe impl AllocPolicy for NoResize {} #[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] /// An allocation policy using exponential growth. pub struct ExpGrow; @@ -449,6 +453,7 @@ const fn min_non_zero_cap() -> usize { } #[cfg(feature = "alloc")] +#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))] unsafe impl AllocPolicy for ExpGrow { #[inline] fn try_grow( From dc8d8f9c1d556098982b9618a43dc4a51a042916 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 10 Mar 2022 10:08:09 -0800 Subject: [PATCH 5/6] add From<&mut [T;N]>, try_with_capacity Signed-off-by: Andrew Whitehead --- src/collections/vec.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/collections/vec.rs b/src/collections/vec.rs index 932951a..82348bf 100644 --- a/src/collections/vec.rs +++ b/src/collections/vec.rs @@ -1150,11 +1150,28 @@ impl>, I: Capacity> Vec { } impl>, I: Capacity> Vec { + /// Constructs a new, empty Vec with the specified capacity, returning + /// an error if the capacity exceeds the maximum supported by the backing + /// Storage or the Capacity type cannot fully index the backing Storage. + #[inline] + pub fn try_with_capacity(capacity: I) -> Result { + if S::MIN_REPRESENTABLE > I::MAX_REPRESENTABLE { + CapacityError::new() + } else { + Ok(Vec { + len: I::ZERO, + buf: S::try_with_capacity(capacity.as_usize())?, + elem: PhantomData, + }) + } + } + /// Constructs a new, empty Vec with the specified capacity. /// /// # Panics - /// Panics if the specified capacity cannot be represented by a `usize` + /// Panics if the Capacity type cannot fully index the backing Storage, /// or if the capacity exceeds the maximum supported by the backing Storage. + #[inline] pub fn with_capacity(capacity: I) -> Self { if S::MIN_REPRESENTABLE > I::MAX_REPRESENTABLE { buffer_too_large_for_index_type::(); @@ -1217,6 +1234,17 @@ impl>, I: Capacity, const N: usize> Fro } } +impl>, I: Capacity, const N: usize> From<&mut [T; N]> + for Vec +{ + fn from(source: &mut [T; N]) -> Self { + let cap = cast_capacity(source.len()); + let mut ret = Self::with_capacity(cap); + ret.extend(source.iter().cloned()); + ret + } +} + impl>, I: Capacity, const N: usize> From<[T; N]> for Vec { From f21de2b16e89767458562f57812789458c4e04c0 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 10 Mar 2022 12:29:22 -0800 Subject: [PATCH 6/6] update try_grow signature, implement for Box Signed-off-by: Andrew Whitehead --- src/storage.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/storage.rs b/src/storage.rs index 9548251..f68a9bd 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -291,10 +291,7 @@ pub unsafe trait Storage: Sized { /// # Safety /// When supported, this method must return a new, non-overlapping /// memory block without invalidating the current block. - fn try_grow( - &mut self, - _min_capacity: Option, - ) -> Result { + fn try_grow(&self, _min_capacity: Option) -> Result { CapacityError::new() } } @@ -527,10 +524,7 @@ unsafe impl Storage for AllocStorage { fn capacity(&self) -> usize { self.cap } - fn try_grow( - &mut self, - min_capacity: Option, - ) -> Result { + fn try_grow(&self, min_capacity: Option) -> Result { let (ptr, cap) = A::try_grow::(self.cap, min_capacity)?; Ok(AllocStorage { ptr, @@ -576,6 +570,11 @@ unsafe impl> Storage for alloc::boxed::Box { fn capacity(&self) -> usize { (**self).capacity() } + fn try_grow(&self, min_capacity: Option) -> Result { + Ok(alloc::boxed::Box::new( + (**self).try_grow::(min_capacity)?, + )) + } } /// Shorthand for `[MaybeUninit; C]` for use with generic data structures.