use crate::error::InvLayout;
use crate::{
error::{AllocError, ArithOp, ArithOverflow, Cause, LayoutErr},
type_props::{
varsized_nonnull_from_raw_parts, varsized_pointer_from_raw_parts, PtrProps, SizedProps,
USIZE_MAX_NO_HIGH_BIT,
},
Alloc,
};
use core::{
alloc::Layout,
mem::{forget, transmute},
num::NonZeroUsize,
ops::Deref,
ptr::{self, NonNull},
};
pub const fn checked_op(l: usize, op: ArithOp, r: usize) -> Result<usize, ArithOverflow> {
let res = match op {
ArithOp::Add => l.checked_add(r),
ArithOp::Sub => l.checked_sub(r),
ArithOp::Mul => l.checked_mul(r),
ArithOp::Div => l.checked_div(r),
ArithOp::Rem => l.checked_rem(r),
};
match res {
Some(v) => Ok(v),
None => Err(ArithOverflow(l, op, r)),
}
}
#[must_use]
pub fn checked_op_panic(l: usize, op: ArithOp, r: usize) -> usize {
match checked_op(l, op, r) {
Ok(v) => v,
Err(e) => panic!("{}", e),
}
}
#[cfg(all(feature = "const_extras", not(feature = "std")))]
#[must_use]
#[allow(unknown_lints)]
#[allow(clippy::incompatible_msrv)]
pub const fn checked_op_panic_const(l: usize, op: ArithOp, r: usize) -> usize {
match checked_op(l, op, r) {
Ok(v) => v,
Err(_) => panic!("An arithmetic operation overflowed"),
}
}
#[cfg(any(not(feature = "const_extras"), feature = "std"))]
#[must_use]
pub fn checked_op_panic_const(l: usize, op: ArithOp, r: usize) -> usize {
match checked_op(l, op, r) {
Ok(v) => v,
Err(..) => panic!("An arithmetic operation overflowed"),
}
}
pub(crate) fn alloc_then<Ret, A: Alloc + ?Sized, E, F: Fn(NonNull<u8>, E) -> Ret>(
a: &A,
layout: Layout,
e: E,
then: F,
) -> Result<Ret, AllocError> {
match a.alloc(layout) {
Ok(ptr) => Ok(then(ptr, e)),
Err(e) => Err(e),
}
}
const_if! {
"const_extras",
"Creates a `NonNull<[T]>` from a pointer and a length.\n\nThis is a helper used in place of
[`NonNull::slice_from_raw_parts`], which was stabilized after this crate's MSRV.",
#[must_use]
pub const fn nonnull_slice_from_raw_parts<T>(p: NonNull<T>, len: usize) -> NonNull<[T]> {
varsized_nonnull_from_raw_parts(p.cast(), len)
}
}
const_if! {
"const_extras",
"Creates a `*mut [T]` from a pointer and a length.\n\nThis is a helper used in place of \
[`ptr::slice_from_raw_parts_mut`], which was const-stabilized after this crate's MSRV.",
#[must_use]
pub const fn slice_ptr_from_raw_parts<T>(p: *mut T, len: usize) -> *mut [T] {
varsized_pointer_from_raw_parts(p.cast(), len)
}
}
const_if! {
"const_max",
"Returns the length of a [`NonNull`] slice pointer.\n\nThis is a helper used in place of \
[`NonNull::len`], which was stabilized after this crate's MSRV.\n\n# Safety\n\nCallers must \
ensure `ptr` is aligned and non-dangling.",
#[must_use]
#[inline]
pub const unsafe fn nonnull_slice_len<T>(ptr: NonNull<[T]>) -> usize {
(&*ptr.as_ptr()).len()
}
}
pub(crate) fn null_q_zsl_check<T, F: Fn(Layout) -> *mut T>(
layout: Layout,
f: F,
nq: fn(*mut T, Layout) -> Result<NonNull<u8>, AllocError>,
) -> Result<NonNull<u8>, AllocError> {
zsl_check(layout, |layout: Layout| nq(f(layout), layout))
}
#[allow(dead_code)]
#[cfg(feature = "os_err_reporting")]
pub(crate) fn null_q_dyn<T>(ptr: *mut T, layout: Layout) -> Result<NonNull<u8>, AllocError> {
null_q_oserr(ptr, layout)
}
#[allow(dead_code)]
#[cfg(not(feature = "os_err_reporting"))]
pub(crate) fn null_q_dyn<T>(ptr: *mut T, layout: Layout) -> Result<NonNull<u8>, AllocError> {
null_q(ptr, layout)
}
#[cfg(feature = "os_err_reporting")]
fn null_q_oserr<T>(ptr: *mut T, layout: Layout) -> Result<NonNull<u8>, AllocError> {
if ptr.is_null() {
Err(AllocError::AllocFailed(
layout,
Cause::OSErr(std::io::Error::last_os_error()),
))
} else {
Ok(unsafe { NonNull::new_unchecked(ptr.cast()) })
}
}
pub(crate) fn null_q<T>(ptr: *mut T, layout: Layout) -> Result<NonNull<u8>, AllocError> {
if ptr.is_null() {
Err(AllocError::AllocFailed(layout, Cause::Unknown))
} else {
Ok(unsafe { NonNull::new_unchecked(ptr.cast()) })
}
}
pub(crate) fn zsl_check<Ret, F: Fn(Layout) -> Result<Ret, AllocError>>(
layout: Layout,
f: F,
) -> Result<Ret, AllocError> {
if layout.size() == 0 {
Err(AllocError::ZeroSizedLayout(dangling_nonnull_for(layout)))
} else {
f(layout)
}
}
pub const fn align_up(sz: usize, align: NonZeroUsize) -> Result<usize, InvLayout> {
if sz > USIZE_MAX_NO_HIGH_BIT || align.get() > USIZE_MAX_NO_HIGH_BIT {
return Err(InvLayout(sz, align.get(), LayoutErr::Overflow));
}
Ok(unsafe { align_up_unchecked(sz, align.get()) })
}
#[must_use]
#[inline]
pub const unsafe fn align_up_unchecked(sz: usize, align: usize) -> usize {
let m1 = align - 1;
(sz + m1) & !m1
}
#[must_use]
pub const fn dangling_nonnull_for(layout: Layout) -> NonNull<u8> {
unsafe { dangling_nonnull(layout.align()) }
}
#[must_use]
#[inline]
pub const unsafe fn dangling_nonnull(align: usize) -> NonNull<u8> {
transmute::<NonZeroUsize, NonNull<u8>>(NonZeroUsize::new_unchecked(align))
}
#[cfg(feature = "alloc_slice")]
pub(crate) const fn layout_or_err<T>(n: usize) -> Result<Layout, InvLayout> {
match layout_or_sz_align::<T>(n) {
Ok(l) => Ok(l),
Err((sz, aln, r)) => Err(InvLayout(sz, aln, r)),
}
}
pub const fn layout_or_sz_align<T>(n: usize) -> Result<Layout, (usize, usize, LayoutErr)> {
let (sz, align) = (T::SZ, T::ALN);
if sz != 0 && n > ((USIZE_MAX_NO_HIGH_BIT + 1) - align) / sz {
return Err((sz, align, LayoutErr::Overflow));
}
unsafe { Ok(Layout::from_size_align_unchecked(sz * n, align)) }
}
pub struct AllocGuard<'a, T: ?Sized, A: Alloc + ?Sized> {
ptr: NonNull<T>,
alloc: &'a A,
}
impl<'a, T: ?Sized, A: Alloc + ?Sized> AllocGuard<'a, T, A> {
const_if! {
"const_extras",
"Creates a new guard from a pointer and a reference to an allocator.\n\n# Safety\n\n\
Callers must guarantee `ptr` is a valid, readable, writable pointer allocated using \
`alloc`.",
#[inline]
pub const unsafe fn new(ptr: NonNull<T>, alloc: &'a A) -> AllocGuard<'a, T, A> {
AllocGuard { ptr, alloc }
}
}
const_if! {
"const_max",
"Initializes the value by writing to the contained pointer.",
#[cfg_attr(miri, track_caller)]
#[inline]
pub const fn init(&mut self, elem: T)
where
T: Sized
{
unsafe {
ptr::write(self.ptr.as_ptr(), elem);
}
}
}
const_if! {
"const_extras",
"Releases ownership of the allocation, preventing deallocation, and returns the raw \
pointer.",
#[must_use]
#[inline]
pub const fn release(self) -> NonNull<T> {
let ptr = self.ptr;
forget(self);
ptr
}
}
}
impl<T: ?Sized, A: Alloc + ?Sized> Drop for AllocGuard<'_, T, A> {
#[cfg_attr(miri, track_caller)]
fn drop(&mut self) {
unsafe {
self.alloc.dealloc(self.ptr.cast::<u8>(), self.ptr.layout());
}
}
}
impl<T: ?Sized, A: Alloc + ?Sized> Deref for AllocGuard<'_, T, A> {
type Target = NonNull<T>;
#[inline]
fn deref(&self) -> &NonNull<T> {
&self.ptr
}
}
pub struct SliceAllocGuard<'a, T, A: Alloc + ?Sized> {
ptr: NonNull<T>,
alloc: &'a A,
pub(crate) init: usize,
full: usize,
}
impl<'a, T, A: Alloc + ?Sized> SliceAllocGuard<'a, T, A> {
const_if! {
"const_extras",
"Creates a new slice guard for `full` elements at `ptr` in the given allocator.\n\n# \
Safety\n\nCallers must ensure that `ptr` was allocated using `alloc`, has space for `full` \
`T`, and is readable, writable, valid, and aligned.",
#[inline]
pub const unsafe fn new(ptr: NonNull<T>, alloc: &'a A, full: usize)
-> SliceAllocGuard<'a, T, A> {
SliceAllocGuard {
ptr,
alloc,
init: 0,
full,
}
}
}
const_if! {
"const_extras",
"Creates a new slice guard for `full` elements at `ptr` in the given allocator.\n\n# \
Safety\n\nIn addition to the restrictions of [`SliceAllocGuard::new`], callers must ensure \
that `init` is the number of existing initialized elements in the slice.",
#[inline]
pub const unsafe fn new_with_init(ptr: NonNull<T>, alloc: &'a A, init: usize, full: usize)
-> SliceAllocGuard<'a, T, A> {
SliceAllocGuard {
ptr,
alloc,
init,
full,
}
}
}
const_if! {
"const_extras",
"Release ownership of the slice without deallocating memory, returning a `NonNull<T>` \
pointer to the slice.",
#[must_use]
#[inline]
pub const fn release(self) -> NonNull<[T]> {
let ret = self.get_init_part();
forget(self);
ret
}
}
const_if! {
"const_extras",
"Release ownership of the slice without deallocating memory, returning a `NonNull<T>` \
pointer to the slice's first element.",
#[must_use]
#[inline]
pub const fn release_first(self) -> NonNull<T> {
let ret = self.ptr;
forget(self);
ret
}
}
const_if! {
"const_extras",
"Gets a `NonNull<[T]>` pointer to the initialized elements of the slice.",
#[cfg_attr(miri, track_caller)]
#[must_use]
pub const fn get_init_part(&self) -> NonNull<[T]> {
nonnull_slice_from_raw_parts(self.ptr, self.init)
}
}
const_if! {
"const_extras",
"Gets a `NonNull<[T]>` pointer to the uninitialized elements of the slice.",
#[must_use]
pub const fn get_uninit_part(&self) -> NonNull<[T]> {
nonnull_slice_from_raw_parts(
unsafe { NonNull::new_unchecked(self.ptr.as_ptr().add(self.init)) },
self.full - self.init,
)
}
}
const_if! {
"const_extras",
"Gets a `NonNull<[T]>` pointer to the full slice.",
#[cfg_attr(miri, track_caller)]
#[must_use]
pub const fn get_full(&self) -> NonNull<[T]> {
nonnull_slice_from_raw_parts(self.ptr, self.full)
}
}
const_if! {
"const_max",
"Sets the initialized element count.\n\n# Safety\n\nCallers must ensure the new \
count is correct.",
#[inline]
pub const unsafe fn set_init(&mut self, init: usize) {
self.init = init;
}
}
const_if! {
"const_max",
"Initializes the next element of the slice with `elem`.\n\n# Errors\n\nReturns \
`Err(elem)` if the slice is at capacity.",
#[inline]
pub const fn init(&mut self, elem: T) -> Result<(), T> {
if self.init == self.full {
return Err(elem);
}
unsafe {
self.init_unchecked(elem);
}
Ok(())
}
}
const_if! {
"const_max",
"Initializes the next element of the slice with `elem`.\n\n# Safety\n\nCallers must \
ensure that the slice is not at capacity. (`initialized() < full()`)",
#[inline]
pub const unsafe fn init_unchecked(&mut self, elem: T) {
ptr::write(self.ptr.as_ptr().add(self.init), elem);
self.init += 1;
}
}
pub fn extend_init<I: IntoIterator<Item = T>>(&mut self, iter: I) -> Result<(), I::IntoIter> {
let mut iter = iter.into_iter();
loop {
if self.init == self.full {
return Err(iter);
}
match iter.next() {
Some(elem) => unsafe {
ptr::write(self.ptr.as_ptr().add(self.init), elem);
self.init += 1;
},
None => return Ok(()),
}
}
}
const_if! {
"const_extras",
"Returns how many elements have been initialized.",
#[must_use]
#[inline]
pub const fn initialized(&self) -> usize {
self.init
}
}
const_if! {
"const_extras",
"Returns the total number of elements in the slice.",
#[must_use]
#[inline]
pub const fn full(&self) -> usize {
self.full
}
}
const_if! {
"const_extras",
"Returns `true` if every element in the slice has been initialized.",
#[must_use]
#[inline]
pub const fn is_full(&self) -> bool {
self.init == self.full
}
}
const_if! {
"const_extras",
"Returns `true` if no elements have been initialized.",
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.init == 0
}
}
const_if! {
"const_max",
"Copies as many elements from `slice` as will fit.\n\nOn success, all elements are \
copied and `Ok(())` is returned. If `slice.len() > remaining_capacity`, it copies as \
many elements as will fit, advances the initialized count to full, and returns \
`Err(excess)`.\n\n# Errors\n\nReturns `Err(excess)` if `slice.len() > \
remaining_capacity`.",
pub const fn copy_from_slice(&mut self, slice: &[T]) -> Result<(), usize>
where
T: Copy
{
let lim = self.full - self.init;
let to_copy = if slice.len() < lim { slice.len() } else { lim };
unsafe {
ptr::copy(
slice.as_ptr(),
self.ptr.as_ptr().add(self.init),
to_copy
);
}
self.init += to_copy;
let uncopied = slice.len() - to_copy;
if uncopied == 0 {
Ok(())
} else {
Err(uncopied)
}
}
}
}
impl<T, A: Alloc + ?Sized> Drop for SliceAllocGuard<'_, T, A> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(slice_ptr_from_raw_parts(self.ptr.as_ptr(), self.init));
self.alloc.dealloc(
self.ptr.cast(),
Layout::from_size_align_unchecked(T::SZ * self.full, T::ALN),
);
}
}
}
impl<T, A: Alloc + ?Sized> Deref for SliceAllocGuard<'_, T, A> {
type Target = NonNull<T>;
#[inline]
fn deref(&self) -> &NonNull<T> {
&self.ptr
}
}