pub use core::alloc::{GlobalAlloc, Layout, LayoutErr};
pub use liballoc::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, realloc};
#[cfg(feature = "std")]
pub use std::alloc::System;
use crate::{capacity_overflow, collections::TryReserveError, ptr::Unique};
use core::{
fmt,
intrinsics,
mem,
ptr::{self, NonNull},
};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AllocErr;
impl fmt::Display for AllocErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("memory allocation failed")
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AllocInit {
Uninitialized,
Zeroed,
}
#[derive(Debug)]
#[must_use = "`MemoryBlock` should be passed to `AllocRef::dealloc`"]
pub struct MemoryBlock {
ptr: Unique<u8>,
layout: Layout,
}
impl MemoryBlock {
#[inline]
pub const unsafe fn new(ptr: NonNull<u8>, layout: Layout) -> Self {
Self {
ptr: Unique::new_unchecked(ptr.as_ptr()),
layout,
}
}
#[inline]
pub const fn ptr(&self) -> NonNull<u8> {
unsafe { NonNull::new_unchecked(self.ptr.as_ptr()) }
}
#[inline]
pub const fn layout(&self) -> Layout {
self.layout
}
#[inline]
pub const fn size(&self) -> usize {
self.layout().size()
}
#[inline]
pub const fn align(&self) -> usize {
self.layout().align()
}
#[inline]
pub fn init(&mut self, init: AllocInit) {
unsafe { self.init_offset(init, 0) }
}
#[inline]
pub unsafe fn init_offset(&mut self, init: AllocInit, offset: usize) {
debug_assert!(
offset <= self.size(),
"`offset` must be smaller than or equal to `size()`"
);
match init {
AllocInit::Uninitialized => (),
AllocInit::Zeroed => self
.ptr()
.as_ptr()
.add(offset)
.write_bytes(0, self.size() - offset),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReallocPlacement {
MayMove,
InPlace,
}
pub unsafe trait AllocRef: Copy {
fn alloc(self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr>;
unsafe fn dealloc(self, memory: MemoryBlock);
unsafe fn grow(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<(), AllocErr> {
match placement {
ReallocPlacement::InPlace => Err(AllocErr),
ReallocPlacement::MayMove => {
let old_size = memory.size();
debug_assert!(
new_size > old_size,
"`new_size` must be greater than or equal to `memory.size()`"
);
if new_size == old_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
let new_memory = self.alloc(new_layout, init)?;
ptr::copy_nonoverlapping(
memory.ptr().as_ptr(),
new_memory.ptr().as_ptr(),
old_size,
);
self.dealloc(mem::replace(memory, new_memory));
Ok(())
}
}
}
unsafe fn shrink(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
) -> Result<(), AllocErr> {
match placement {
ReallocPlacement::InPlace => Err(AllocErr),
ReallocPlacement::MayMove => {
let old_size = memory.size();
debug_assert!(
new_size <= old_size,
"`new_size` must be smaller than or equal to `layout.size()`"
);
if new_size == old_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
let new_memory = self.alloc(new_layout, AllocInit::Uninitialized)?;
ptr::copy_nonoverlapping(
memory.ptr().as_ptr(),
new_memory.ptr().as_ptr(),
new_size,
);
self.dealloc(mem::replace(memory, new_memory));
Ok(())
}
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct Global;
unsafe impl AllocRef for Global {
#[inline]
fn alloc(self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
unsafe {
if layout.size() == 0 {
Ok(MemoryBlock::new(layout.dangling(), layout))
} else {
let raw_ptr = match init {
AllocInit::Uninitialized => alloc(layout),
AllocInit::Zeroed => alloc_zeroed(layout),
};
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
Ok(MemoryBlock::new(ptr, layout))
}
}
}
#[inline]
unsafe fn dealloc(self, memory: MemoryBlock) {
if memory.size() != 0 {
dealloc(memory.ptr().as_ptr(), memory.layout())
}
}
#[inline]
unsafe fn grow(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<(), AllocErr> {
let old_size = memory.size();
debug_assert!(
new_size >= old_size,
"`new_size` must be greater than or equal to `memory.size()`"
);
if old_size == new_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
match placement {
ReallocPlacement::InPlace => return Err(AllocErr),
ReallocPlacement::MayMove if memory.size() == 0 => {
*memory = self.alloc(new_layout, init)?
}
ReallocPlacement::MayMove => {
intrinsics::assume(new_size > old_size);
let ptr = realloc(memory.ptr().as_ptr(), memory.layout(), new_size);
*memory = MemoryBlock::new(NonNull::new(ptr).ok_or(AllocErr)?, new_layout);
memory.init_offset(init, old_size);
}
}
Ok(())
}
#[inline]
unsafe fn shrink(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
) -> Result<(), AllocErr> {
let old_size = memory.size();
debug_assert!(
new_size <= old_size,
"`new_size` must be smaller than or equal to `memory.size()`"
);
if old_size == new_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
match placement {
ReallocPlacement::InPlace => return Err(AllocErr),
ReallocPlacement::MayMove if new_size == 0 => {
let new_memory = MemoryBlock::new(new_layout.dangling(), new_layout);
let old_memory = mem::replace(memory, new_memory);
self.dealloc(old_memory)
}
ReallocPlacement::MayMove => {
intrinsics::assume(new_size < old_size);
let ptr = realloc(memory.ptr().as_ptr(), memory.layout(), new_size);
*memory = MemoryBlock::new(NonNull::new(ptr).ok_or(AllocErr)?, new_layout);
}
}
Ok(())
}
}
#[cfg(feature = "std")]
unsafe impl AllocRef for System {
#[inline]
fn alloc(self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
unsafe {
if layout.size() == 0 {
Ok(MemoryBlock::new(layout.dangling(), layout))
} else {
let raw_ptr = match init {
AllocInit::Uninitialized => GlobalAlloc::alloc(&self, layout),
AllocInit::Zeroed => GlobalAlloc::alloc_zeroed(&self, layout),
};
let ptr = NonNull::new(raw_ptr).ok_or(AllocErr)?;
Ok(MemoryBlock::new(ptr, layout))
}
}
}
#[inline]
unsafe fn dealloc(self, memory: MemoryBlock) {
if memory.size() != 0 {
GlobalAlloc::dealloc(&self, memory.ptr().as_ptr(), memory.layout())
}
}
#[inline]
unsafe fn grow(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<(), AllocErr> {
let old_size = memory.size();
debug_assert!(
new_size >= old_size,
"`new_size` must be greater than or equal to `memory.size()`"
);
if old_size == new_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
match placement {
ReallocPlacement::InPlace => return Err(AllocErr),
ReallocPlacement::MayMove if memory.size() == 0 => {
*memory = self.alloc(new_layout, init)?
}
ReallocPlacement::MayMove => {
intrinsics::assume(new_size > old_size);
let ptr =
GlobalAlloc::realloc(&self, memory.ptr().as_ptr(), memory.layout(), new_size);
*memory = MemoryBlock::new(NonNull::new(ptr).ok_or(AllocErr)?, new_layout);
memory.init_offset(init, old_size);
}
}
Ok(())
}
#[inline]
unsafe fn shrink(
self,
memory: &mut MemoryBlock,
new_size: usize,
placement: ReallocPlacement,
) -> Result<(), AllocErr> {
let old_size = memory.size();
debug_assert!(
new_size >= old_size,
"`new_size` must be smaller than or equal to `memory.size()`"
);
if old_size == new_size {
return Ok(());
}
let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
match placement {
ReallocPlacement::InPlace => return Err(AllocErr),
ReallocPlacement::MayMove if new_size == 0 => {
let new_memory = MemoryBlock::new(new_layout.dangling(), new_layout);
let old_memory = mem::replace(memory, new_memory);
self.dealloc(old_memory)
}
ReallocPlacement::MayMove => {
intrinsics::assume(new_size < old_size);
let ptr =
GlobalAlloc::realloc(&self, memory.ptr().as_ptr(), memory.layout(), new_size);
*memory = MemoryBlock::new(NonNull::new(ptr).ok_or(AllocErr)?, new_layout);
}
}
Ok(())
}
}
pub(crate) fn handle_reserve_error<T>(result: Result<T, TryReserveError>) -> T {
match result {
Ok(t) => t,
Err(TryReserveError::AllocError { layout }) => handle_alloc_error(layout),
Err(TryReserveError::CapacityOverflow) => capacity_overflow(),
}
}