use crate::core_alloc::{AllocError, Allocator, Layout};
use core::ffi::c_void;
use core::mem::MaybeUninit;
use core::ptr::{self, NonNull};
#[cfg(debug_assertions)]
use core::sync::atomic::{AtomicU64, Ordering};
use crate::mimalloc;
#[cfg(debug_assertions)]
pub(crate) static HEAP_NEW_COUNT: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
#[cfg(debug_assertions)]
pub(crate) static HEAP_DESTROY_COUNT: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
#[cfg(debug_assertions)]
#[inline]
fn debug_thread_stamp() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
std::thread_local!(static ID: u64 = NEXT.fetch_add(1, Ordering::Relaxed));
ID.with(|id| *id)
}
pub struct MimallocArena {
heap: NonNull<mimalloc::Heap>,
owns: bool,
#[cfg(debug_assertions)]
owning_thread: AtomicU64,
}
unsafe impl Send for MimallocArena {}
unsafe impl Sync for MimallocArena {}
impl Default for MimallocArena {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl MimallocArena {
#[inline]
pub fn new() -> Self {
#[cfg(debug_assertions)]
HEAP_NEW_COUNT.fetch_add(1, Ordering::Relaxed);
let heap = unsafe { mimalloc::mi_heap_new() };
let heap = NonNull::new(heap).unwrap_or_else(|| crate::out_of_memory());
Self {
heap,
owns: true,
#[cfg(debug_assertions)]
owning_thread: AtomicU64::new(debug_thread_stamp()),
}
}
#[inline]
pub fn borrowing_default() -> Self {
let heap = unsafe { mimalloc::mi_heap_main() };
let heap = NonNull::new(heap).unwrap_or_else(|| crate::out_of_memory());
Self {
heap,
owns: false,
#[cfg(debug_assertions)]
owning_thread: AtomicU64::new(0),
}
}
#[inline(always)]
fn assert_owning_thread(&self) {
#[cfg(debug_assertions)]
{
let owner = self.owning_thread.load(Ordering::Relaxed);
if owner == 0 {
return;
}
let cur = debug_thread_stamp();
debug_assert_eq!(
owner, cur,
"MimallocArena: mi_heap_* allocation on thread {cur}, \
but heap is owned by thread {owner} (mi_heap is not Sync for alloc)"
);
}
}
#[inline]
pub fn init() -> Self {
Self::new()
}
#[inline]
pub fn heap_ptr(&self) -> *mut mimalloc::Heap {
self.heap.as_ptr()
}
#[cold]
#[inline(never)]
pub fn reset(&mut self) {
debug_assert!(
self.owns,
"MimallocArena::reset() on a borrowing_default() arena — would destroy mi_heap_main()"
);
#[cfg(debug_assertions)]
{
HEAP_DESTROY_COUNT.fetch_add(1, Ordering::Relaxed);
HEAP_NEW_COUNT.fetch_add(1, Ordering::Relaxed);
}
unsafe { mimalloc::mi_heap_destroy(self.heap_ptr()) };
let heap = unsafe { mimalloc::mi_heap_new() };
self.heap = NonNull::new(heap).unwrap_or_else(|| crate::out_of_memory());
#[cfg(debug_assertions)]
self.owning_thread
.store(debug_thread_stamp(), Ordering::Relaxed);
}
#[inline]
pub fn reset_retain_with_limit(&mut self, limit: usize) -> bool {
if self.owns && self.allocated_bytes() <= limit {
#[cfg(debug_assertions)]
self.owning_thread
.store(debug_thread_stamp(), Ordering::Relaxed);
return true;
}
self.reset();
false
}
#[inline]
pub fn gc(&self) {
unsafe { mimalloc::mi_heap_collect(self.heap_ptr(), false) };
}
#[inline]
pub fn help_catch_memory_issues(&self) {
#[cfg(debug_assertions)]
{
self.gc();
mimalloc::mi_collect(false);
}
}
pub fn allocated_bytes(&self) -> usize {
extern "C" fn visit(
_heap: *const mimalloc::Heap,
area: *const mimalloc::mi_heap_area_t,
_block: *mut c_void,
_block_size: usize,
arg: *mut c_void,
) -> bool {
unsafe {
let total = &mut *arg.cast::<usize>();
*total += (*area).used.saturating_mul((*area).full_block_size);
}
true
}
let mut total: usize = 0;
unsafe {
mimalloc::mi_heap_visit_blocks(
self.heap_ptr(),
false,
Some(visit),
(&raw mut total).cast(),
);
}
total
}
#[inline]
pub fn owns_ptr(&self, addr: usize) -> bool {
unsafe { mimalloc::mi_heap_contains(self.heap_ptr(), core::ptr::without_provenance(addr)) }
}
#[inline]
fn aligned_alloc(&self, len: usize, align: usize) -> *mut u8 {
self.assert_owning_thread();
unsafe { heap_alloc_maybe_aligned(self.heap_ptr(), len, align) }
}
#[inline]
pub fn resize_in_place(&self, ptr: NonNull<u8>, _old_len: usize, new_len: usize) -> bool {
unsafe { !mimalloc::mi_expand(ptr.as_ptr().cast(), new_len).is_null() }
}
#[inline]
fn remap(&self, ptr: NonNull<u8>, _old_len: usize, new_len: usize, align: usize) -> *mut u8 {
self.assert_owning_thread();
unsafe {
mimalloc::mi_heap_realloc_aligned(self.heap_ptr(), ptr.as_ptr().cast(), new_len, align)
.cast()
}
}
#[inline]
pub fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
let p = self.aligned_alloc(layout.size(), layout.align());
NonNull::new(p).unwrap_or_else(|| crate::out_of_memory())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc<T>(&self, val: T) -> &mut T {
let p = self.alloc_layout(Layout::new::<T>()).cast::<T>();
unsafe {
p.as_ptr().write(val);
&mut *p.as_ptr()
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_str(&self, s: &str) -> &mut str {
let bytes = self.alloc_slice_copy(s.as_bytes());
unsafe { core::str::from_utf8_unchecked_mut(bytes) }
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> &mut [T] {
let layout = Layout::for_value(src);
let dst = self.alloc_layout(layout).cast::<T>();
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst.as_ptr(), src.len());
core::slice::from_raw_parts_mut(dst.as_ptr(), src.len())
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_clone<T: Clone>(&self, src: &[T]) -> &mut [T] {
self.alloc_slice_fill_iter(src.iter().cloned())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_default<T: Default>(&self, len: usize) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| T::default())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_copy<T: Copy>(&self, len: usize, value: T) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| value)
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_with<T, F>(&self, len: usize, mut f: F) -> &mut [T]
where
F: FnMut(usize) -> T,
{
let layout = Layout::array::<T>(len).unwrap_or_else(|_| crate::out_of_memory());
let dst = self.alloc_layout(layout).cast::<T>();
unsafe {
for i in 0..len {
dst.as_ptr().add(i).write(f(i));
}
core::slice::from_raw_parts_mut(dst.as_ptr(), len)
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_iter<T, I>(&self, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let mut iter = iter.into_iter();
let len = iter.len();
self.alloc_slice_fill_with(len, |_| {
iter.next()
.expect("ExactSizeIterator under-reported length")
})
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_uninit_slice<T>(&self, len: usize) -> &mut [MaybeUninit<T>] {
let layout = Layout::array::<T>(len).unwrap_or_else(|_| crate::out_of_memory());
let dst = self.alloc_layout(layout).cast::<MaybeUninit<T>>();
unsafe { core::slice::from_raw_parts_mut(dst.as_ptr(), len) }
}
#[inline]
pub fn std_allocator(&self) -> crate::StdAllocator {
crate::StdAllocator {
ptr: ptr::from_ref(self).cast_mut().cast(),
vtable: &HEAP_ALLOCATOR_VTABLE,
}
}
#[inline]
pub fn is_instance(alloc: &crate::StdAllocator) -> bool {
core::ptr::eq(alloc.vtable, &raw const HEAP_ALLOCATOR_VTABLE)
|| core::ptr::eq(alloc.vtable, &raw const GLOBAL_MIMALLOC_VTABLE)
}
#[inline]
pub fn get_thread_local_default() -> crate::StdAllocator {
crate::StdAllocator {
ptr: core::ptr::null_mut(),
vtable: &GLOBAL_MIMALLOC_VTABLE,
}
}
}
impl Drop for MimallocArena {
#[inline]
fn drop(&mut self) {
if !self.owns {
return;
}
#[cfg(debug_assertions)]
HEAP_DESTROY_COUNT.fetch_add(1, Ordering::Relaxed);
unsafe { mimalloc::mi_heap_destroy(self.heap_ptr()) };
}
}
#[inline(always)]
fn alloc_result(p: *mut u8, size: usize) -> Result<NonNull<[u8]>, AllocError> {
NonNull::new(p)
.map(|p| NonNull::slice_from_raw_parts(p, size))
.ok_or(AllocError)
}
unsafe impl Allocator for &MimallocArena {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let p = self.aligned_alloc(layout.size(), layout.align());
alloc_result(p, layout.size())
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.assert_owning_thread();
let p = unsafe {
mimalloc::mi_heap_zalloc_auto_align(self.heap_ptr(), layout.size(), layout.align())
};
alloc_result(p.cast(), layout.size())
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { crate::basic::mi_free_checked(ptr.as_ptr().cast(), layout.size(), layout.align()) }
}
#[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.remap(ptr, old.size(), new.size(), new.align()),
new.size(),
)
}
#[inline]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
let p = self.remap(ptr, old.size(), new.size(), new.align());
let p = NonNull::new(p).ok_or(AllocError)?;
unsafe { ptr::write_bytes(p.as_ptr().add(old.size()), 0, new.size() - old.size()) };
Ok(NonNull::slice_from_raw_parts(p, new.size()))
}
#[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(
self.remap(ptr, old.size(), new.size(), new.align()),
new.size(),
)
}
}
#[inline]
unsafe fn heap_alloc_maybe_aligned(heap: *mut mimalloc::Heap, len: usize, align: usize) -> *mut u8 {
let p = unsafe {
if mimalloc::must_use_aligned_alloc(align) {
mimalloc::mi_heap_malloc_aligned(heap, len, align)
} else {
mimalloc::mi_heap_malloc(heap, len)
}
};
#[cfg(debug_assertions)]
if !p.is_null() {
let usable = unsafe { mimalloc::mi_malloc_usable_size(p) };
debug_assert!(
usable >= len,
"mimalloc: allocated size is too small: {usable} < {len}"
);
}
p.cast()
}
unsafe fn vtable_alloc(ctx: *mut c_void, len: usize, a: crate::Alignment, _ra: usize) -> *mut u8 {
let arena = unsafe { &*ctx.cast::<MimallocArena>() };
arena.aligned_alloc(len, a.to_byte_units())
}
unsafe fn vtable_resize(
ctx: *mut c_void,
buf: &mut [u8],
_a: crate::Alignment,
new_len: usize,
_ra: usize,
) -> bool {
let arena = unsafe { &*ctx.cast::<MimallocArena>() };
arena.resize_in_place(
unsafe { NonNull::new_unchecked(buf.as_mut_ptr()) },
buf.len(),
new_len,
)
}
unsafe fn vtable_remap(
ctx: *mut c_void,
buf: &mut [u8],
a: crate::Alignment,
new_len: usize,
_ra: usize,
) -> *mut u8 {
let arena = unsafe { &*ctx.cast::<MimallocArena>() };
arena.remap(
unsafe { NonNull::new_unchecked(buf.as_mut_ptr()) },
buf.len(),
new_len,
a.to_byte_units(),
)
}
unsafe fn vtable_free(_ctx: *mut c_void, buf: &mut [u8], a: crate::Alignment, _ra: usize) {
unsafe { crate::basic::mi_free_checked(buf.as_mut_ptr().cast(), buf.len(), a.to_byte_units()) }
}
pub(crate) static HEAP_ALLOCATOR_VTABLE: crate::AllocatorVTable = crate::AllocatorVTable {
alloc: vtable_alloc,
resize: vtable_resize,
remap: vtable_remap,
free: vtable_free,
};
unsafe fn global_vtable_alloc(
_ctx: *mut c_void,
len: usize,
a: crate::Alignment,
_ra: usize,
) -> *mut u8 {
crate::default_alloc::malloc_aligned(len, a.to_byte_units()).cast()
}
pub(crate) static GLOBAL_MIMALLOC_VTABLE: crate::AllocatorVTable = crate::AllocatorVTable {
alloc: global_vtable_alloc,
resize: crate::basic::MimallocAllocator::resize_with_default_allocator,
remap: crate::basic::MimallocAllocator::remap_with_default_allocator,
free: crate::basic::default_allocator_free,
};
#[inline]
pub fn std_vtables() -> [&'static crate::AllocatorVTable; 2] {
[&HEAP_ALLOCATOR_VTABLE, &GLOBAL_MIMALLOC_VTABLE]
}
pub struct ArenaString<'a> {
buf: crate::core_alloc::AllocVec<u8, &'a MimallocArena>,
}
impl<'a> ArenaString<'a> {
#[inline]
pub fn new_in(arena: &'a MimallocArena) -> Self {
Self {
buf: crate::core_alloc::AllocVec::new_in(arena),
}
}
#[inline]
pub fn with_capacity_in(cap: usize, arena: &'a MimallocArena) -> Self {
Self {
buf: crate::core_alloc::AllocVec::with_capacity_in(cap, arena),
}
}
#[inline]
pub fn from_str_in(s: &str, arena: &'a MimallocArena) -> Self {
let mut buf = crate::core_alloc::AllocVec::with_capacity_in(s.len(), arena);
buf.extend_from_slice(s.as_bytes());
Self { buf }
}
#[inline]
pub fn push_str(&mut self, s: &str) {
self.buf.extend_from_slice(s.as_bytes());
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.buf) }
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.buf
}
#[inline]
pub fn len(&self) -> usize {
self.buf.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[inline]
pub fn into_bump_str(self) -> &'a str {
let bytes = self.buf.into_bump_slice();
unsafe { core::str::from_utf8_unchecked(bytes) }
}
}
impl core::fmt::Write for ArenaString<'_> {
#[inline]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.buf.extend_from_slice(s.as_bytes());
Ok(())
}
}
impl core::ops::Deref for ArenaString<'_> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
pub trait ArenaVecExt<'a, T> {
fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, arena: &'a MimallocArena) -> Self;
fn into_bump_slice(self) -> &'a [T];
fn into_bump_slice_mut(self) -> &'a mut [T];
fn bump(&self) -> &'a MimallocArena;
}
impl<'a, T> ArenaVecExt<'a, T> for crate::core_alloc::AllocVec<T, &'a MimallocArena> {
#[inline]
fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, arena: &'a MimallocArena) -> Self {
let iter = iter.into_iter();
let (lo, _) = iter.size_hint();
let mut v = crate::core_alloc::AllocVec::with_capacity_in(lo, arena);
v.extend(iter);
v
}
#[inline]
fn into_bump_slice(self) -> &'a [T] {
&*self.leak()
}
#[inline]
fn into_bump_slice_mut(self) -> &'a mut [T] {
self.leak()
}
#[inline]
fn bump(&self) -> &'a MimallocArena {
*self.allocator()
}
}
impl<'a, T> ArenaVecExt<'a, T> for crate::BabyVec<'a, T> {
#[inline]
fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, arena: &'a MimallocArena) -> Self {
crate::vec_from_iter_in(iter, arena)
}
#[inline]
fn into_bump_slice(self) -> &'a [T] {
&*self.leak()
}
#[inline]
fn into_bump_slice_mut(self) -> &'a mut [T] {
self.leak()
}
#[inline]
fn bump(&self) -> &'a MimallocArena {
*self.allocator()
}
}