use core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
marker::PhantomData,
ops::{Deref, DerefMut},
ptr::null_mut,
};
use crate::{
base::binning::Binning,
base::{Reserved, Talc},
ptr_utils::nonnull_slice_from_raw_parts,
source::Source,
};
use core::ptr::NonNull;
use allocator_api2::alloc::{AllocError, Allocator};
#[derive(Debug)]
pub struct TalcCell<S: Source, B: Binning> {
cell: UnsafeCell<Talc<S, B>>,
#[cfg(debug_assertions)]
borrowed_at: core::cell::Cell<Option<&'static core::panic::Location<'static>>>,
}
impl<S: Source, B: Binning> TalcCell<S, B> {
#[inline]
pub const fn new(source: S) -> Self {
Self {
cell: UnsafeCell::new(Talc::new(source)),
#[cfg(debug_assertions)]
borrowed_at: core::cell::Cell::new(None),
}
}
#[inline]
pub fn get_mut(&mut self) -> &mut Talc<S, B> {
self.cell.get_mut()
}
#[inline]
pub fn into_inner(self) -> Talc<S, B> {
self.cell.into_inner()
}
#[inline]
#[track_caller]
unsafe fn borrow(&self) -> BorrowedTalc<'_, S, B> {
#[cfg(debug_assertions)]
{
if let Some(borrowed_at) = self.borrowed_at.take() {
panic!(
"Tried to borrow the Talc, was borrowed previously at {}:{}:{}. Did the source attempt to use the TalcCell?",
borrowed_at.file(),
borrowed_at.line(),
borrowed_at.column(),
);
}
self.borrowed_at.set(Some(core::panic::Location::caller()));
}
BorrowedTalc {
ptr: unsafe { NonNull::new_unchecked(self.cell.get()) },
_phantom: PhantomData,
#[cfg(debug_assertions)]
borrow_release: &self.borrowed_at,
}
}
#[inline]
#[track_caller]
pub fn replace_source(&self, source: S) -> S {
unsafe {
core::mem::replace(&mut self.borrow().source, source)
}
}
#[cfg(feature = "counters")]
#[inline]
#[track_caller]
pub fn counters(&self) -> crate::base::Counters {
unsafe {
self.borrow().counters().clone()
}
}
#[inline]
#[track_caller]
pub unsafe fn reserved(&self, heap_end: NonNull<u8>) -> Reserved {
self.borrow().reserved(heap_end)
}
#[inline]
#[track_caller]
pub unsafe fn claim(&self, base: *mut u8, size: usize) -> Option<NonNull<u8>> {
self.borrow().claim(base, size)
}
#[inline]
#[track_caller]
pub unsafe fn extend(&self, heap_end: NonNull<u8>, new_end: *mut u8) -> NonNull<u8> {
self.borrow().extend(heap_end, new_end)
}
#[inline]
#[track_caller]
pub unsafe fn truncate(&self, heap_end: NonNull<u8>, new_end: *mut u8) -> Option<NonNull<u8>> {
self.borrow().truncate(heap_end, new_end)
}
#[inline]
#[track_caller]
pub unsafe fn resize(&self, heap_end: NonNull<u8>, new_end: *mut u8) -> Option<NonNull<u8>> {
self.borrow().resize(heap_end, new_end)
}
}
impl<S: Source + Clone, B: Binning> TalcCell<S, B> {
#[inline]
#[track_caller]
pub fn clone_source(&self) -> S {
unsafe {
self.borrow().source.clone()
}
}
}
struct BorrowedTalc<'b, S: Source, B: Binning> {
ptr: NonNull<Talc<S, B>>,
_phantom: PhantomData<&'b ()>,
#[cfg(debug_assertions)]
borrow_release: &'b core::cell::Cell<Option<&'static core::panic::Location<'static>>>,
}
impl<S: Source, B: Binning> Drop for BorrowedTalc<'_, S, B> {
#[inline]
fn drop(&mut self) {
#[cfg(debug_assertions)]
{
self.borrow_release.set(None);
}
}
}
impl<S: Source, B: Binning> Deref for BorrowedTalc<'_, S, B> {
type Target = Talc<S, B>;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref() }
}
}
impl<S: Source, B: Binning> DerefMut for BorrowedTalc<'_, S, B> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.ptr.as_mut() }
}
}
unsafe impl<S: Source, B: Binning> GlobalAlloc for TalcCell<S, B> {
#[inline]
#[track_caller]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.borrow().allocate(layout).map_or(null_mut(), |nn| nn.as_ptr())
}
#[inline]
#[track_caller]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.borrow().deallocate(ptr, layout)
}
#[inline]
#[track_caller]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let ptr = unsafe { self.alloc(layout) };
if !ptr.is_null() {
unsafe { core::ptr::write_bytes(ptr, 0, size) };
}
ptr
}
#[cfg(not(any(feature = "disable-grow-in-place", feature = "disable-realloc-in-place")))]
#[track_caller]
unsafe fn realloc(&self, ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 {
let mut talc = self.borrow();
if talc.try_realloc_in_place(ptr, old_layout, new_size) {
return ptr;
}
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
let allocation = match talc.allocate(new_layout) {
Some(ptr) => ptr.as_ptr(),
None => return null_mut(),
};
allocation.copy_from_nonoverlapping(ptr, old_layout.size());
talc.deallocate(ptr, old_layout);
allocation
}
#[cfg(all(feature = "disable-grow-in-place", not(feature = "disable-realloc-in-place")))]
#[track_caller]
unsafe fn realloc(&self, ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 {
let mut talc = self.borrow();
if new_size <= old_layout.size() {
talc.shrink(ptr, old_layout, new_size);
return ptr;
}
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
let Some(allocation) = talc.allocate(new_layout) else { return null_mut() };
allocation.as_ptr().copy_from_nonoverlapping(ptr, old_layout.size());
talc.deallocate(ptr, old_layout);
allocation.as_ptr()
}
#[cfg(feature = "disable-realloc-in-place")]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let new_ptr = unsafe { self.alloc(new_layout) };
if !new_ptr.is_null() {
unsafe {
core::ptr::copy_nonoverlapping(
ptr,
new_ptr,
core::cmp::min(layout.size(), new_size),
);
self.dealloc(ptr, layout);
}
}
new_ptr
}
}
unsafe impl<S: Source, B: Binning> Allocator for TalcCell<S, B> {
#[inline]
#[track_caller]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
if layout.size() == 0 {
let dangling = unsafe { NonNull::new_unchecked(layout.align() as *mut u8) };
return Ok(nonnull_slice_from_raw_parts(dangling, layout.size()));
}
match unsafe { self.borrow().allocate(layout) } {
Some(allocation) => Ok(nonnull_slice_from_raw_parts(allocation, layout.size())),
None => Err(AllocError),
}
}
#[inline]
#[track_caller]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() != 0 {
self.borrow().deallocate(ptr.as_ptr(), layout)
}
}
#[inline]
#[track_caller]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let ptr = self.allocate(layout)?;
unsafe { ptr.cast::<u8>().as_ptr().write_bytes(0, ptr.len()) }
Ok(ptr)
}
#[inline]
#[track_caller]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
let res = self.grow(ptr, old_layout, new_layout);
if let Ok(allocation) = res {
allocation
.as_ptr()
.cast::<u8>()
.add(old_layout.size())
.write_bytes(0, new_layout.size() - old_layout.size());
}
res
}
#[cfg(not(any(feature = "disable-grow-in-place", feature = "disable-realloc-in-place")))]
#[track_caller]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(new_layout.size() >= old_layout.size());
if old_layout.size() == 0 {
return Allocator::allocate(self, new_layout);
} else if crate::ptr_utils::is_aligned_to(ptr.as_ptr(), new_layout.align()) {
if self.borrow().try_grow_in_place(ptr.as_ptr(), old_layout, new_layout.size()) {
return Ok(nonnull_slice_from_raw_parts(ptr, new_layout.size()));
}
}
let allocation = self.borrow().allocate(new_layout).ok_or(AllocError)?;
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), old_layout.size());
self.borrow().deallocate(ptr.as_ptr(), old_layout);
Ok(nonnull_slice_from_raw_parts(allocation, new_layout.size()))
}
#[cfg(any(feature = "disable-grow-in-place", feature = "disable-realloc-in-place"))]
#[inline]
#[track_caller]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(new_layout.size() >= old_layout.size());
let new_ptr = self.allocate(new_layout)?;
unsafe {
core::ptr::copy_nonoverlapping(
ptr.as_ptr(),
new_ptr.as_ptr().cast(),
old_layout.size(),
);
self.deallocate(ptr, old_layout);
}
Ok(new_ptr)
}
#[cfg(not(feature = "disable-realloc-in-place"))]
#[track_caller]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(new_layout.size() <= old_layout.size());
let mut talc = self.borrow();
if new_layout.size() == 0 {
if old_layout.size() > 0 {
talc.deallocate(ptr.as_ptr(), old_layout);
}
let dangling = unsafe { NonNull::new_unchecked(new_layout.align() as *mut u8) };
return Ok(nonnull_slice_from_raw_parts(dangling, new_layout.size()));
}
if !crate::ptr_utils::is_aligned_to(ptr.as_ptr(), new_layout.align()) {
let allocation = talc.allocate(new_layout).ok_or(AllocError)?;
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), new_layout.size());
talc.deallocate(ptr.as_ptr(), old_layout);
return Ok(nonnull_slice_from_raw_parts(allocation, new_layout.size()));
}
talc.shrink(ptr.as_ptr(), old_layout, new_layout.size());
Ok(nonnull_slice_from_raw_parts(ptr, new_layout.size()))
}
#[cfg(feature = "disable-realloc-in-place")]
#[track_caller]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(new_layout.size() <= old_layout.size());
let new_ptr = self.allocate(new_layout)?;
unsafe {
core::ptr::copy_nonoverlapping(
ptr.as_ptr(),
new_ptr.as_ptr().cast(),
new_layout.size(),
);
self.deallocate(ptr, old_layout);
}
Ok(new_ptr)
}
}
pub struct TalcSyncCell<S: Source, B: Binning>(TalcCell<S, B>);
unsafe impl<S: Source, B: Binning> Sync for TalcSyncCell<S, B> {}
impl<S: Source, B: Binning> TalcSyncCell<S, B> {
pub const fn new_wasm(source: S) -> Self {
if cfg!(all(not(target_feature = "atomics"), target_family = "wasm")) {
Self(TalcCell::new(source))
} else {
panic!("Not running on single-threaded WebAssembly; `TalcSyncCell` is unsafe.")
}
}
pub const unsafe fn new(talc: TalcCell<S, B>) -> Self {
Self(talc)
}
}
unsafe impl<S: Source, B: Binning> GlobalAlloc for TalcSyncCell<S, B> {
#[track_caller]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.0.alloc(layout)
}
#[track_caller]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.0.dealloc(ptr, layout)
}
#[track_caller]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
self.0.realloc(ptr, layout, new_size)
}
}