use std::alloc::Layout;
use std::fmt::Debug;
use std::ptr;
use std::ptr::NonNull;
use crate::error::Error;
use crate::error::ErrorKind::OutOfMemory;
use crate::{Alloc, AllocMut, Gc, GcMut};
pub const DEFAULT_ALLOC_RETRY_LIMIT: Option<u32> = Some(3);
pub trait Allocator {
type Alloc;
fn as_raw_allocator(&mut self) -> &mut Self::Alloc;
fn yield_point(&mut self);
fn request_gc(&mut self, request: CollectionType);
#[inline(always)]
fn alloc<T>(&mut self, val: T) -> Gc<T, Self::Alloc>
where
Self::Alloc: Alloc<T>,
{
self.alloc_with(|| val)
}
#[inline(always)]
fn alloc_mut<T>(&mut self, val: T) -> GcMut<T, Self::Alloc>
where
Self::Alloc: AllocMut<T>,
<Self::Alloc as Alloc<T>>::MutTy: From<T>,
{
self.alloc_with::<_, <Self::Alloc as Alloc<T>>::MutTy>(|| val.into())
}
#[inline(always)]
fn try_alloc<T>(&mut self, val: T) -> Result<Gc<T, Self::Alloc>, Error>
where
Self::Alloc: Alloc<T>,
{
self.try_alloc_with(|| val)
}
#[inline(always)]
fn alloc_with<F, T>(&mut self, f: F) -> Gc<T, Self::Alloc>
where
F: FnOnce() -> T,
Self::Alloc: Alloc<T>,
{
self.try_gc_alloc_with(DEFAULT_ALLOC_RETRY_LIMIT, f)
.unwrap_or_else(|err| failed_allocation(err))
}
#[inline(always)]
fn try_gc_alloc_with<F, T>(
&mut self,
retry_limit: Option<u32>,
f: F,
) -> Result<Gc<T, Self::Alloc>, Error>
where
F: FnOnce() -> T,
Self::Alloc: Alloc<T>,
{
let layout = Layout::new::<T>();
unsafe {
self.try_gc_alloc_init(retry_limit, layout, |ptr| {
ptr::write(ptr.as_ptr() as *mut T, f())
})
}
}
#[inline(always)]
unsafe fn try_gc_alloc_init<F, T>(
&mut self,
mut retry_limit: Option<u32>,
layout: Layout,
init: F,
) -> Result<Gc<T, Self::Alloc>, Error>
where
T: ?Sized,
F: FnOnce(NonNull<u8>),
Self::Alloc: Alloc<T>,
{
let handle = loop {
match unsafe { Alloc::<T>::try_alloc_layout(self.as_raw_allocator(), layout) } {
Ok(handle) => break handle,
Err(err) if err.kind() == OutOfMemory => {
match &mut retry_limit {
None => {}
Some(0) => return Err(err),
Some(x) => *x -= 1,
}
self.request_gc(CollectionType::AllocAtLeast(layout));
self.yield_point();
}
Err(err) => return Err(err),
};
};
unsafe {
let data_ptr = Alloc::<T>::handle_ptr(self.as_raw_allocator(), &handle);
debug_assert!(
data_ptr.as_ptr() as usize & (layout.align() - 1) == 0,
"GC allocation did not meet required alignment"
);
init(data_ptr);
Ok(Gc::from_raw(handle))
}
}
#[inline(always)]
fn try_gc_alloc_setup<F, T>(
&mut self,
retry_limit: Option<u32>,
init: F,
) -> Result<Gc<T, Self::Alloc>, Error>
where
T: ?Sized + Default,
F: FnOnce(&mut T),
Self::Alloc: Alloc<T>,
{
let layout = Layout::new::<T>();
unsafe {
self.try_gc_alloc_init(retry_limit, layout, |ptr| {
let mut_ref = &mut *ptr.cast().as_ptr();
*mut_ref = T::default();
init(mut_ref);
})
}
}
#[inline(always)]
fn try_alloc_with<F, T>(&mut self, f: F) -> Result<Gc<T, Self::Alloc>, Error>
where
F: FnOnce() -> T,
Self::Alloc: Alloc<T>,
{
self.try_gc_alloc_with(None, f)
}
#[inline(always)]
fn alloc_slice_copy<T>(&mut self, src: &[T]) -> Gc<[T], Self::Alloc>
where
T: Copy,
Self::Alloc: Alloc<[T]>,
{
let layout = Layout::new::<T>();
unsafe {
self.try_gc_alloc_init(DEFAULT_ALLOC_RETRY_LIMIT, layout, |ptr| {
ptr::copy_nonoverlapping(src.as_ptr(), ptr.as_ptr() as *mut T, src.len());
})
.unwrap_or_else(|err| failed_allocation(err))
}
}
#[inline(always)]
fn alloc_slice_clone<T>(&mut self, src: &[T]) -> Gc<[T], Self::Alloc>
where
T: Clone,
Self::Alloc: Alloc<[T]>,
{
self.alloc_slice_fill_with(src.len(), |index| src[index].clone())
}
#[inline(always)]
fn alloc_str(&mut self, src: &str) -> Gc<str, Self::Alloc>
where
Self::Alloc: Alloc<str>,
{
let layout = Layout::for_value(src.as_bytes());
unsafe {
self.try_gc_alloc_init(DEFAULT_ALLOC_RETRY_LIMIT, layout, |ptr| {
ptr::copy_nonoverlapping(src.as_ptr(), ptr.as_ptr(), src.len());
})
.unwrap_or_else(|err| failed_allocation(err))
}
}
#[inline(always)]
fn alloc_slice_fill_with<T, F>(&mut self, len: usize, f: F) -> Gc<[T], Self::Alloc>
where
F: FnMut(usize) -> T,
Self::Alloc: Alloc<[T]>,
{
self.try_alloc_slice_fill_with(len, f)
.unwrap_or_else(|err| failed_allocation(err))
}
#[inline(always)]
fn try_alloc_slice_fill_with<T, F>(
&mut self,
len: usize,
f: F,
) -> Result<Gc<[T], Self::Alloc>, Error>
where
F: FnMut(usize) -> T,
Self::Alloc: Alloc<[T]>,
{
self.try_gc_alloc_slice_fill_with(Some(0), len, f)
}
#[inline(always)]
fn try_gc_alloc_slice_fill_with<T, F>(
&mut self,
retry_limit: Option<u32>,
len: usize,
mut f: F,
) -> Result<Gc<[T], Self::Alloc>, Error>
where
F: FnMut(usize) -> T,
Self::Alloc: Alloc<[T]>,
{
let layout = Layout::array::<T>(len).unwrap_or_else(|err| failed_allocation(err));
unsafe {
self.try_gc_alloc_init(retry_limit, layout, |ptr| {
for index in 0..len {
ptr::write(ptr.cast::<T>().as_ptr().add(index), f(index));
}
})
}
}
#[inline(always)]
fn alloc_slice_fill_copy<T>(&mut self, len: usize, value: T) -> Gc<[T], Self::Alloc>
where
T: Copy,
Self::Alloc: Alloc<[T]>,
{
self.alloc_slice_fill_with(len, |_| value)
}
#[inline(always)]
fn alloc_slice_fill_clone<T>(&mut self, len: usize, value: &T) -> Gc<[T], Self::Alloc>
where
T: Clone,
Self::Alloc: Alloc<[T]>,
{
self.alloc_slice_fill_with(len, |_| value.clone())
}
#[inline(always)]
fn alloc_slice_fill_iter<T, I>(&mut self, iter: I) -> Gc<[T], Self::Alloc>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
Self::Alloc: Alloc<[T]>,
{
let mut iter = iter.into_iter();
self.alloc_slice_fill_with(iter.len(), |_| {
iter.next().expect("Iterator supplied too few elements")
})
}
#[inline(always)]
fn alloc_slice_fill_default<T>(&mut self, len: usize) -> Gc<[T], Self::Alloc>
where
T: Default,
Self::Alloc: Alloc<[T]>,
{
self.alloc_slice_fill_with(len, |_| T::default())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum CollectionType {
Full,
Partial,
AllocAtLeast(Layout),
Suggest,
Custom(u64),
}
#[cold]
#[inline(never)]
fn failed_allocation<T: Debug>(err: T) -> ! {
panic!("Failed to perform GC allocation: {:?}", err)
}