diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f93d2..7e4d806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 36.1.1-rc7 (api=2.0.0, abi=2.0.0) +- BREAKING CHANGES: + - The in-place constructors for `Box` and `Arc` now require the initializer function to return a result, yielding the uninitialized allocation if allocation succeeded but initialization reported a failure. +- Added more constructors (such as `FromIterator`) for `BoxedSlice` and `ArcSlice`. + # 36.1.1-rc6 (api=2.0.0, abi=2.0.0) - Refine `RustAlloc`'s implementation such that it stays a ZST. diff --git a/Cargo.toml b/Cargo.toml index 45e1cb5..24704aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,12 @@ license = " EPL-2.0 OR Apache-2.0" categories = ["development-tools::ffi", "no-std::no-alloc"] repository = "https://github.com/ZettaScaleLabs/stabby" readme = "stabby/README.md" -version = "36.1.1-rc6" # Track +version = "36.1.1-rc7" # Track [workspace.dependencies] -stabby-macros = { path = "./stabby-macros/", version = "36.1.1-rc6", default-features = false } # Track -stabby-abi = { path = "./stabby-abi/", version = "36.1.1-rc6", default-features = false } # Track -stabby = { path = "./stabby/", version = "36.1.1-rc6", default-features = false } # Track +stabby-macros = { path = "./stabby-macros/", version = "36.1.1-rc7", default-features = false } # Track +stabby-abi = { path = "./stabby-abi/", version = "36.1.1-rc7", default-features = false } # Track +stabby = { path = "./stabby/", version = "36.1.1-rc7", default-features = false } # Track abi_stable = "0.11.0" libc = "0.2" diff --git a/stabby-abi/src/alloc/boxed.rs b/stabby-abi/src/alloc/boxed.rs index 557f635..a26d6b2 100644 --- a/stabby-abi/src/alloc/boxed.rs +++ b/stabby-abi/src/alloc/boxed.rs @@ -12,10 +12,14 @@ // Pierre Avital, // -use crate::IntoDyn; +use crate::{unreachable_unchecked, IntoDyn}; use super::{vec::*, AllocPtr, AllocSlice, IAlloc}; -use core::{fmt::Debug, ptr::NonNull}; +use core::{ + fmt::Debug, + mem::{ManuallyDrop, MaybeUninit}, + ptr::NonNull, +}; /// An ABI-stable Box, provided `Alloc` is ABI-stable. #[crate::stabby] @@ -41,12 +45,21 @@ where /// /// If the allocation fails, the `constructor` will not be run. /// - /// If the `constructor` panics, the allocated memory will be leaked. + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. + /// + /// # Errors + /// Returns the uninitialized allocation if the constructor declares a failure. /// /// # Panics /// If the allocator fails to provide an appropriate allocation. - pub fn make)>(constructor: F) -> Self { - Self::make_in(constructor, super::DefaultAllocator::new()) + pub unsafe fn make< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( + constructor: F, + ) -> Result>> { + // SAFETY: Ensured by parent fn + unsafe { Self::make_in(constructor, super::DefaultAllocator::new()) } } /// Attempts to allocate [`Self`] and store `value` in it. /// @@ -64,85 +77,105 @@ impl Box { /// If the `constructor` panics, the allocated memory will be leaked. /// /// # Errors - /// Returns the `constructor` and the allocator in case of failure. + /// - Returns the `constructor` and the allocator in case of allocation failure. + /// - Returns the uninitialized allocated memory if `constructor` fails. + /// + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. /// /// # Notes /// Note that the allocation may or may not be zeroed. - pub fn try_make_in)>( + #[allow(clippy::type_complexity)] + pub unsafe fn try_make_in< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( constructor: F, mut alloc: Alloc, - ) -> Result { + ) -> Result, Alloc>, (F, Alloc)>> { let mut ptr = match AllocPtr::alloc(&mut alloc) { Some(mut ptr) => { - unsafe { ptr.prefix_mut().alloc.write(alloc) }; + // SAFETY: `ptr` just got allocated via `AllocPtr::alloc`. + unsafe { ptr.prefix_mut() }.alloc.write(alloc); ptr } - None => return Err((constructor, alloc)), + None => return Err(Err((constructor, alloc))), }; - unsafe { - constructor(ptr.as_mut()); - } - Ok(Self { - ptr: unsafe { ptr.assume_init() }, - }) + // SAFETY: We are the sole owners of `ptr` + constructor(unsafe { ptr.as_mut() }).map_or_else( + |()| Err(Ok(Box { ptr })), + |_| { + Ok(Self { + // SAFETY: `constructor` reported success. + ptr: unsafe { ptr.assume_init() }, + }) + }, + ) } /// Attempts to allocate a [`Self`] and store `value` in it /// # Errors /// Returns `value` and the allocator in case of failure. pub fn try_new_in(value: T, alloc: Alloc) -> Result { - let this = Self::try_make_in( - |slot| unsafe { - slot.write(core::ptr::read(&value)); - }, - alloc, - ); + // SAFETY: `ctor` is a valid constructor, always initializing the value. + let this = unsafe { + Self::try_make_in( + |slot: &mut core::mem::MaybeUninit| { + // SAFETY: `value` will be forgotten if the allocation succeeds and `read` is called. + Ok(slot.write(core::ptr::read(&value))) + }, + alloc, + ) + }; match this { - Ok(this) => Ok(this), - Err((_, a)) => Err((value, a)), + Ok(this) => { + core::mem::forget(value); + Ok(this) + } + Err(Err((_, a))) => Err((value, a)), + // SAFETY: the constructor is infallible. + Err(Ok(_)) => unsafe { unreachable_unchecked!() }, } } /// Attempts to allocate [`Self`], initializing it with `constructor`. /// /// Note that the allocation may or may not be zeroed. /// + /// # Errors + /// Returns the uninitialized allocated memory if `constructor` fails. + /// + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. + /// /// # Panics /// If the allocator fails to provide an appropriate allocation. - pub fn make_in)>( + pub unsafe fn make_in< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( constructor: F, - mut alloc: Alloc, - ) -> Self { - let mut ptr = match AllocPtr::alloc(&mut alloc) { - Some(mut ptr) => { - unsafe { ptr.prefix_mut().alloc.write(alloc) }; - ptr - } - None => panic!("Allocation failed"), - }; - unsafe { - constructor(ptr.as_mut()); - } - Self { - ptr: unsafe { ptr.assume_init() }, - } + alloc: Alloc, + ) -> Result, Alloc>> { + Self::try_make_in(constructor, alloc).map_err(|e| match e { + Ok(uninit) => uninit, + Err(_) => panic!("Allocation failed"), + }) } /// Attempts to allocate [`Self`] and store `value` in it. /// /// # Panics /// If the allocator fails to provide an appropriate allocation. pub fn new_in(value: T, alloc: Alloc) -> Self { - Self::make_in( - move |slot| { - slot.write(value); - }, - alloc, - ) + // SAFETY: `constructor` fits the spec. + let this = unsafe { Self::make_in(move |slot| Ok(slot.write(value)), alloc) }; + // SAFETY: `constructor` is infallible. + unsafe { this.unwrap_unchecked() } } /// Extracts the value from the allocation, freeing said allocation. pub fn into_inner(mut this: Self) -> T { - let ret = unsafe { core::ptr::read(&*this) }; - this.free(); + // SAFETY: `this` will be forgotten, preventing double-frees. + let ret = ManuallyDrop::new(unsafe { core::ptr::read(&*this) }); + // SAFETY: `this` is immediately forgotten as required. + unsafe { this.free() }; core::mem::forget(this); - ret + ManuallyDrop::into_inner(ret) } /// Returns the pointer to the inner raw allocation, leaking `this`. /// @@ -161,8 +194,13 @@ impl Box { } impl Box { - fn free(&mut self) { + /// Frees the allocation without destroying the value in it. + /// # Safety + /// `self` is in an invalid state after this and MUST be forgotten immediately. + unsafe fn free(&mut self) { + // SAFETY: `Box` guarantees that `alloc` is stored in the prefix, and it won't be reused after this. let mut alloc = unsafe { self.ptr.prefix().alloc.assume_init_read() }; + // SAFETY: `self.ptr` was definitely allocated in `alloc` unsafe { self.ptr.free(&mut alloc) } } } @@ -200,18 +238,22 @@ impl crate::IPtrMut for Box { impl crate::IPtrOwned for Box { fn drop(this: &mut core::mem::ManuallyDrop, drop: unsafe extern "C" fn(&mut ())) { let rthis = &mut ***this; + // SAFETY: This is evil casting shenanigans, but `IPtrOwned` is a type anonimization primitive. unsafe { drop(core::mem::transmute::<&mut T, &mut ()>(rthis)); } - this.free(); + // SAFETY: `this` is immediately forgotten. + unsafe { this.free() } } } impl Drop for Box { fn drop(&mut self) { + // SAFETY: We own the target of `ptr` and guarantee it is initialized. unsafe { core::ptr::drop_in_place(self.ptr.as_mut()); } - self.free() + // SAFETY: `this` is immediately forgotten. + unsafe { self.free() } } } impl IntoDyn for Box { @@ -219,6 +261,7 @@ impl IntoDyn for Box { type Target = T; fn anonimize(self) -> Self::Anonymized { let original_prefix = self.ptr.prefix_ptr(); + // SAFETY: Evil anonimization. let anonymized = unsafe { core::mem::transmute::(self) }; let anonymized_prefix = anonymized.ptr.prefix_ptr(); assert_eq!(anonymized_prefix, original_prefix, "The allocation prefix was lost in anonimization, this is definitely a bug, please report it."); @@ -253,16 +296,19 @@ impl BoxedSlice { } /// Cast into a standard slice. pub fn as_slice(&self) -> &[T] { + // SAFETY: we own this slice. unsafe { core::slice::from_raw_parts(self.slice.start.as_ptr(), self.len()) } } /// Cast into a standard mutable slice. pub fn as_slice_mut(&mut self) -> &mut [T] { + // SAFETY: we own this slice. unsafe { core::slice::from_raw_parts_mut(self.slice.start.as_ptr(), self.len()) } } /// Attempts to add an element to the boxed slice without reallocating. /// # Errors /// Returns the value if pushing would require reallocating. pub fn try_push(&mut self, value: T) -> Result<(), T> { + // SAFETY: the prefix must be initialized for this type to exist. if self.slice.len() >= unsafe { self.slice.start.prefix() } .capacity @@ -270,6 +316,7 @@ impl BoxedSlice { { return Err(value); } + // SAFETY: we've acertained that we have enough space to push an element. unsafe { core::ptr::write(self.slice.end.as_ptr(), value); self.slice.end = NonNull::new_unchecked(self.slice.end.as_ptr().add(1)); @@ -278,11 +325,13 @@ impl BoxedSlice { } pub(crate) fn into_raw_components(self) -> (AllocSlice, usize, Alloc) { let slice = self.slice; + // SAFETY: We forget `alloc` immediately. let alloc = unsafe { core::ptr::read(&self.alloc) }; core::mem::forget(self); let capacity = if core::mem::size_of::() == 0 || slice.is_empty() { 0 } else { + // SAFETY: we store the capacity in the prefix when constructed. unsafe { slice .start @@ -331,6 +380,7 @@ impl From> for BoxedSlice { fn from(value: Vec) -> Self { let (mut slice, capacity, alloc) = value.into_raw_components(); if capacity != 0 { + // SAFETY: the AllocSlice is initialized, storing to it is safe. unsafe { slice.start.prefix_mut().capacity = core::sync::atomic::AtomicUsize::new(capacity); } @@ -379,6 +429,11 @@ impl From<&[T]> for BoxedSlice { Vec::from(value).into() } } +impl FromIterator for BoxedSlice { + fn from_iter>(iter: I) -> Self { + Vec::from_iter(iter).into() + } +} impl Drop for BoxedSlice { fn drop(&mut self) { diff --git a/stabby-abi/src/alloc/collections/arc_btree.rs b/stabby-abi/src/alloc/collections/arc_btree.rs index 6d66d07..d74a0f6 100644 --- a/stabby-abi/src/alloc/collections/arc_btree.rs +++ b/stabby-abi/src/alloc/collections/arc_btree.rs @@ -51,6 +51,7 @@ where core::sync::atomic::Ordering::Release, core::sync::atomic::Ordering::Acquire, ) { + // SAFETY: we've just replaced the old pointer succesfully, we can now free it. Ok(old) => unsafe { core::mem::forget(new); ArcBTreeSet::take_ownership_from_ptr(old); @@ -158,7 +159,9 @@ pub struct ArcBTreeSet< const REPLACE_ON_INSERT: bool = { false }, const SPLIT_LIMIT: usize = { 5 }, > { + /// The root of the tree root: Option>, + /// The allocator of the tree, which is present iff the root is `None` alloc: core::mem::MaybeUninit, } impl Clone @@ -173,6 +176,7 @@ where root: Some(root), alloc: core::mem::MaybeUninit::uninit(), }, + // SAFETY: if there is no root, then there's an allocator. None => unsafe { Self { root: None, @@ -237,6 +241,7 @@ impl { fn drop(&mut self) { if self.root.is_none() { + // SAFETY: The root is `None`, hence the allocator is here. unsafe { self.alloc.assume_init_drop() } } } @@ -247,9 +252,12 @@ where DefaultAllocator: core::default::Default, { /// Takes a pointer to the root. + /// + /// For the pointer to own the pointee of `self`, said pointee must be forgotten. const fn as_ptr( &self, ) -> *mut ArcBTreeSetNodeInner { + // SAFETY: the root is copied and transmuted to a pointer: the pointer is not considered to own the root yet. unsafe { core::mem::transmute::< Option>, @@ -267,6 +275,7 @@ where alloc: core::mem::MaybeUninit::new(Default::default()), }, Some(ptr) => { + // SAFETY: The pointer came from `as_ptr`, and is therefore of the correct layout. We use ManuallyDrop to avoid any risk of double-free let owner: core::mem::ManuallyDrop<_> = unsafe { core::mem::transmute::< NonNull< @@ -307,6 +316,7 @@ where alloc: core::mem::MaybeUninit::new(Default::default()), }, Some(ptr) => { + // SAFETY: The pointer came from `as_ptr`, which must have become the owner by the time this is called. let root = unsafe { core::mem::transmute::< NonNull< @@ -376,6 +386,7 @@ impl inner.insert(value), None => { + // SAFETY: root == None <=> allocator is can be taken to construct a node. let alloc = unsafe { self.alloc.assume_init_read() }; self.root = Some(ArcBTreeSetNode(Arc::new_in( ArcBTreeSetNodeInner::new( @@ -468,6 +479,7 @@ mod seal { for ArcBTreeSetNodeInner { fn drop(&mut self) { + // SAFETY: This only yields back the existing entries that can be safetly dropped. unsafe { core::ptr::drop_in_place(self.entries_mut()) } } } @@ -478,10 +490,9 @@ mod seal { let mut entries: [MaybeUninit< ArcBTreeSetEntry, >; SPLIT_LIMIT] = [(); SPLIT_LIMIT].map(|_| core::mem::MaybeUninit::uninit()); - unsafe { - for (i, entry) in self.entries().iter().enumerate() { - *entries.get_unchecked_mut(i) = MaybeUninit::new(entry.clone()) - } + for (i, entry) in self.entries().iter().enumerate() { + // SAFETY: the number of entries is guaranteed to be small enough. + unsafe { *entries.get_unchecked_mut(i) = MaybeUninit::new(entry.clone()) } } Self { entries, @@ -568,13 +579,12 @@ mod seal { { use core::cmp::Ordering; let inner = Arc::make_mut(&mut self.0); - let alloc = unsafe { - AllocPtr { - ptr: NonNull::new_unchecked(inner), - marker: PhantomData, - } + let arc = AllocPtr { + ptr: NonNull::from(&*inner), + marker: PhantomData, }; - let alloc = unsafe { alloc.prefix().alloc.assume_init_ref() }; + // SAFETY: `arc` is known to bg a valid `Arc`, meaning its prefix contains an initialized alloc. + let alloc = unsafe { arc.prefix().alloc.assume_init_ref() }; let entries = inner.entries_mut(); for (i, entry) in entries.iter_mut().enumerate() { match entry.value.cmp(&value) { @@ -640,12 +650,16 @@ mod seal { where Alloc: Clone, { + debug_assert!(i < self.len); + // SAFETY: See line comments unsafe { - for j in (i..self.len).rev() { - *self.entries.get_unchecked_mut(j + 1) = - MaybeUninit::new(self.entries.get_unchecked(j).assume_init_read()); - } - self.len += 1; + // SAFETY: A node always has at least one slot free thanks to splitting being called at the end of any operations that may increase `self.len` + core::ptr::copy( + self.entries.as_ptr().add(i), + self.entries.as_mut_ptr().add(i + 1), + self.len - i, + ); + // SAFETY: A node always has at least one slot free thanks to splitting being called at the end of any operations that may increase `self.len` *self.entries.get_unchecked_mut(i) = MaybeUninit::new(ArcBTreeSetEntry { value, smaller: core::mem::replace( @@ -658,6 +672,7 @@ mod seal { ), }); } + self.len += 1; self.split(alloc) } fn push( @@ -669,6 +684,7 @@ mod seal { where Alloc: Clone, { + // SAFETY: A node always has at least one slot free thanks to splitting being called at the end of any operations that may increase `self.len` unsafe { self.entries .get_unchecked_mut(self.len) @@ -676,8 +692,8 @@ mod seal { value, smaller: core::mem::replace(&mut self.greater, greater), }); - self.len += 1; } + self.len += 1; self.split(alloc) } fn split( @@ -687,32 +703,36 @@ mod seal { where Alloc: Clone, { - unsafe { - if self.len == SPLIT_LIMIT { - let ArcBTreeSetEntry { - value: pivot, - smaller, - } = self - .entries + if self.len == SPLIT_LIMIT { + // SAFETY: we've just confirmed the `SPLIT_LIMIT/2` node is initialized by checking the length + let pivot = unsafe { + self.entries .get_unchecked(SPLIT_LIMIT / 2) - .assume_init_read(); - let mut right = Self { - entries: [(); SPLIT_LIMIT].map(|_| core::mem::MaybeUninit::uninit()), - len: SPLIT_LIMIT / 2, - greater: self.greater.take(), - }; + .assume_init_read() + }; + let ArcBTreeSetEntry { + value: pivot, + smaller, + } = pivot; + let mut right = Self { + entries: [(); SPLIT_LIMIT].map(|_| core::mem::MaybeUninit::uninit()), + len: SPLIT_LIMIT / 2, + greater: self.greater.take(), + }; + // SAFETY: the left entries are all initialized, allowing to read `SPLIT_LIMIT/2` elements into `right` + unsafe { core::ptr::copy_nonoverlapping( self.entries.get_unchecked(SPLIT_LIMIT / 2 + 1), right.entries.get_unchecked_mut(0), - SPLIT_LIMIT / 2, - ); - self.greater = smaller; - self.len = SPLIT_LIMIT / 2; - let right = ArcBTreeSetNode(Arc::new_in(right, alloc.clone())); - Some((right, pivot)) - } else { - None - } + right.len, + ) + }; + self.greater = smaller; + self.len = SPLIT_LIMIT / 2; + let right = ArcBTreeSetNode(Arc::new_in(right, alloc.clone())); + Some((right, pivot)) + } else { + None } } } @@ -721,11 +741,13 @@ mod seal { ArcBTreeSetNodeInner { pub fn entries(&self) -> &[ArcBTreeSetEntry] { + // SAFETY: Entries up to `self.len` are always initialized. unsafe { core::mem::transmute(self.entries.get_unchecked(..self.len)) } } pub fn entries_mut( &mut self, ) -> &mut [ArcBTreeSetEntry] { + // SAFETY: Entries up to `self.len` are always initialized. unsafe { core::mem::transmute(self.entries.get_unchecked_mut(..self.len)) } } } diff --git a/stabby-abi/src/alloc/single_or_vec.rs b/stabby-abi/src/alloc/single_or_vec.rs index 13b22df..c42aba8 100644 --- a/stabby-abi/src/alloc/single_or_vec.rs +++ b/stabby-abi/src/alloc/single_or_vec.rs @@ -81,6 +81,7 @@ where Self::with_capacity_in(capacity, Alloc::default()) } /// Constructs a new vector in `alloc`, allocating sufficient space for `capacity` elements. + /// /// # Errors /// Returns an [`AllocationError`] if the allocator couldn't provide a sufficient allocation. pub fn try_with_capacity_in(capacity: usize, alloc: Alloc) -> Result { @@ -89,6 +90,7 @@ where }) } /// Constructs a new vector, allocating sufficient space for `capacity` elements. + /// /// # Errors /// Returns an [`AllocationError`] if the allocator couldn't provide a sufficient allocation. pub fn try_with_capacity(capacity: usize) -> Result @@ -107,6 +109,7 @@ where self.inner.match_ref(|_| false, |vec| vec.is_empty()) } /// Adds `value` at the end of `self`. + /// /// # Panics /// This function panics if the vector tried to grow due to /// being full, and the allocator failed to provide a new allocation. diff --git a/stabby-abi/src/alloc/sync.rs b/stabby-abi/src/alloc/sync.rs index 7644da2..ee096a7 100644 --- a/stabby-abi/src/alloc/sync.rs +++ b/stabby-abi/src/alloc/sync.rs @@ -16,12 +16,12 @@ use core::{ fmt::Debug, hash::Hash, marker::PhantomData, - mem::ManuallyDrop, + mem::{ManuallyDrop, MaybeUninit}, ptr::NonNull, sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, }; -use crate::{vtable::HasDropVt, Dyn, IStable, IntoDyn}; +use crate::{unreachable_unchecked, vtable::HasDropVt, Dyn, IStable, IntoDyn}; use super::{ vec::{ptr_add, ptr_diff, Vec, VecInner}, @@ -44,10 +44,23 @@ impl Arc { /// /// Note that the allocation may or may not be zeroed. /// + /// If the allocation fails, the `constructor` will not be run. + /// + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. + /// + /// # Errors + /// Returns the uninitialized allocation if the constructor declares a failure. + /// /// # Panics /// If the allocator fails to provide an appropriate allocation. - pub fn make)>(constructor: F) -> Self { - Self::make_in(constructor, DefaultAllocator::new()) + pub unsafe fn make< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( + constructor: F, + ) -> Result>> { + // SAFETY: Ensured by parent fn + unsafe { Self::make_in(constructor, super::DefaultAllocator::new()) } } /// Attempts to allocate [`Self`] and store `value` in it. /// @@ -61,83 +74,104 @@ impl Arc { impl Arc { /// Attempts to allocate [`Self`], initializing it with `constructor`. /// + /// Note that the allocation may or may not be zeroed. + /// + /// If the `constructor` panics, the allocated memory will be leaked. + /// /// # Errors - /// Returns the constructor and the allocator in case of allocation failure. + /// - Returns the `constructor` and the allocator in case of allocation failure. + /// - Returns the uninitialized allocated memory if `constructor` fails. + /// + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. /// /// # Notes /// Note that the allocation may or may not be zeroed. - pub fn try_make_in)>( + #[allow(clippy::type_complexity)] + pub unsafe fn try_make_in< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( constructor: F, mut alloc: Alloc, - ) -> Result { + ) -> Result, Alloc>, (F, Alloc)>> { let mut ptr = match AllocPtr::alloc(&mut alloc) { Some(mut ptr) => { - unsafe { ptr.prefix_mut().alloc.write(alloc) }; + // SAFETY: `ptr` just got allocated via `AllocPtr::alloc`. + let prefix = unsafe { ptr.prefix_mut() }; + prefix.alloc.write(alloc); + prefix.strong = AtomicUsize::new(1); + prefix.weak = AtomicUsize::new(1); ptr } - None => return Err((constructor, alloc)), + None => return Err(Err((constructor, alloc))), }; - unsafe { - constructor(ptr.as_mut()); - ptr.prefix_mut().strong = AtomicUsize::new(1); - ptr.prefix_mut().weak = AtomicUsize::new(1) - } - Ok(Self { - ptr: unsafe { ptr.assume_init() }, - }) + // SAFETY: We are the sole owners of `ptr` + constructor(unsafe { ptr.as_mut() }).map_or_else( + |()| Err(Ok(Arc { ptr })), + |_| { + Ok(Self { + // SAFETY: `constructor` reported success. + ptr: unsafe { ptr.assume_init() }, + }) + }, + ) } /// Attempts to allocate a [`Self`] and store `value` in it /// # Errors /// Returns `value` and the allocator in case of failure. pub fn try_new_in(value: T, alloc: Alloc) -> Result { - let this = Self::try_make_in( - |slot| unsafe { - slot.write(core::ptr::read(&value)); - }, - alloc, - ); + // SAFETY: `ctor` is a valid constructor, always initializing the value. + let this = unsafe { + Self::try_make_in( + |slot: &mut core::mem::MaybeUninit| { + // SAFETY: `value` will be forgotten if the allocation succeeds and `read` is called. + Ok(slot.write(core::ptr::read(&value))) + }, + alloc, + ) + }; match this { - Ok(this) => Ok(this), - Err((_, a)) => Err((value, a)), + Ok(this) => { + core::mem::forget(value); + Ok(this) + } + Err(Err((_, a))) => Err((value, a)), + // SAFETY: the constructor is infallible. + Err(Ok(_)) => unsafe { unreachable_unchecked!() }, } } /// Attempts to allocate [`Self`], initializing it with `constructor`. /// /// Note that the allocation may or may not be zeroed. /// + /// # Errors + /// Returns the uninitialized allocated memory if `constructor` fails. + /// + /// # Safety + /// `constructor` MUST return `Err(())` if it failed to initialize the passed argument. + /// /// # Panics /// If the allocator fails to provide an appropriate allocation. - pub fn make_in)>( + pub unsafe fn make_in< + F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit) -> Result<&'a mut T, ()>, + >( constructor: F, - mut alloc: Alloc, - ) -> Self { - let mut ptr = match AllocPtr::alloc(&mut alloc) { - Some(mut ptr) => unsafe { - ptr.prefix_mut().alloc.write(alloc); - ptr - }, - None => panic!("Allocation failed"), - }; - unsafe { - constructor(ptr.as_mut()); - ptr.prefix_mut().strong = AtomicUsize::new(1); - ptr.prefix_mut().weak = AtomicUsize::new(1) - } - Self { - ptr: unsafe { ptr.assume_init() }, - } + alloc: Alloc, + ) -> Result, Alloc>> { + Self::try_make_in(constructor, alloc).map_err(|e| match e { + Ok(uninit) => uninit, + Err(_) => panic!("Allocation failed"), + }) } /// Attempts to allocate [`Self`] and store `value` in it. /// /// # Panics /// If the allocator fails to provide an appropriate allocation. pub fn new_in(value: T, alloc: Alloc) -> Self { - Self::make_in( - move |slot| { - slot.write(value); - }, - alloc, - ) + // SAFETY: `constructor` fits the spec. + let this = unsafe { Self::make_in(move |slot| Ok(slot.write(value)), alloc) }; + // SAFETY: `constructor` is infallible. + unsafe { this.unwrap_unchecked() } } /// Returns the pointer to the inner raw allocation, leaking `this`. @@ -285,6 +319,16 @@ pub struct Weak { unsafe impl Send for Weak {} // SAFETY: Same constraints as in `std`. unsafe impl Sync for Weak {} +impl From<&Arc> for Arc { + fn from(value: &Arc) -> Self { + value.clone() + } +} +impl From<&Weak> for Weak { + fn from(value: &Weak) -> Self { + value.clone() + } +} impl From<&Arc> for Weak { fn from(value: &Arc) -> Self { unsafe { value.ptr.prefix() } @@ -586,6 +630,12 @@ impl<'a, T, Alloc: IAlloc> IntoIterator for &'a ArcSlice { } } +impl FromIterator for ArcSlice { + fn from_iter>(iter: I) -> Self { + Vec::from_iter(iter).into() + } +} + /// A weak reference counted slice. #[crate::stabby] pub struct WeakSlice { @@ -637,6 +687,16 @@ impl Clone for WeakSlice { Self { inner: self.inner } } } +impl From<&ArcSlice> for ArcSlice { + fn from(value: &ArcSlice) -> Self { + value.clone() + } +} +impl From<&WeakSlice> for WeakSlice { + fn from(value: &WeakSlice) -> Self { + value.clone() + } +} impl From<&ArcSlice> for WeakSlice { fn from(value: &ArcSlice) -> Self { unsafe { value.inner.start.prefix() } diff --git a/stabby-abi/src/typenum2/unsigned.rs b/stabby-abi/src/typenum2/unsigned.rs index d8eac54..5f6cc77 100644 --- a/stabby-abi/src/typenum2/unsigned.rs +++ b/stabby-abi/src/typenum2/unsigned.rs @@ -91,6 +91,8 @@ pub trait IBitBase { type _ATernary: Alignment; /// Support for [`IBit`] type AsForbiddenValue: ISingleForbiddenValue; + /// u8 if B1, () otherwise + type _Padding: IStable + Default + Copy + Unpin; } /// false #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -112,6 +114,7 @@ impl IBitBase for B0 { type _SaddTernary = B; type _StabTernary = B; type _ATernary = B; + type _Padding = (); type AsForbiddenValue = Saturator; } /// true @@ -134,6 +137,7 @@ impl IBitBase for B1 { type _SaddTernary = A; type _StabTernary = A; type _ATernary = A; + type _Padding = PadByte; type AsForbiddenValue = End; } /// A boolean. [`B0`] and [`B1`] are the canonical members of this type-class @@ -456,6 +460,31 @@ impl NonZero for UInt { , UInt> as IUnsignedBase>::_Simplified; type TruncateAtRightmostOne = Self::_TruncateAtRightmostOne; } +/// A helper to generate padding of appropriate size. +#[repr(C)] +pub struct PaddingHelper, Bit: IBitBase>(Double, Double, Bit::_Padding); +impl + Default, Bit: IBitBase> Default for PaddingHelper { + fn default() -> Self { + Self(Default::default(), Default::default(), Default::default()) + } +} +impl + Copy, Bit: IBitBase> Copy for PaddingHelper {} +impl + Copy, Bit: IBitBase> Clone for PaddingHelper { + fn clone(&self) -> Self { + *self + } +} +unsafe impl, Bit: IBitBase> IStable for PaddingHelper { + type Align = U1; + type Size = <::Mul as IUnsigned>::Add>; + type ForbiddenValues = End; + type ContainsIndirections = B0; + type HasExactlyOneNiche = B0; + type UnusedBits = as IStable>::UnusedBits; + #[cfg(feature = "experimental-ctypes")] + type CType = crate::tuple::Tuple3; + primitive_report!("Padding"); +} impl IUnsignedBase for UInt { const _U128: u128 = (Msb::_U128 << 1) | (::BOOL as u128); type Bit = Bit; @@ -476,8 +505,7 @@ impl IUnsignedBase for UInt { ::UTernary>, Bit>>; type _SatDecrement = , UInt> as IUnsignedBase>::_Simplified; - type _Padding = - OneMoreByte<<::_SatDecrement as IUnsignedBase>::_Padding>; + type _Padding = PaddingHelper; type NextPow2 = ::Add<::UTernary>; type _TruncateAtRightmostOne = Bit::NzTernary>; type _NonZero = Self; diff --git a/stabby-macros/src/structs.rs b/stabby-macros/src/structs.rs index a64ae93..faebcbd 100644 --- a/stabby-macros/src/structs.rs +++ b/stabby-macros/src/structs.rs @@ -213,6 +213,9 @@ pub fn stabby( }); let report_bounds = report.bounds(); let ctype = cfg!(feature = "experimental-ctypes").then(|| { + if matches!(repr, Some(AllowedRepr::Align(_))) { + return quote! {type CType = Self;}; + } let ctype = report.crepr(); quote! {type CType = #ctype;} }); diff --git a/stabby/benches/boxed_slices.rs b/stabby/benches/boxed_slices.rs index b6554d1..e9df13c 100644 --- a/stabby/benches/boxed_slices.rs +++ b/stabby/benches/boxed_slices.rs @@ -10,11 +10,7 @@ fn bench_slices(c: &mut Criterion) { b.iter(|| stabby::boxed::Box::new(black_box(15))); }); c.bench_function("stabby_box_make", |b| { - b.iter(|| { - stabby::boxed::Box::make(|slot| { - slot.write(black_box(15)); - }) - }); + b.iter(|| unsafe { stabby::boxed::Box::make(|slot| Ok(slot.write(black_box(15)))) }); }); c.bench_function("std_box_big_new", |b| { b.iter(|| Box::<[usize; 10000]>::new([15; 10000])); @@ -23,16 +19,16 @@ fn bench_slices(c: &mut Criterion) { b.iter(|| stabby::boxed::Box::<[usize; 10000]>::new([15; 10000])); }); c.bench_function("stabby_box_big_make", |b| { - b.iter(|| { + b.iter(|| unsafe { stabby::boxed::Box::<[usize; 10000]>::make(|slot| { - for slot in unsafe { - core::mem::transmute::< - &mut MaybeUninit<[usize; 10000]>, - &mut [MaybeUninit; 10000], - >(slot) - } { + for slot in core::mem::transmute::< + &mut MaybeUninit<[usize; 10000]>, + &mut [MaybeUninit; 10000], + >(slot) + { slot.write(15); } + Ok(slot.assume_init_mut()) }) }); });