use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ptr::{self, NonNull};
use kevy_hash::KevyHash;
use crate::iter::{Iter, Keys, Values};
pub(crate) const GROUP_WIDTH: usize = 16;
pub(crate) const EMPTY: u8 = 0xFF;
pub(crate) const DELETED: u8 = 0x80;
pub(crate) const MIN_CAP: usize = 16;
#[inline]
pub(crate) fn h2(hash: u64) -> u8 {
((hash >> 57) & 0x7F) as u8
}
#[inline(always)]
fn prefetch_t0(ptr: *const u8) {
#[cfg(all(target_arch = "x86_64", not(miri)))]
{
unsafe {
core::arch::x86_64::_mm_prefetch(
ptr as *const i8,
core::arch::x86_64::_MM_HINT_T0,
);
}
}
#[cfg(all(target_arch = "aarch64", not(miri)))]
{
unsafe {
core::arch::asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) ptr,
options(nostack, preserves_flags, readonly),
);
}
}
#[cfg(any(miri, not(any(target_arch = "x86_64", target_arch = "aarch64"))))]
{
let _ = ptr;
}
}
#[inline]
pub(crate) fn table_layout<KV>(cap: usize) -> (Layout, usize) {
let slots = Layout::array::<MaybeUninit<KV>>(cap).expect("slots layout overflow");
let meta = Layout::array::<u8>(cap + GROUP_WIDTH).expect("metadata layout overflow");
let (combined, meta_offset) = slots.extend(meta).expect("layout extend overflow");
(combined.pad_to_align(), meta_offset)
}
pub struct KevyMap<K, V> {
pub(crate) slots_ptr: NonNull<MaybeUninit<(K, V)>>,
pub(crate) metadata_ptr: NonNull<u8>,
pub(crate) cap: usize,
pub(crate) mask: usize,
pub(crate) occupied: usize,
pub(crate) deleted: usize,
_marker: PhantomData<(K, V)>,
}
unsafe impl<K: Send, V: Send> Send for KevyMap<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for KevyMap<K, V> {}
type SlotSlices<'a, K, V> = (&'a [u8], &'a [MaybeUninit<(K, V)>]);
pub(crate) enum ProbeOutcome {
Found(usize),
NotFound {
insert_at: usize,
via_tombstone: bool,
},
}
impl<K, V> KevyMap<K, V> {
pub fn new() -> Self {
Self {
slots_ptr: NonNull::dangling(),
metadata_ptr: NonNull::dangling(),
cap: 0,
mask: 0,
occupied: 0,
deleted: 0,
_marker: PhantomData,
}
}
pub fn with_capacity(cap_hint: usize) -> Self {
if cap_hint == 0 {
return Self::new();
}
let needed = cap_hint.saturating_mul(8).div_ceil(7);
let cap = needed.next_power_of_two().max(MIN_CAP);
Self::alloc_table(cap)
}
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 = unsafe { alloc(layout) };
if base.is_null() {
handle_alloc_error(layout);
}
let meta_byte_ptr = unsafe { base.add(meta_offset) };
unsafe { ptr::write_bytes(meta_byte_ptr, EMPTY, cap + GROUP_WIDTH) };
let slots_ptr = base as *mut MaybeUninit<(K, V)>;
let metadata_ptr = meta_byte_ptr;
kevy_madvise::advise_hugepage(base as *const u8, 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,
_marker: PhantomData,
}
}
#[inline]
pub(crate) fn set_meta(&mut self, i: usize, v: u8) {
debug_assert!(i < self.cap);
let i2 = (i.wrapping_sub(GROUP_WIDTH) & self.mask) + GROUP_WIDTH;
unsafe {
*self.metadata_ptr.as_ptr().add(i) = v;
*self.metadata_ptr.as_ptr().add(i2) = v;
}
}
#[inline]
pub fn len(&self) -> usize {
self.occupied
}
#[inline]
pub fn is_empty(&self) -> bool {
self.occupied == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.cap
}
pub fn clear(&mut self) {
if self.cap == 0 {
return;
}
if std::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) as *mut (K, V));
}
}
}
}
unsafe {
ptr::write_bytes(self.metadata_ptr.as_ptr(), EMPTY, self.cap + GROUP_WIDTH);
}
self.occupied = 0;
self.deleted = 0;
}
pub fn iter(&self) -> Iter<'_, K, V> {
let (metadata, slots) = self.as_slices();
Iter::new(metadata, slots)
}
pub fn iter_from_bucket(&self, start: usize) -> Iter<'_, K, V> {
let (metadata, slots) = self.as_slices();
Iter::with_start(metadata, slots, start)
}
pub fn keys(&self) -> Keys<'_, K, V> {
Keys::new(self.iter())
}
pub fn values(&self) -> Values<'_, K, V> {
Values::new(self.iter())
}
#[inline]
fn as_slices(&self) -> SlotSlices<'_, K, V> {
if self.cap == 0 {
return (&[], &[]);
}
unsafe {
(
std::slice::from_raw_parts(self.metadata_ptr.as_ptr(), self.cap),
std::slice::from_raw_parts(self.slots_ptr.as_ptr(), self.cap),
)
}
}
#[inline(always)]
pub fn prefetch_for_hash(&self, hash: u64) {
if self.cap == 0 {
return;
}
let idx = (hash as usize) & self.mask;
let ptr = unsafe { self.metadata_ptr.as_ptr().add(idx) };
prefetch_t0(ptr);
}
#[inline]
pub(crate) fn threshold(&self) -> usize {
self.cap - (self.cap / 8)
}
}
impl<K, V> Default for KevyMap<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K, Q, V> std::ops::Index<&Q> for KevyMap<K, V>
where
K: std::borrow::Borrow<Q>,
Q: KevyHash + Eq + ?Sized,
{
type Output = V;
fn index(&self, key: &Q) -> &V {
self.get(key).expect("no entry found for key")
}
}
impl<K, V> Drop for KevyMap<K, V> {
fn drop(&mut self) {
if self.cap == 0 {
return;
}
if std::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) as *mut (K, V));
}
}
}
}
let (layout, _) = table_layout::<(K, V)>(self.cap);
unsafe {
dealloc(self.slots_ptr.as_ptr() as *mut u8, layout);
}
}
}
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for KevyMap<K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
impl<K: KevyHash + Eq, V> FromIterator<(K, V)> for KevyMap<K, V> {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
let iter = iter.into_iter();
let mut m = match iter.size_hint() {
(lo, Some(hi)) if hi <= lo.saturating_mul(2) => Self::with_capacity(hi),
(lo, _) => Self::with_capacity(lo),
};
for (k, v) in iter {
m.insert(k, v);
}
m
}
}
impl<K: KevyHash + Eq, V> Extend<(K, V)> for KevyMap<K, V> {
fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
for (k, v) in iter {
self.insert(k, v);
}
}
}
#[cfg(test)]
#[path = "map_tests.rs"]
mod tests;