use crate::raw::{AllocError, AllocResult, Storage, StorageWithCapacity};
use core::alloc::Layout;
use std::{
alloc::{alloc, handle_alloc_error, realloc},
mem::MaybeUninit,
ptr::NonNull,
};
type Heap<T> = Box<[MaybeUninit<T>]>;
enum OnFailure {
Abort,
Error,
}
pub(crate) unsafe fn box_from_raw_parts<T>(ptr: NonNull<T>, capacity: usize) -> Heap<T> {
let ptr = std::ptr::slice_from_raw_parts_mut(ptr.as_ptr().cast(), capacity);
Box::from_raw(ptr)
}
pub(crate) fn box_into_raw_parts<T>(b: Heap<T>) -> (NonNull<T>, usize) {
let ptr = Box::into_raw(b);
unsafe {
let capacity = (*ptr).len(); (NonNull::new_unchecked(ptr.cast()), capacity)
}
}
unsafe impl<T> Storage for Heap<T> {
type Item = T;
fn reserve(&mut self, new_capacity: usize) {
if self.len() < new_capacity {
let _ = reserve_slow(self, new_capacity, OnFailure::Abort);
}
}
fn try_reserve(&mut self, new_capacity: usize) -> AllocResult {
if self.len() < new_capacity {
reserve_slow(self, new_capacity, OnFailure::Error)
} else {
Ok(())
}
}
}
pub fn padding_needed_for(layout: Layout, align: usize) -> usize {
let len = layout.size();
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
len_rounded_up.wrapping_sub(len)
}
pub fn repeat(layout: Layout, n: usize) -> Result<Layout, ()> {
let padded_size = layout.size() + padding_needed_for(layout, layout.align());
let alloc_size = padded_size.checked_mul(n).ok_or(())?;
unsafe { Ok(Layout::from_size_align_unchecked(alloc_size, layout.align())) }
}
fn box_with_capacity<T>(capacity: usize) -> Heap<T> {
if core::mem::size_of::<T>() == 0 {
return Box::default()
}
let layout = repeat(Layout::new::<T>(), capacity).expect("Invalid layout");
let ptr = unsafe { alloc(layout) };
let ptr = match core::ptr::NonNull::new(ptr) {
Some(ptr) => ptr,
None => handle_alloc_error(layout),
};
unsafe { box_from_raw_parts(ptr.cast(), capacity) }
}
unsafe impl<T> StorageWithCapacity for Heap<T> {
fn with_capacity(cap: usize) -> Self { box_with_capacity(cap) }
}
#[cold]
#[inline(never)]
fn reserve_slow<T>(b: &mut Heap<T>, new_capacity: usize, on_failure: OnFailure) -> AllocResult {
assert!(new_capacity > b.len());
let (ptr, cap) = unsafe { box_into_raw_parts(std::ptr::read(b)) };
let new_capacity = new_capacity
.max(cap.checked_mul(2).expect("Could not grow further"))
.max(super::INIT_ALLOC_CAPACITY);
let layout = repeat(Layout::new::<T>(), new_capacity).expect("Invalid layout");
let ptr = if cap == 0 {
unsafe { alloc(layout) }
} else {
let new_layout = layout;
let old_layout = repeat(Layout::new::<T>(), cap).expect("Invalid layout");
unsafe { realloc(ptr.as_ptr().cast(), old_layout, new_layout.size()) }
};
let ptr = match (core::ptr::NonNull::new(ptr), on_failure) {
(Some(ptr), _) => ptr,
(None, OnFailure::Abort) => handle_alloc_error(layout),
(None, OnFailure::Error) => return Err(AllocError),
};
unsafe {
let new = box_from_raw_parts(ptr.cast(), new_capacity);
let old = std::mem::replace(b, new);
std::mem::forget(old);
}
Ok(())
}