use alloc_crate::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr;
use core::ptr::NonNull;
use crate::map::{EMPTY, GROUP_WIDTH, KevyMap, MIN_CAP, table_layout};
const THP_BACKED_THRESHOLD: usize = 1024 * 1024;
impl<K, V> KevyMap<K, V> {
#[must_use]
pub fn table_allocation(&self) -> Option<(*const u8, Layout)> {
(self.cap != 0 && !self.mmap_backed).then(|| {
(self.slots_ptr.as_ptr().cast::<u8>().cast_const(), table_layout::<(K, V)>(self.cap).0)
})
}
pub(crate) fn alloc_table(cap: usize) -> Self {
debug_assert!(cap.is_power_of_two());
debug_assert!(cap >= MIN_CAP);
let (layout, meta_offset) = table_layout::<(K, V)>(cap);
let (base, mmap_backed) = if layout.size() >= THP_BACKED_THRESHOLD {
if let Some(p) = kevy_madvise::mmap_anon_aligned_2mb(layout.size()) {
(p.as_ptr(), true)
} else {
(fallback_alloc(layout), false)
}
} else {
(fallback_alloc(layout), false)
};
let meta_byte_ptr = unsafe { base.add(meta_offset) };
unsafe { ptr::write_bytes(meta_byte_ptr, EMPTY, cap + GROUP_WIDTH) };
let slots_ptr = base.cast::<MaybeUninit<(K, V)>>();
let metadata_ptr = meta_byte_ptr;
if !mmap_backed {
kevy_madvise::advise_hugepage(base.cast_const(), layout.size());
}
Self {
slots_ptr: unsafe { NonNull::new_unchecked(slots_ptr) },
metadata_ptr: unsafe { NonNull::new_unchecked(metadata_ptr) },
cap,
mask: cap - 1,
occupied: 0,
deleted: 0,
mmap_backed,
_marker: PhantomData,
}
}
}
#[inline]
#[must_use]
pub fn malloc_footprint(size: usize) -> usize {
let chunk = ((size + 8 + 15) & !15).max(32);
if size == 0 { 0 } else { chunk }
}
impl<K, V> KevyMap<K, V> {
#[inline]
pub fn footprint(&self) -> usize {
if self.cap == 0 {
return 0;
}
let kv = core::mem::size_of::<(K, V)>();
let size =
(self.cap * (kv + 1) + GROUP_WIDTH).next_multiple_of(core::mem::align_of::<(K, V)>());
debug_assert_eq!(size, table_layout::<(K, V)>(self.cap).0.size());
if self.mmap_backed { size.next_multiple_of(HUGE_PAGE) } else { malloc_footprint(size) }
}
pub fn grown_footprint(&self) -> usize {
let cap = if self.cap == 0 { MIN_CAP } else { self.cap * 2 };
let size = table_layout::<(K, V)>(cap).0.size();
let mapped = size >= THP_BACKED_THRESHOLD && cfg!(target_os = "linux") && !cfg!(miri);
if mapped { size.next_multiple_of(HUGE_PAGE) } else { malloc_footprint(size) }
}
#[inline]
pub fn room(&self) -> usize {
if self.cap == 0 {
0
} else {
self.threshold().saturating_sub(self.occupied + self.deleted)
}
}
#[inline]
pub(crate) fn grows_on_insert(&self) -> bool {
self.cap == 0 || (self.occupied + self.deleted) >= self.threshold()
}
}
impl<K: kevy_hash::KevyHash + Eq, V> KevyMap<K, V> {
#[allow(clippy::inline_always)]
#[inline(always)]
pub fn insert_sized(&mut self, key: K, value: V) -> (Option<V>, isize) {
if self.grows_on_insert() {
let before = self.footprint();
self.grow();
let grown = self.footprint() as isize - before as isize;
return (self.insert_with_room(key, value), grown);
}
(self.insert_with_room(key, value), 0)
}
}
const HUGE_PAGE: usize = 2 * 1024 * 1024;
fn fallback_alloc(layout: Layout) -> *mut u8 {
let p = unsafe { alloc(layout) };
if p.is_null() {
handle_alloc_error(layout);
}
p
}
impl<K, V> Drop for KevyMap<K, V> {
fn drop(&mut self) {
if self.cap == 0 {
return;
}
if core::mem::needs_drop::<(K, V)>() {
for i in 0..self.cap {
let meta = unsafe { *self.metadata_ptr.as_ptr().add(i) };
if meta & 0x80 == 0 {
unsafe {
ptr::drop_in_place(self.slots_ptr.as_ptr().add(i).cast::<(K, V)>());
}
}
}
}
let (layout, _) = table_layout::<(K, V)>(self.cap);
if self.mmap_backed {
unsafe {
kevy_madvise::munmap_2mb(self.slots_ptr.cast(), layout.size());
}
} else {
unsafe {
dealloc(self.slots_ptr.as_ptr().cast::<u8>(), layout);
}
}
}
}