use core::ptr::{NonNull, null_mut};
use allocator_api2::alloc::{AllocError, Allocator, GlobalAlloc, Layout};
use crate::{base::Talc, base::binning::Binning, source::Source};
const RELEASE_LOCK_ON_REALLOC_LIMIT: usize = 0x4000;
#[derive(Debug)]
pub struct TalcLock<R: lock_api::RawMutex, S: Source, B: Binning> {
mutex: lock_api::Mutex<R, Talc<S, B>>,
}
impl<R: lock_api::RawMutex, S: Source, B: Binning> TalcLock<R, S, B> {
pub const fn new(source: S) -> Self {
Self { mutex: lock_api::Mutex::new(Talc::new(source)) }
}
#[track_caller]
pub fn lock(&self) -> lock_api::MutexGuard<'_, R, Talc<S, B>> {
self.mutex.lock()
}
pub fn try_lock(&self) -> Option<lock_api::MutexGuard<'_, R, Talc<S, B>>> {
self.mutex.try_lock()
}
pub fn get_mut(&mut self) -> &mut Talc<S, B> {
self.mutex.get_mut()
}
pub fn into_inner(self) -> Talc<S, B> {
self.mutex.into_inner()
}
}
unsafe impl<R: lock_api::RawMutex, S: Source, B: Binning> GlobalAlloc for TalcLock<R, S, B> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.lock().allocate(layout).map_or(null_mut(), |nn| nn.as_ptr())
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.lock().deallocate(ptr, layout)
}
#[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 lock = self.lock();
if lock.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 lock.allocate(new_layout) {
Some(ptr) => ptr.as_ptr(),
None => return null_mut(),
};
if old_layout.size() > RELEASE_LOCK_ON_REALLOC_LIMIT {
drop(lock);
allocation.copy_from_nonoverlapping(ptr, old_layout.size());
lock = self.lock();
} else {
allocation.copy_from_nonoverlapping(ptr, old_layout.size());
}
lock.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 lock = self.lock();
if new_size <= old_layout.size() {
lock.shrink(ptr, old_layout, new_size);
return ptr;
}
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
let allocation = match lock.allocate(new_layout) {
Some(ptr) => ptr.as_ptr(),
None => return null_mut(),
};
if old_layout.size() > RELEASE_LOCK_ON_REALLOC_LIMIT {
drop(lock);
allocation.copy_from_nonoverlapping(ptr, old_layout.size());
lock = self.lock();
} else {
allocation.copy_from_nonoverlapping(ptr, old_layout.size());
}
lock.deallocate(ptr, old_layout);
allocation
}
#[cfg(feature = "disable-realloc-in-place")]
unsafe fn realloc(&self, ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 {
let mut lock = self.lock();
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, old_layout.align()) };
let allocation = match unsafe { lock.allocate(new_layout) } {
Some(new_ptr) => new_ptr.as_ptr(),
None => return null_mut(),
};
unsafe {
let copy_count = core::cmp::min(old_layout.size(), new_size);
if copy_count > RELEASE_LOCK_ON_REALLOC_LIMIT {
drop(lock);
allocation.copy_from_nonoverlapping(ptr, copy_count);
lock = self.lock();
} else {
allocation.copy_from_nonoverlapping(ptr, copy_count);
}
core::ptr::copy_nonoverlapping(ptr, allocation, copy_count);
lock.deallocate(ptr, old_layout);
}
allocation
}
}
#[cfg_attr(feature = "disable-realloc-in-place", expect(dead_code))]
#[inline(always)]
fn is_aligned_to(ptr: *mut u8, align: usize) -> bool {
(ptr as usize).trailing_zeros() >= align.trailing_zeros()
}
#[inline(always)]
fn nonnull_slice_from_raw_parts(nn: NonNull<u8>, len: usize) -> NonNull<[u8]> {
unsafe { NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(nn.as_ptr(), len)) }
}
unsafe impl<R: lock_api::RawMutex, S: Source, B: Binning> Allocator for TalcLock<R, S, B> {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
if layout.size() == 0 {
return Ok(nonnull_slice_from_raw_parts(NonNull::dangling(), 0));
}
unsafe { self.lock().allocate(layout) }
.map(|nn| nonnull_slice_from_raw_parts(nn, layout.size()))
.ok_or(AllocError)
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() != 0 {
self.lock().deallocate(ptr.as_ptr(), layout);
}
}
#[cfg(not(any(feature = "disable-grow-in-place", feature = "disable-realloc-in-place")))]
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 self.allocate(new_layout);
} else if is_aligned_to(ptr.as_ptr(), new_layout.align()) {
if self.lock().try_grow_in_place(ptr.as_ptr(), old_layout, new_layout.size()) {
return Ok(nonnull_slice_from_raw_parts(ptr, new_layout.size()));
}
}
let mut lock = self.lock();
let allocation = lock.allocate(new_layout).ok_or(AllocError)?;
if old_layout.size() > RELEASE_LOCK_ON_REALLOC_LIMIT {
drop(lock);
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), old_layout.size());
lock = self.lock();
} else {
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), old_layout.size());
}
lock.deallocate(ptr.as_ptr(), old_layout);
Ok(nonnull_slice_from_raw_parts(allocation, new_layout.size()))
}
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(feature = "disable-realloc-in-place"))]
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());
if new_layout.size() == 0 {
if old_layout.size() > 0 {
self.lock().deallocate(ptr.as_ptr(), old_layout);
}
return Ok(nonnull_slice_from_raw_parts(NonNull::dangling(), 0));
}
if !is_aligned_to(ptr.as_ptr(), new_layout.align()) {
let mut lock = self.lock();
let allocation = lock.allocate(new_layout).ok_or(AllocError)?;
if new_layout.size() > RELEASE_LOCK_ON_REALLOC_LIMIT {
drop(lock);
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), new_layout.size());
lock = self.lock();
} else {
allocation.as_ptr().copy_from_nonoverlapping(ptr.as_ptr(), new_layout.size());
}
lock.deallocate(ptr.as_ptr(), old_layout);
return Ok(nonnull_slice_from_raw_parts(allocation, new_layout.size()));
}
self.lock().shrink(ptr.as_ptr(), old_layout, new_layout.size());
Ok(nonnull_slice_from_raw_parts(ptr, new_layout.size()))
}
}