Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v36.1.1-rc7 #88

Merged
merged 3 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
159 changes: 107 additions & 52 deletions stabby-abi/src/alloc/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
// Pierre Avital, <[email protected]>
//

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]
Expand All @@ -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<F: FnOnce(&mut core::mem::MaybeUninit<T>)>(constructor: F) -> Self {
Self::make_in(constructor, super::DefaultAllocator::new())
pub unsafe fn make<
F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit<T>) -> Result<&'a mut T, ()>,
>(
constructor: F,
) -> Result<Self, Box<MaybeUninit<T>>> {
// SAFETY: Ensured by parent fn
unsafe { Self::make_in(constructor, super::DefaultAllocator::new()) }
}
/// Attempts to allocate [`Self`] and store `value` in it.
///
Expand All @@ -64,85 +77,105 @@ impl<T, Alloc: IAlloc> Box<T, Alloc> {
/// 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<F: FnOnce(&mut core::mem::MaybeUninit<T>)>(
#[allow(clippy::type_complexity)]
pub unsafe fn try_make_in<
F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit<T>) -> Result<&'a mut T, ()>,
>(
constructor: F,
mut alloc: Alloc,
) -> Result<Self, (F, Alloc)> {
) -> Result<Self, Result<Box<MaybeUninit<T>, 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<Self, (T, Alloc)> {
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<T>| {
// 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<F: FnOnce(&mut core::mem::MaybeUninit<T>)>(
pub unsafe fn make_in<
F: for<'a> FnOnce(&'a mut core::mem::MaybeUninit<T>) -> 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<Self, Box<MaybeUninit<T>, 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`.
///
Expand All @@ -161,8 +194,13 @@ impl<T, Alloc: IAlloc> Box<T, Alloc> {
}

impl<T, Alloc: IAlloc> Box<T, Alloc> {
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) }
}
}
Expand Down Expand Up @@ -200,25 +238,30 @@ impl<T, Alloc: IAlloc> crate::IPtrMut for Box<T, Alloc> {
impl<T, Alloc: IAlloc> crate::IPtrOwned for Box<T, Alloc> {
fn drop(this: &mut core::mem::ManuallyDrop<Self>, 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<T, Alloc: IAlloc> Drop for Box<T, Alloc> {
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<T, Alloc: IAlloc> IntoDyn for Box<T, Alloc> {
type Anonymized = Box<(), Alloc>;
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, Self::Anonymized>(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.");
Expand Down Expand Up @@ -253,23 +296,27 @@ impl<T, Alloc: IAlloc> BoxedSlice<T, Alloc> {
}
/// 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
.load(core::sync::atomic::Ordering::Relaxed)
{
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));
Expand All @@ -278,11 +325,13 @@ impl<T, Alloc: IAlloc> BoxedSlice<T, Alloc> {
}
pub(crate) fn into_raw_components(self) -> (AllocSlice<T, Alloc>, 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::<T>() == 0 || slice.is_empty() {
0
} else {
// SAFETY: we store the capacity in the prefix when constructed.
unsafe {
slice
.start
Expand Down Expand Up @@ -331,6 +380,7 @@ impl<T, Alloc: IAlloc> From<Vec<T, Alloc>> for BoxedSlice<T, Alloc> {
fn from(value: Vec<T, Alloc>) -> 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);
}
Expand Down Expand Up @@ -379,6 +429,11 @@ impl<T: Copy, Alloc: IAlloc + Default> From<&[T]> for BoxedSlice<T, Alloc> {
Vec::from(value).into()
}
}
impl<T, Alloc: IAlloc + Default> FromIterator<T> for BoxedSlice<T, Alloc> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Vec::from_iter(iter).into()
}
}

impl<T, Alloc: IAlloc> Drop for BoxedSlice<T, Alloc> {
fn drop(&mut self) {
Expand Down
Loading
Loading