use core::hash::{BuildHasher, Hash};
use core::mem::{self, MaybeUninit};
use core::ops::Range;
use core::slice;
use alloc::{boxed::Box, vec, vec::Vec};
use allocator_api2::alloc::{Allocator, Global, Layout};
use equivalent::Equivalent;
use crate::common::DefaultHashBuilder;
use crate::common::arena::{self, Arena, ArenaSlots, SlotEntry};
use crate::common::config::{GROUP_SIZE, INITIAL_CAPACITY};
use crate::common::control::{self, CTRL_EMPTY, CTRL_TOMBSTONE, ControlByte};
use crate::common::error::TryReserveError;
#[cfg(not(feature = "std"))]
use crate::common::float::FloatExt as _;
use crate::common::iter::RegionCursor;
use crate::common::math::{self, align, capacity, cast, probe};
use crate::macros;
use crate::map;
pub(crate) const MAX_FUNNEL_RESERVE_FRACTION: f64 = 1.0 / 8.0;
pub(crate) const FUNNEL_POW2_LEVELS: usize = 8;
#[inline]
const fn funnel_level_is_pow2(level_idx: usize) -> bool {
level_idx < FUNNEL_POW2_LEVELS
}
struct BucketLevel<T> {
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
bucket_count_mask: u32,
bucket_count: u32,
bucket_size_log2: u32,
salt: u32,
capacity: u32,
len: u32,
tombstones: u32,
}
unsafe impl<T: Send> Send for BucketLevel<T> {}
unsafe impl<T: Sync> Sync for BucketLevel<T> {}
impl<T> ArenaSlots<T> for BucketLevel<T> {
#[inline]
fn ctrl_ptr(&self) -> *mut u8 {
self.ctrl_ptr
}
#[inline]
fn data_ptr(&self) -> *mut MaybeUninit<T> {
self.data_ptr
}
#[inline]
fn capacity(&self) -> usize {
self.capacity as usize
}
}
impl<T> BucketLevel<T> {
fn new_at(
level_idx: usize,
bucket_count: u32,
bucket_width: u32,
pow2: bool,
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
) -> Self {
let cap = bucket_count.saturating_mul(bucket_width);
let bucket_count_mask = if bucket_count == 0 {
0
} else if pow2 {
bucket_count.saturating_sub(1)
} else {
u32::MAX
};
Self {
ctrl_ptr,
data_ptr,
bucket_count_mask,
bucket_count,
bucket_size_log2: bucket_width.trailing_zeros(),
salt: math::level_salt(level_idx),
capacity: cap,
len: 0,
tombstones: 0,
}
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
fn bucket_index(&self, key_hash: u64) -> usize {
let h = (key_hash as u32) ^ self.salt;
if self.bucket_count_mask == u32::MAX {
debug_assert!(self.bucket_count != 0);
(h % self.bucket_count) as usize
} else {
(h as usize) & self.bucket_count_mask as usize
}
}
#[inline]
fn bucket_range(&self, bucket_idx: usize) -> Range<usize> {
let start = bucket_idx << self.bucket_size_log2;
let size = 1usize << self.bucket_size_log2;
start..start + size
}
fn first_free_in_bucket(&self, key_hash: u64) -> Option<usize> {
if self.len >= self.capacity {
return None;
}
let bucket_idx = self.bucket_index(key_hash);
let bucket_range = self.bucket_range(bucket_idx);
debug_assert_eq!(bucket_range.start % GROUP_SIZE, 0);
if !bucket_range.start.is_multiple_of(GROUP_SIZE) {
unsafe { core::hint::unreachable_unchecked() };
}
let group_idx = bucket_range.start / GROUP_SIZE;
self.group(group_idx)
.free_mask()
.lowest()
.map(|offset| bucket_range.start + offset)
}
}
impl<K, V> BucketLevel<SlotEntry<K, V>> {
#[inline]
fn find_in_bucket<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
slot_out: Option<&mut Option<usize>>,
) -> LookupStep
where
Q: Equivalent<K> + ?Sized,
{
let wants_free = matches!(&slot_out, Some(out) if out.is_none());
if self.len == 0 {
if self.capacity == 0 {
return LookupStep::Continue;
}
if wants_free {
let bucket_idx = self.bucket_index(key_hash);
let slot_idx = bucket_idx << self.bucket_size_log2;
if let Some(out) = slot_out {
*out = Some(slot_idx);
}
}
if self.tombstones == 0 {
return LookupStep::StopSearch;
}
return LookupStep::Continue;
}
let bucket_idx = self.bucket_index(key_hash);
let bucket_range = self.bucket_range(bucket_idx);
debug_assert_eq!(bucket_range.start % GROUP_SIZE, 0);
if !bucket_range.start.is_multiple_of(GROUP_SIZE) {
unsafe { core::hint::unreachable_unchecked() };
}
let group = self.group(bucket_range.start / GROUP_SIZE);
for relative_idx in group.match_mask(key_fingerprint) {
let slot_idx = bucket_range.start + relative_idx;
let entry = unsafe { self.get_ref(slot_idx) };
if key.equivalent(&entry.key) {
return LookupStep::Found(slot_idx);
}
}
if wants_free
&& let Some(o) = group.free_mask().lowest()
&& let Some(out) = slot_out
{
*out = Some(bucket_range.start + o);
}
if group.has_empty() {
LookupStep::StopSearch
} else {
LookupStep::Continue
}
}
}
struct ProbeSeq {
group: usize,
step: usize,
}
impl ProbeSeq {
#[inline]
fn new(group: usize, step: usize) -> Self {
Self { group, step }
}
#[inline]
fn advance(&mut self, mask: usize) {
self.group = (self.group + self.step) & mask;
}
}
struct SpecialPrimary<T> {
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
capacity: u32,
group_count_mask: u32,
len: u32,
tombstones: u32,
}
unsafe impl<T: Send> Send for SpecialPrimary<T> {}
unsafe impl<T: Sync> Sync for SpecialPrimary<T> {}
impl<T> ArenaSlots<T> for SpecialPrimary<T> {
#[inline]
fn ctrl_ptr(&self) -> *mut u8 {
self.ctrl_ptr
}
#[inline]
fn data_ptr(&self) -> *mut MaybeUninit<T> {
self.data_ptr
}
#[inline]
fn capacity(&self) -> usize {
self.capacity as usize
}
}
impl<T> SpecialPrimary<T> {
fn new_at(
cap: u32,
group_count_mask: u32,
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
) -> Self {
Self {
ctrl_ptr,
data_ptr,
capacity: cap,
group_count_mask,
len: 0,
tombstones: 0,
}
}
#[inline]
fn group_count(&self) -> usize {
if self.capacity == 0 {
0
} else {
self.capacity as usize / GROUP_SIZE
}
}
#[inline]
fn group_start(&self, key_hash: u64) -> usize {
probe::hash_to_usize(key_hash.rotate_left(11)) & self.group_count_mask as usize
}
#[inline]
fn group_step(&self, key_hash: u64) -> usize {
(probe::hash_to_usize(key_hash.rotate_left(43)) | 1) & self.group_count_mask as usize
}
}
struct SpecialFallback<T> {
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
capacity: u32,
bucket_count: u32,
bucket_size_log2: u32,
len: u32,
tombstones: u32,
}
unsafe impl<T: Send> Send for SpecialFallback<T> {}
unsafe impl<T: Sync> Sync for SpecialFallback<T> {}
impl<T> ArenaSlots<T> for SpecialFallback<T> {
#[inline]
fn ctrl_ptr(&self) -> *mut u8 {
self.ctrl_ptr
}
#[inline]
fn data_ptr(&self) -> *mut MaybeUninit<T> {
self.data_ptr
}
#[inline]
fn capacity(&self) -> usize {
self.capacity as usize
}
}
impl<T> SpecialFallback<T> {
fn new_at(
cap: u32,
bucket_count: u32,
bucket_size_log2: u32,
ctrl_ptr: *mut u8,
data_ptr: *mut MaybeUninit<T>,
) -> Self {
Self {
ctrl_ptr,
data_ptr,
capacity: cap,
bucket_count,
bucket_size_log2,
len: 0,
tombstones: 0,
}
}
#[inline]
fn bucket_range(&self, bucket_idx: usize) -> Range<usize> {
let start = bucket_idx << self.bucket_size_log2;
let size = 1usize << self.bucket_size_log2;
let end = (start + size).min(self.capacity as usize);
start..end
}
#[inline]
fn bucket_a(&self, key_hash: u64) -> usize {
probe::hash_to_usize(key_hash.rotate_left(19)) % self.bucket_count as usize
}
#[inline]
fn bucket_b(&self, key_hash: u64) -> usize {
probe::hash_to_usize(key_hash.rotate_left(37)) % self.bucket_count as usize
}
#[inline]
fn bucket_info(&self, bucket_idx: usize) -> (Option<usize>, usize) {
let mut first_free = None;
let mut occupied_count = 0;
for slot_idx in self.bucket_range(bucket_idx) {
if self.control_at(slot_idx).is_free() {
first_free.get_or_insert(slot_idx);
} else {
occupied_count += 1;
}
}
(first_free, occupied_count)
}
}
struct SpecialArray<T> {
primary: SpecialPrimary<T>,
fallback: SpecialFallback<T>,
total_len: usize,
}
impl<T> SpecialArray<T> {
fn drain_occupied_with<F: FnMut(T)>(&mut self, mut f: F) {
self.primary.drain_values_and_clear(&mut f);
self.fallback.drain_values_and_clear(f);
}
}
struct OverflowHandler<K, V> {
special: SpecialArray<SlotEntry<K, V>>,
primary_probe_limit: usize,
}
impl<K, V> OverflowHandler<K, V> {
fn new(special: SpecialArray<SlotEntry<K, V>>, primary_probe_limit: usize) -> Self {
Self {
special,
primary_probe_limit,
}
}
#[cold]
#[inline(never)]
fn find_in_special<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
mut free_slot: FreeSlot<'_>,
) -> Option<SlotLocation>
where
Q: Equivalent<K> + ?Sized,
{
match self.find_in_special_primary(key_hash, key_fingerprint, key, free_slot.as_deref_mut())
{
LookupStep::Found(slot_idx) => {
return Some(SlotLocation::SpecialPrimary { slot_idx });
}
LookupStep::StopSearch => return None,
LookupStep::Continue => {}
}
self.find_in_special_fallback(key_hash, key_fingerprint, key, free_slot)
.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx })
}
#[inline]
fn find_in_special_primary<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
free_slot: FreeSlot<'_>,
) -> LookupStep
where
Q: Equivalent<K> + ?Sized,
{
let wants_free = matches!(&free_slot, Some(out) if out.is_none());
let primary = &self.special.primary;
if primary.capacity() == 0 || primary.len == 0 {
if wants_free && let Some(out) = free_slot {
*out = self
.first_free_in_special_primary(key_hash)
.map(|slot_idx| SlotLocation::SpecialPrimary { slot_idx });
}
return LookupStep::Continue;
}
let group_count = primary.group_count();
let group_limit = self.primary_probe_limit.min(group_count.max(1));
let mask = primary.group_count_mask as usize;
let mut local: Option<usize> = None;
let mut probe = ProbeSeq::new(primary.group_start(key_hash), primary.group_step(key_hash));
let outcome: LookupStep = 'probe: {
for _ in 0..group_limit {
let has_free = if wants_free && local.is_none() {
let slot = primary.first_free_in_group(probe.group);
if let Some(s) = slot {
local = Some(s);
}
slot.is_some()
} else {
primary.group_match_mask(probe.group, CTRL_EMPTY).any()
};
for relative_idx in primary.group_match_mask(probe.group, key_fingerprint) {
let slot_idx = probe.group * GROUP_SIZE + relative_idx;
let entry = unsafe { primary.get_ref(slot_idx) };
if key.equivalent(&entry.key) {
break 'probe LookupStep::Found(slot_idx);
}
}
if has_free && !primary.group_match_mask(probe.group, CTRL_TOMBSTONE).any() {
break 'probe LookupStep::StopSearch;
}
probe.advance(mask);
}
LookupStep::Continue
};
if wants_free && let Some(out) = free_slot {
*out = local.map(|slot_idx| SlotLocation::SpecialPrimary { slot_idx });
}
outcome
}
#[inline]
fn find_in_special_fallback<Q>(
&self,
key_hash: u64,
key_fingerprint: u8,
key: &Q,
free_slot: FreeSlot<'_>,
) -> Option<usize>
where
Q: Equivalent<K> + ?Sized,
{
let wants_free = matches!(&free_slot, Some(out) if out.is_none());
let fallback = &self.special.fallback;
if fallback.capacity() == 0 || fallback.len == 0 {
if wants_free && let Some(out) = free_slot {
*out = self
.first_free_in_special_fallback(key_hash)
.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx });
}
return None;
}
let bucket_a = fallback.bucket_a(key_hash);
let bucket_b = fallback.bucket_b(key_hash);
let mut local: Option<usize> = None;
let mut found: Option<usize> = None;
for bucket_idx in [bucket_a, bucket_b] {
let need_match = found.is_none();
let need_candidate = wants_free && local.is_none();
if !need_match && !need_candidate {
break;
}
let range = fallback.bucket_range(bucket_idx);
if need_candidate {
for slot_idx in range.clone() {
if fallback.control_at(slot_idx).is_free() {
local = Some(slot_idx);
break;
}
}
}
if need_match {
let controls = unsafe {
slice::from_raw_parts(fallback.ctrl_ptr().add(range.start), range.len())
};
let mut match_offset = 0;
while let Some(relative_idx) = control::find_next_fingerprint_in_controls(
controls,
key_fingerprint,
match_offset,
) {
let slot_idx = range.start + relative_idx;
let entry = unsafe { fallback.get_ref(slot_idx) };
if key.equivalent(&entry.key) {
found = Some(slot_idx);
break;
}
match_offset = relative_idx + 1;
}
}
}
if wants_free && let Some(out) = free_slot {
*out = local.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx });
}
found
}
fn first_free_in_special_primary(&self, key_hash: u64) -> Option<usize> {
let primary = &self.special.primary;
if primary.len as usize >= primary.capacity() {
return None;
}
let group_count = primary.group_count();
let group_limit = self.primary_probe_limit.min(group_count.max(1));
let mask = primary.group_count_mask as usize;
let mut probe = ProbeSeq::new(primary.group_start(key_hash), primary.group_step(key_hash));
for _ in 0..group_limit {
if let Some(slot_idx) = primary.first_free_in_group(probe.group) {
return Some(slot_idx);
}
probe.advance(mask);
}
None
}
fn first_free_in_special_fallback(&self, key_hash: u64) -> Option<usize> {
let fallback = &self.special.fallback;
if fallback.len as usize >= fallback.capacity() {
return None;
}
let bucket_a = fallback.bucket_a(key_hash);
let bucket_b = fallback.bucket_b(key_hash);
let (free_a, occupied_a) = fallback.bucket_info(bucket_a);
if bucket_a == bucket_b {
return free_a;
}
let (free_b, occupied_b) = fallback.bucket_info(bucket_b);
match (free_a, free_b) {
(Some(slot_a), Some(slot_b)) => {
if occupied_a <= occupied_b {
Some(slot_a)
} else {
Some(slot_b)
}
}
(free_a, free_b) => free_a.or(free_b),
}
}
fn place_new_special_primary_entry(
&mut self,
slot_idx: usize,
key: K,
value: V,
key_fingerprint: u8,
) {
let primary = &mut self.special.primary;
let was_tombstone = primary.control_at(slot_idx) == CTRL_TOMBSTONE;
primary.write_with_control(slot_idx, SlotEntry { key, value }, key_fingerprint);
primary.len += 1;
if was_tombstone {
primary.tombstones -= 1;
}
self.special.total_len += 1;
}
fn place_new_special_fallback_entry(
&mut self,
slot_idx: usize,
key: K,
value: V,
key_fingerprint: u8,
) {
let fallback = &mut self.special.fallback;
let was_tombstone = fallback.control_at(slot_idx) == CTRL_TOMBSTONE;
fallback.write_with_control(slot_idx, SlotEntry { key, value }, key_fingerprint);
fallback.len += 1;
if was_tombstone {
fallback.tombstones -= 1;
}
self.special.total_len += 1;
}
#[inline]
fn replace_special_primary_value(&mut self, slot_idx: usize, value: V) -> V {
let entry = unsafe { self.special.primary.get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
#[inline]
fn replace_special_fallback_value(&mut self, slot_idx: usize, value: V) -> V {
let entry = unsafe { self.special.fallback.get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
#[inline]
fn erase_special_primary(&mut self, slot_idx: usize) -> bool {
self.special.primary.erase(slot_idx)
}
#[inline]
fn erase_special_fallback(&mut self, slot_idx: usize) -> bool {
self.special.fallback.erase(slot_idx)
}
fn account_erased(&mut self, location: SlotLocation, wrote_tombstone: bool) {
match location {
SlotLocation::SpecialPrimary { .. } => {
self.special.primary.tombstones += u32::from(wrote_tombstone);
self.special.primary.len -= 1;
}
SlotLocation::SpecialFallback { .. } => {
self.special.fallback.tombstones += u32::from(wrote_tombstone);
self.special.fallback.len -= 1;
}
SlotLocation::Level { .. } => unreachable!("level location passed to overflow"),
}
self.special.total_len -= 1;
}
fn special_primary_needs_cleanup(&self) -> bool {
let primary = &self.special.primary;
primary.tombstones as usize > capacity::tombstone_cleanup_threshold(primary.capacity())
}
fn special_fallback_needs_cleanup(&self) -> bool {
let fallback = &self.special.fallback;
fallback.tombstones as usize > capacity::tombstone_cleanup_threshold(fallback.capacity())
}
fn region_needs_cleanup(&self, location: SlotLocation) -> bool {
match location {
SlotLocation::SpecialPrimary { .. } => self.special_primary_needs_cleanup(),
SlotLocation::SpecialFallback { .. } => self.special_fallback_needs_cleanup(),
SlotLocation::Level { .. } => unreachable!("level location passed to overflow"),
}
}
#[inline]
fn len(&self) -> usize {
self.special.total_len
}
#[inline]
fn primary(&self) -> &SpecialPrimary<SlotEntry<K, V>> {
&self.special.primary
}
#[inline]
fn fallback(&self) -> &SpecialFallback<SlotEntry<K, V>> {
&self.special.fallback
}
fn drain_special_into<F: FnMut((K, V))>(&mut self, mut f: F) {
self.special.drain_occupied_with(|entry| {
f((entry.key, entry.value));
});
}
fn wipe_special(&mut self) {
self.special.primary.clear_all_controls();
self.special.primary.len = 0;
self.special.primary.tombstones = 0;
self.special.fallback.clear_all_controls();
self.special.fallback.len = 0;
self.special.fallback.tombstones = 0;
self.special.total_len = 0;
}
fn clear_special(&mut self) {
self.special.primary.drop_values_and_clear();
self.special.primary.len = 0;
self.special.primary.tombstones = 0;
self.special.fallback.drop_values_and_clear();
self.special.fallback.len = 0;
self.special.fallback.tombstones = 0;
self.special.total_len = 0;
}
fn drop_values(&mut self) {
self.special.primary.drop_values();
self.special.fallback.drop_values();
}
#[inline]
unsafe fn take_special_primary_entry(&mut self, slot_idx: usize) -> SlotEntry<K, V> {
unsafe { self.special.primary.take(slot_idx) }
}
#[inline]
unsafe fn take_special_fallback_entry(&mut self, slot_idx: usize) -> SlotEntry<K, V> {
unsafe { self.special.fallback.take(slot_idx) }
}
#[inline]
unsafe fn special_primary_ref(&self, slot_idx: usize) -> &SlotEntry<K, V> {
unsafe { self.special.primary.get_ref(slot_idx) }
}
#[inline]
unsafe fn special_fallback_ref(&self, slot_idx: usize) -> &SlotEntry<K, V> {
unsafe { self.special.fallback.get_ref(slot_idx) }
}
#[inline]
unsafe fn special_primary_ptr(&self, slot_idx: usize) -> *mut SlotEntry<K, V> {
self.special.primary.slot_ptr(slot_idx)
}
#[inline]
unsafe fn special_fallback_ptr(&self, slot_idx: usize) -> *mut SlotEntry<K, V> {
self.special.fallback.slot_ptr(slot_idx)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotLocation {
Level { level_idx: usize, slot_idx: usize },
SpecialPrimary { slot_idx: usize },
SpecialFallback { slot_idx: usize },
}
type FreeSlot<'a> = Option<&'a mut Option<SlotLocation>>;
enum LookupStep {
Found(usize),
Continue,
StopSearch,
}
enum LevelMiss {
ChainClean,
MayContinue,
}
struct ResizeScheduler<K, V> {
target: usize,
pending: Vec<(K, V)>,
overflow: Vec<(K, V)>,
}
impl<K, V> ResizeScheduler<K, V> {
fn new(target: usize, pending: Vec<(K, V)>) -> Self {
Self {
target,
pending,
overflow: Vec::new(),
}
}
fn try_empty(target: usize, entry_capacity: usize) -> Result<Self, TryReserveError> {
let mut pending = Vec::new();
pending
.try_reserve(entry_capacity)
.map_err(|_| TryReserveError::AllocError)?;
Ok(Self::new(target, pending))
}
#[inline]
fn target(&self) -> usize {
self.target
}
fn advance_target(&mut self) -> Result<usize, TryReserveError> {
self.target = self
.target
.checked_mul(2)
.ok_or(TryReserveError::CapacityOverflow)?;
Ok(self.target)
}
}
pub struct FunnelTable<K, V, S = DefaultHashBuilder, A: Allocator + Clone = Global> {
levels: BucketLevelSlice<K, V>,
overflow: OverflowHandler<K, V>,
len: usize,
total_slots: usize,
max_insertions: usize,
reserve_fraction: f64,
max_populated_level: usize,
hash_builder: S,
alloc: A,
arena: Arena,
}
impl<K, V> ResizeScheduler<K, V>
where
K: Eq + Hash,
{
fn collect_from<S, A>(target: usize, table: &mut FunnelTable<K, V, S, A>) -> Self
where
S: BuildHasher,
A: Allocator + Clone,
{
let mut scheduler = Self::new(target, Vec::with_capacity(table.len));
table.drain_entries_into(&mut scheduler.pending);
scheduler
}
fn try_collect_from<S, A>(
target: usize,
table: &mut FunnelTable<K, V, S, A>,
) -> Result<Self, TryReserveError>
where
S: BuildHasher,
A: Allocator + Clone,
{
let mut scheduler = Self::try_empty(target, table.len)?;
table.drain_entries_into(&mut scheduler.pending);
Ok(scheduler)
}
fn reinsert_into<S, A>(&mut self, table: &mut FunnelTable<K, V, S, A>) -> bool
where
S: BuildHasher,
A: Allocator + Clone,
{
self.overflow.clear();
for (key, value) in self.pending.drain(..) {
if let Err(pair) = table.try_insert_new_entry_unchecked(key, value) {
self.overflow.push(pair);
}
}
if self.overflow.is_empty() {
return true;
}
table.drain_entries_into(&mut self.overflow);
mem::swap(&mut self.pending, &mut self.overflow);
false
}
}
unsafe impl<K: Send, V: Send, S: Send, A: Allocator + Clone + Send> Send
for FunnelTable<K, V, S, A>
{
}
unsafe impl<K: Sync, V: Sync, S: Sync, A: Allocator + Clone + Sync> Sync
for FunnelTable<K, V, S, A>
{
}
impl<K, V, S, A: Allocator + Clone> Drop for FunnelTable<K, V, S, A> {
fn drop(&mut self) {
let levels = &mut self.levels;
let overflow = &mut self.overflow;
self.arena.drop_table(&self.alloc, || {
for level in levels {
level.drop_values();
}
overflow.drop_values();
});
}
}
macros::declare_backend_aliases! {
table = FunnelTable,
map_no_lifetime {
"Open-addressed hash map using funnel hashing." FunnelHashMap => HashMap,
"Consuming iterator over owned `(K, V)`." FunnelIntoIter => IntoIter,
"Owned `K` iterator." FunnelIntoKeys => IntoKeys,
"Owned `V` iterator." FunnelIntoValues => IntoValues,
},
map_ref {
"A view into a single entry, occupied or vacant." FunnelEntry => Entry,
"View of an occupied entry." FunnelOccupiedEntry => OccupiedEntry,
"View of a vacant entry." FunnelVacantEntry => VacantEntry,
"Error returned by `try_insert` on key collision." FunnelOccupiedError => OccupiedError,
"Borrowing iterator over `(&K, &V)`." FunnelIter => Iter,
"Borrowing iterator over `(&K, &mut V)`." FunnelIterMut => IterMut,
"`&K` iterator." FunnelKeys => Keys,
"`&V` iterator." FunnelValues => Values,
"`&mut V` iterator." FunnelValuesMut => ValuesMut,
"Draining iterator that empties the map." FunnelDrain => Drain,
},
map_extract_if {
"Iterator yielding entries removed by `extract_if`." FunnelExtractIf
},
set_no_lifetime {
"Hash set using funnel hashing." FunnelHashSet => HashSet,
"Consuming iterator over set values." FunnelSetIntoIter => IntoIter,
},
set_ref {
"Borrowing iterator over set values." FunnelSetIter => Iter,
"Draining iterator that empties the set." FunnelSetDrain => Drain,
"Iterator yielding values removed by set `extract_if`." FunnelSetExtractIf => ExtractIf,
"Iterator over values present only in the first set." FunnelDifference => Difference,
"Iterator over values present in both sets." FunnelIntersection => Intersection,
"Iterator over values present in exactly one set." FunnelSymmetricDifference => SymmetricDifference,
"Iterator over values present in either set." FunnelUnion => Union,
"A view into a single set entry." FunnelSetEntry => Entry,
"View of an occupied set entry." FunnelSetOccupiedEntry => OccupiedEntry,
"View of a vacant set entry." FunnelSetVacantEntry => VacantEntry,
},
}
type BucketLevelSlice<K, V> = Box<[BucketLevel<SlotEntry<K, V>>]>;
type FunnelArenaBuild<K, V> = (Arena, BucketLevelSlice<K, V>, SpecialArray<SlotEntry<K, V>>);
type FunnelArenaInner<K, V> = (BucketLevelSlice<K, V>, SpecialArray<SlotEntry<K, V>>);
struct FunnelGeometry<'a> {
level_bucket_counts: &'a [usize],
bucket_width: usize,
primary_ctrl: usize,
fallback_ctrl: usize,
fallback_bucket_size: usize,
}
impl<'a> FunnelGeometry<'a> {
fn new(
level_bucket_counts: &'a [usize],
bucket_width: usize,
special_primary_capacity: usize,
special_fallback_capacity: usize,
fallback_bucket_size: usize,
) -> Self {
Self {
level_bucket_counts,
bucket_width: bucket_width.next_power_of_two(),
primary_ctrl: align::round_up_to_pow2_groups(special_primary_capacity),
fallback_ctrl: align::round_up_to_group(special_fallback_capacity),
fallback_bucket_size,
}
}
fn total_ctrl(&self) -> Result<usize, TryReserveError> {
let mut sum: usize = 0;
for (level_idx, &bc) in self.level_bucket_counts.iter().enumerate() {
let bc = if bc == 0 {
0
} else if funnel_level_is_pow2(level_idx) {
bc.checked_next_power_of_two()
.ok_or(TryReserveError::CapacityOverflow)?
} else {
bc
};
let part = bc
.checked_mul(self.bucket_width)
.ok_or(TryReserveError::CapacityOverflow)?;
sum = sum
.checked_add(part)
.ok_or(TryReserveError::CapacityOverflow)?;
}
sum.checked_add(self.primary_ctrl)
.and_then(|s| s.checked_add(self.fallback_ctrl))
.ok_or(TryReserveError::CapacityOverflow)
}
fn build_regions<K, V>(
&self,
arena_base: *mut u8,
data_base_off: usize,
) -> Result<FunnelArenaInner<K, V>, TryReserveError> {
let mut cursor = arena::LayoutCursor::<SlotEntry<K, V>>::new(arena_base, data_base_off)?;
let mut levels: Vec<BucketLevel<SlotEntry<K, V>>> = Vec::new();
levels
.try_reserve_exact(self.level_bucket_counts.len())
.map_err(|_| TryReserveError::AllocError)?;
let bw32 =
u32::try_from(self.bucket_width).map_err(|_| TryReserveError::CapacityOverflow)?;
for (level_idx, &bc_raw) in self.level_bucket_counts.iter().enumerate() {
let pow2 = funnel_level_is_pow2(level_idx);
let bc = u32::try_from(if bc_raw == 0 {
0
} else if pow2 {
bc_raw.next_power_of_two()
} else {
bc_raw
})
.map_err(|_| TryReserveError::CapacityOverflow)?;
let cap = bc.saturating_mul(bw32);
let (ctrl_ptr, data_ptr) = unsafe { cursor.reserve(cap)? };
levels.push(BucketLevel::new_at(
level_idx, bc, bw32, pow2, ctrl_ptr, data_ptr,
));
}
let primary_cap =
u32::try_from(self.primary_ctrl).map_err(|_| TryReserveError::CapacityOverflow)?;
let primary_gc_mask = u32::try_from(self.primary_ctrl / GROUP_SIZE)
.map_err(|_| TryReserveError::CapacityOverflow)?
.wrapping_sub(1);
let (primary_ctrl_ptr, primary_data_ptr) = unsafe { cursor.reserve(primary_cap)? };
let primary = SpecialPrimary::new_at(
primary_cap,
primary_gc_mask,
primary_ctrl_ptr,
primary_data_ptr,
);
let fallback_cap =
u32::try_from(self.fallback_ctrl).map_err(|_| TryReserveError::CapacityOverflow)?;
let fb_size = self.fallback_bucket_size.next_power_of_two();
let fb_count = u32::try_from(if fb_size == 0 {
0
} else {
self.fallback_ctrl.div_ceil(fb_size)
})
.map_err(|_| TryReserveError::CapacityOverflow)?;
let fb_log2 = u32::try_from(fb_size)
.map_err(|_| TryReserveError::CapacityOverflow)?
.trailing_zeros();
let (fallback_ctrl_ptr, fallback_data_ptr) = unsafe { cursor.reserve(fallback_cap)? };
let fallback = SpecialFallback::new_at(
fallback_cap,
fb_count,
fb_log2,
fallback_ctrl_ptr,
fallback_data_ptr,
);
Ok((
levels.into_boxed_slice(),
SpecialArray {
primary,
fallback,
total_len: 0,
},
))
}
fn try_alloc<K, V, A: Allocator + Clone>(
&self,
alloc: &A,
) -> Result<FunnelArenaBuild<K, V>, TryReserveError> {
let total_ctrl = self.total_ctrl()?;
let (arena_layout, data_base_off) = arena::layout_for::<K, V>(total_ctrl)?;
let arena = Arena::try_allocate_with_ctrl_zeroed(arena_layout, total_ctrl, alloc)?;
match self.build_regions::<K, V>(arena.as_ptr(), data_base_off) {
Ok((levels, special)) => Ok((arena, levels, special)),
Err(e) => {
arena.deallocate(alloc);
Err(e)
}
}
}
fn alloc<K, V, A: Allocator + Clone>(&self, alloc: &A) -> FunnelArenaBuild<K, V> {
self.try_alloc(alloc).unwrap_or_else(|_| {
let layout = match self
.total_ctrl()
.and_then(|tc| arena::layout_for::<K, V>(tc).map(|(layout, _)| layout))
{
Ok(layout) => layout,
Err(_) => Layout::from_size_align(1, 1).unwrap(),
};
allocator_api2::alloc::handle_alloc_error(layout)
})
}
}
struct FunnelSplit {
level_bucket_counts: Vec<usize>,
bucket_width: usize,
primary_ctrl: usize,
fallback_ctrl: usize,
fallback_bucket_size: usize,
primary_probe_limit: usize,
}
impl FunnelSplit {
fn for_slots(total_slots: usize, reserve_fraction: f64) -> Self {
let level_count = compute_level_count(reserve_fraction);
let bucket_width = align::round_up_to_group(compute_bucket_width(reserve_fraction));
let primary_probe_limit = probe::log_log_probe_limit(total_slots).max(1);
let mut special_capacity =
choose_special_capacity(total_slots, reserve_fraction, bucket_width);
let mut main_capacity = total_slots.saturating_sub(special_capacity);
let main_remainder = main_capacity % bucket_width.max(1);
if main_remainder != 0 {
main_capacity = main_capacity.saturating_sub(main_remainder);
special_capacity = total_slots.saturating_sub(main_capacity);
}
let total_main_buckets = main_capacity.checked_div(bucket_width).unwrap_or(0);
let level_bucket_counts = partition_funnel_buckets(total_main_buckets, level_count);
let fallback_bucket_size = (primary_probe_limit.saturating_mul(2)).max(2);
let primary_ctrl = align::round_up_to_pow2_groups(special_capacity.div_ceil(2));
let fallback_ctrl =
align::round_up_to_group(special_capacity.saturating_sub(special_capacity.div_ceil(2)));
Self {
level_bucket_counts,
bucket_width,
primary_ctrl,
fallback_ctrl,
fallback_bucket_size,
primary_probe_limit,
}
}
fn geometry(&self) -> FunnelGeometry<'_> {
FunnelGeometry::new(
&self.level_bucket_counts,
self.bucket_width,
self.primary_ctrl,
self.fallback_ctrl,
self.fallback_bucket_size,
)
}
}
struct FunnelRegions<K, V> {
levels: BucketLevelSlice<K, V>,
special: SpecialArray<SlotEntry<K, V>>,
}
impl<K, V> arena::RegionSet for FunnelRegions<K, V> {
fn drop_all_values(&mut self) {
for level in &mut self.levels {
level.drop_values();
}
self.special.primary.drop_values();
self.special.fallback.drop_values();
}
}
impl<K, V, S, A> FunnelTable<K, V, S, A>
where
K: Eq + Hash,
S: BuildHasher,
A: Allocator + Clone,
{
#[must_use]
pub fn with_capacity_and_reserve_fraction_and_hasher_in(
capacity: usize,
reserve_fraction: f64,
hash_builder: S,
alloc: A,
) -> Self {
let reserve_fraction =
capacity::sanitize_reserve_fraction(reserve_fraction).min(MAX_FUNNEL_RESERVE_FRACTION);
let total_slots = if capacity == 0 {
0
} else {
capacity::capacity_for(INITIAL_CAPACITY, capacity, reserve_fraction)
.expect("capacity overflow")
};
let max_insertions = capacity::max_insertions(total_slots, reserve_fraction);
let split = FunnelSplit::for_slots(total_slots, reserve_fraction);
let primary_probe_limit = split.primary_probe_limit;
let (arena, levels, special) = split.geometry().alloc(&alloc);
let overflow = OverflowHandler::new(special, primary_probe_limit);
Self {
levels,
overflow,
len: 0,
total_slots,
max_insertions,
reserve_fraction,
max_populated_level: 0,
hash_builder,
alloc,
arena,
}
}
fn insert_for_vacant_entry(&mut self, key: K, value: V, key_hash: u64) -> SlotLocation {
let key_fingerprint = control::control_fingerprint(key_hash);
let mut location = if self.len < self.max_insertions {
self.choose_slot_for_new_key(key_hash)
} else {
None
};
if location.is_none() {
let new_capacity = if self.total_slots == 0 {
INITIAL_CAPACITY
} else {
self.total_slots.saturating_mul(2)
};
self.resize(new_capacity);
location = Some(
self.choose_slot_for_new_key(key_hash)
.expect("no free slot found after resize"),
);
}
let final_location = location.expect("location set above");
self.place_new_entry(final_location, key, value, key_fingerprint);
final_location
}
fn insert_at_location_after_resize_check(
&mut self,
location: Option<SlotLocation>,
key_hash: u64,
key: K,
value: V,
key_fingerprint: u8,
) -> Option<V> {
let final_location = match location {
Some(loc) if self.len < self.max_insertions => loc,
_ => {
let new_capacity = if self.total_slots == 0 {
INITIAL_CAPACITY
} else {
self.total_slots.saturating_mul(2)
};
self.resize(new_capacity);
self.choose_slot_for_new_key(key_hash)
.expect("no free slot found after resize")
}
};
self.place_new_entry(final_location, key, value, key_fingerprint);
None
}
#[inline]
unsafe fn slot_ptr_at(&self, loc: SlotLocation) -> *mut SlotEntry<K, V> {
match loc {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let levels_ptr: *const BucketLevel<SlotEntry<K, V>> = self.levels.as_ptr();
let level = unsafe { &*levels_ptr.add(level_idx) };
level.slot_ptr(slot_idx)
}
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
self.overflow.special_primary_ptr(slot_idx)
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
self.overflow.special_fallback_ptr(slot_idx)
},
}
}
#[inline]
unsafe fn take_entry_at(&mut self, location: SlotLocation) -> SlotEntry<K, V> {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { self.levels[level_idx].take(slot_idx) },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
self.overflow.take_special_primary_entry(slot_idx)
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
self.overflow.take_special_fallback_entry(slot_idx)
},
}
}
#[inline]
fn erase_location(&mut self, location: SlotLocation) -> bool {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => self.levels[level_idx].erase(slot_idx),
SlotLocation::SpecialPrimary { slot_idx } => {
self.overflow.erase_special_primary(slot_idx)
}
SlotLocation::SpecialFallback { slot_idx } => {
self.overflow.erase_special_fallback(slot_idx)
}
}
}
#[inline]
fn account_erased_location(&mut self, location: SlotLocation, wrote_tombstone: bool) {
match location {
SlotLocation::Level { level_idx, .. } => {
let level = &mut self.levels[level_idx];
if wrote_tombstone {
level.tombstones += 1;
}
level.len -= 1;
}
SlotLocation::SpecialPrimary { .. } | SlotLocation::SpecialFallback { .. } => {
self.overflow.account_erased(location, wrote_tombstone);
}
}
self.len -= 1;
}
#[inline]
fn finish_counted_removal(&mut self, location: SlotLocation) {
let wrote_tombstone = self.erase_location(location);
self.account_erased_location(location, wrote_tombstone);
}
fn take_and_tombstone(&mut self, location: SlotLocation) -> (K, V) {
let removed = unsafe { self.take_entry_at(location) };
self.finish_counted_removal(location);
(removed.key, removed.value)
}
fn region_needs_cleanup(&self, location: SlotLocation) -> bool {
let (tombstones, cap) = match location {
SlotLocation::Level { level_idx, .. } => {
let level = &self.levels[level_idx];
(level.tombstones, level.capacity())
}
SlotLocation::SpecialPrimary { .. } | SlotLocation::SpecialFallback { .. } => {
return self.overflow.region_needs_cleanup(location);
}
};
tombstones as usize > capacity::tombstone_cleanup_threshold(cap)
}
fn wipe_all(&mut self) {
for level in &mut self.levels {
level.clear_all_controls();
level.len = 0;
level.tombstones = 0;
}
self.overflow.wipe_special();
self.len = 0;
self.max_populated_level = 0;
}
fn clear(&mut self) {
for level in &mut self.levels {
level.drop_values_and_clear();
level.len = 0;
level.tombstones = 0;
}
self.overflow.clear_special();
self.len = 0;
self.max_populated_level = 0;
}
fn try_resize(&mut self, new_capacity: usize) -> Result<(), TryReserveError>
where
S: Clone,
{
let mut new_map = Self::try_with_slots_and_reserve_fraction_and_hasher_in(
new_capacity,
self.reserve_fraction,
self.hash_builder.clone(),
self.alloc.clone(),
)?;
let mut scheduler = ResizeScheduler::try_collect_from(new_capacity, self)?;
loop {
if scheduler.reinsert_into(&mut new_map) {
*self = new_map;
return Ok(());
}
let target = scheduler.advance_target()?;
new_map = Self::try_with_slots_and_reserve_fraction_and_hasher_in(
target,
self.reserve_fraction,
self.hash_builder.clone(),
self.alloc.clone(),
)?;
}
}
fn try_with_slots_and_reserve_fraction_and_hasher_in(
total_slots: usize,
reserve_fraction: f64,
hash_builder: S,
alloc: A,
) -> Result<Self, TryReserveError> {
let reserve_fraction =
capacity::sanitize_reserve_fraction(reserve_fraction).min(MAX_FUNNEL_RESERVE_FRACTION);
let max_insertions = capacity::max_insertions(total_slots, reserve_fraction);
let split = FunnelSplit::for_slots(total_slots, reserve_fraction);
let primary_probe_limit = split.primary_probe_limit;
let (arena, levels, special) = split.geometry().try_alloc(&alloc)?;
let overflow = OverflowHandler::new(special, primary_probe_limit);
Ok(Self {
levels,
overflow,
len: 0,
total_slots,
max_insertions,
reserve_fraction,
max_populated_level: 0,
hash_builder,
alloc,
arena,
})
}
fn resize(&mut self, new_capacity: usize) {
let mut scheduler = ResizeScheduler::collect_from(new_capacity, self);
loop {
self.install_fresh_storage(scheduler.target());
if scheduler.reinsert_into(self) {
return;
}
scheduler
.advance_target()
.expect("capacity overflow during funnel resize retry");
}
}
fn drain_entries_into(&mut self, out: &mut Vec<(K, V)>) {
for level in &mut self.levels {
level.drain_values_and_clear(|entry| {
out.push((entry.key, entry.value));
});
}
self.overflow.drain_special_into(|entry| {
out.push(entry);
});
for level in &mut self.levels {
level.len = 0;
level.tombstones = 0;
}
self.overflow.wipe_special();
self.len = 0;
self.max_populated_level = 0;
}
fn install_fresh_storage(&mut self, new_capacity: usize) {
let split = FunnelSplit::for_slots(new_capacity, self.reserve_fraction);
let new_primary_probe_limit = split.primary_probe_limit;
let alloc = &self.alloc;
let (new_arena, new_levels, new_special) = split.geometry().alloc(alloc);
let new_overflow = OverflowHandler::new(new_special, new_primary_probe_limit);
let old_arena = mem::replace(&mut self.arena, new_arena);
self.levels = new_levels;
self.overflow = new_overflow;
self.total_slots = new_capacity;
self.max_insertions = capacity::max_insertions(new_capacity, self.reserve_fraction);
self.max_populated_level = 0;
old_arena.deallocate(alloc);
}
#[inline]
fn hash_key<Q>(&self, key: &Q) -> u64
where
Q: Hash + ?Sized,
{
self.hash_builder.hash_one(key)
}
#[inline]
fn choose_slot_for_new_key(&self, key_hash: u64) -> Option<SlotLocation> {
for (level_idx, level) in self.levels.iter().enumerate() {
if let Some(slot_idx) = level.first_free_in_bucket(key_hash) {
return Some(SlotLocation::Level {
level_idx,
slot_idx,
});
}
}
if let Some(slot_idx) = self.overflow.first_free_in_special_primary(key_hash) {
return Some(SlotLocation::SpecialPrimary { slot_idx });
}
self.overflow
.first_free_in_special_fallback(key_hash)
.map(|slot_idx| SlotLocation::SpecialFallback { slot_idx })
}
#[cold]
#[inline(never)]
fn find_in_special<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
free_slot: FreeSlot<'_>,
) -> Option<SlotLocation>
where
Q: Equivalent<K> + ?Sized,
{
self.overflow
.find_in_special(key, key_hash, key_fingerprint, free_slot)
}
fn find_in_levels<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
free_slot: FreeSlot<'_>,
) -> (Option<SlotLocation>, LevelMiss)
where
Q: Equivalent<K> + ?Sized,
{
let wants_free = matches!(&free_slot, Some(out) if out.is_none());
let mut local: Option<SlotLocation> = None;
for (level_idx, level) in self.levels.iter().enumerate() {
let mut slot_candidate: Option<usize> = None;
let out = if wants_free && local.is_none() {
Some(&mut slot_candidate)
} else {
None
};
let step = level.find_in_bucket(key_hash, key_fingerprint, key, out);
if let Some(slot_idx) = slot_candidate {
local = Some(SlotLocation::Level {
level_idx,
slot_idx,
});
}
match step {
LookupStep::Found(slot_idx) => {
return (
Some(SlotLocation::Level {
level_idx,
slot_idx,
}),
LevelMiss::MayContinue,
);
}
LookupStep::Continue => {}
LookupStep::StopSearch => {
if let Some(out) = free_slot {
*out = local;
}
return (None, LevelMiss::ChainClean);
}
}
}
if wants_free && let Some(out) = free_slot {
*out = local;
}
(None, LevelMiss::MayContinue)
}
#[inline]
fn replace_existing_value(&mut self, location: SlotLocation, value: V) -> V {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let entry = unsafe { self.levels[level_idx].get_mut(slot_idx) };
mem::replace(&mut entry.value, value)
}
SlotLocation::SpecialPrimary { slot_idx } => {
self.overflow.replace_special_primary_value(slot_idx, value)
}
SlotLocation::SpecialFallback { slot_idx } => self
.overflow
.replace_special_fallback_value(slot_idx, value),
}
}
#[inline]
fn try_insert_new_entry_unchecked(&mut self, key: K, value: V) -> Result<(), (K, V)> {
let key_hash = self.hash_key(&key);
let key_fingerprint = control::control_fingerprint(key_hash);
let Some(location) = self.choose_slot_for_new_key(key_hash) else {
return Err((key, value));
};
if let SlotLocation::Level {
level_idx,
slot_idx,
} = location
{
self.place_new_level_entry(level_idx, slot_idx, key, value, key_fingerprint);
} else {
self.place_new_entry(location, key, value, key_fingerprint);
}
Ok(())
}
#[inline]
fn place_new_entry(&mut self, location: SlotLocation, key: K, value: V, key_fingerprint: u8) {
match location {
SlotLocation::Level {
level_idx,
slot_idx,
} => self.place_new_level_entry(level_idx, slot_idx, key, value, key_fingerprint),
SlotLocation::SpecialPrimary { slot_idx } => {
self.place_new_special_primary_entry(slot_idx, key, value, key_fingerprint);
}
SlotLocation::SpecialFallback { slot_idx } => {
self.place_new_special_fallback_entry(slot_idx, key, value, key_fingerprint);
}
}
}
#[inline]
fn place_new_level_entry(
&mut self,
level_idx: usize,
slot_idx: usize,
key: K,
value: V,
key_fingerprint: u8,
) {
let level = &mut self.levels[level_idx];
let was_tombstone = level.control_at(slot_idx) == CTRL_TOMBSTONE;
level.write_with_control(slot_idx, SlotEntry { key, value }, key_fingerprint);
level.len += 1;
if was_tombstone {
level.tombstones -= 1;
}
if level_idx > self.max_populated_level {
self.max_populated_level = level_idx;
}
self.len += 1;
}
#[inline]
fn place_new_special_primary_entry(
&mut self,
slot_idx: usize,
key: K,
value: V,
key_fingerprint: u8,
) {
self.overflow
.place_new_special_primary_entry(slot_idx, key, value, key_fingerprint);
self.len += 1;
}
#[inline]
fn place_new_special_fallback_entry(
&mut self,
slot_idx: usize,
key: K,
value: V,
key_fingerprint: u8,
) {
self.overflow
.place_new_special_fallback_entry(slot_idx, key, value, key_fingerprint);
self.len += 1;
}
#[inline]
unsafe fn slot_ref(&self, loc: SlotLocation) -> &SlotEntry<K, V> {
match loc {
SlotLocation::Level {
level_idx,
slot_idx,
} => unsafe { self.levels[level_idx].get_ref(slot_idx) },
SlotLocation::SpecialPrimary { slot_idx } => unsafe {
self.overflow.special_primary_ref(slot_idx)
},
SlotLocation::SpecialFallback { slot_idx } => unsafe {
self.overflow.special_fallback_ref(slot_idx)
},
}
}
#[inline]
fn find_slot_location_with_hash<Q>(
&self,
key: &Q,
key_hash: u64,
key_fingerprint: u8,
) -> Option<SlotLocation>
where
Q: Equivalent<K> + ?Sized,
{
let level0 = unsafe { self.levels.get_unchecked(0) };
match level0.find_in_bucket(key_hash, key_fingerprint, key, None) {
LookupStep::Found(slot_idx) => {
return Some(SlotLocation::Level {
level_idx: 0,
slot_idx,
});
}
LookupStep::Continue => {}
LookupStep::StopSearch => return None,
}
if self.max_populated_level > 0 {
let search_limit = (self.max_populated_level + 1).min(self.levels.len());
let tail = unsafe { self.levels.get_unchecked(1..search_limit) };
for (offset, level) in tail.iter().enumerate() {
match level.find_in_bucket(key_hash, key_fingerprint, key, None) {
LookupStep::Found(slot_idx) => {
return Some(SlotLocation::Level {
level_idx: offset + 1,
slot_idx,
});
}
LookupStep::Continue => {}
LookupStep::StopSearch => return None,
}
}
}
if self.overflow.len() == 0 {
return None;
}
self.find_in_special(key, key_hash, key_fingerprint, None)
}
fn shrink_max_populated_level(&mut self) {
while self.max_populated_level > 0 && self.levels[self.max_populated_level].len == 0 {
self.max_populated_level -= 1;
}
}
}
impl<K, V, S, A> FunnelTable<K, V, S, A>
where
K: Eq + Hash,
S: BuildHasher,
A: Allocator + Clone,
{
#[cold]
fn scan_advance(&self, scan: &mut FunnelScan) -> Option<(*mut SlotEntry<K, V>, SlotLocation)> {
if !scan.region.started() {
if self.levels.is_empty() {
scan.phase = ScanPhase::Primary;
scan.region.enter(self.overflow.primary());
} else {
scan.region.enter(&self.levels[0]);
}
}
loop {
if let Some((ptr, slot_idx)) = scan.region.step::<SlotEntry<K, V>>() {
return Some((ptr, scan.location_at(slot_idx)));
}
match scan.phase {
ScanPhase::Levels => {
scan.level_idx += 1;
if scan.level_idx < self.levels.len() {
scan.region.enter(&self.levels[scan.level_idx]);
} else {
scan.phase = ScanPhase::Primary;
scan.region.enter(self.overflow.primary());
}
}
ScanPhase::Primary => {
scan.phase = ScanPhase::Fallback;
scan.region.enter(self.overflow.fallback());
}
ScanPhase::Fallback => {
scan.phase = ScanPhase::Done;
return None;
}
ScanPhase::Done => return None,
}
}
}
}
#[allow(private_interfaces)]
impl<K, V, S, A> map::TableBackend<K, V> for FunnelTable<K, V, S, A>
where
K: Eq + Hash,
S: BuildHasher,
A: Allocator + Clone,
{
type Location = SlotLocation;
type Hasher = S;
type Alloc = A;
#[inline]
fn hasher(&self) -> &S {
&self.hash_builder
}
#[inline]
fn allocator(&self) -> &A {
&self.alloc
}
#[inline]
fn len(&self) -> usize {
self.len
}
#[inline]
fn capacity(&self) -> usize {
self.max_insertions
}
#[inline]
fn total_slots(&self) -> usize {
self.total_slots
}
#[inline]
fn reserve_fraction(&self) -> f64 {
self.reserve_fraction
}
#[inline]
unsafe fn slot_ref(&self, loc: SlotLocation) -> &SlotEntry<K, V> {
unsafe { self.slot_ref(loc) }
}
#[inline]
unsafe fn slot_ptr(&self, loc: SlotLocation) -> *mut SlotEntry<K, V> {
unsafe { self.slot_ptr_at(loc) }
}
#[inline]
fn replace_value(&mut self, loc: SlotLocation, value: V) -> V {
self.replace_existing_value(loc, value)
}
#[inline]
fn find<Q>(&self, key: &Q, hash: u64, fingerprint: u8) -> Option<SlotLocation>
where
Q: Hash + Equivalent<K> + ?Sized,
{
self.find_slot_location_with_hash(key, hash, fingerprint)
}
#[inline]
fn insert_for_vacant(&mut self, key: K, value: V, hash: u64) -> SlotLocation {
self.insert_for_vacant_entry(key, value, hash)
}
fn insert(&mut self, key: K, value: V, key_hash: u64) -> Option<V>
where
K: Hash + Eq,
{
let key_fingerprint = control::control_fingerprint(key_hash);
let mut candidate: Option<SlotLocation> = None;
let (found, miss) =
self.find_in_levels(&key, key_hash, key_fingerprint, Some(&mut candidate));
if let Some(location) = found {
return Some(self.replace_existing_value(location, value));
}
if (matches!(miss, LevelMiss::ChainClean) || self.overflow.len() == 0)
&& let Some(SlotLocation::Level {
level_idx,
slot_idx,
}) = candidate
{
if self.len < self.max_insertions {
self.place_new_level_entry(level_idx, slot_idx, key, value, key_fingerprint);
return None;
}
return self.insert_at_location_after_resize_check(
candidate,
key_hash,
key,
value,
key_fingerprint,
);
}
if let Some(location) =
self.find_in_special(&key, key_hash, key_fingerprint, Some(&mut candidate))
{
return Some(self.replace_existing_value(location, value));
}
self.insert_at_location_after_resize_check(candidate, key_hash, key, value, key_fingerprint)
}
fn remove(&mut self, loc: SlotLocation) -> (K, V) {
let (kv, needs_cleanup) = match loc {
SlotLocation::Level {
level_idx,
slot_idx,
} => {
let level = &mut self.levels[level_idx];
let removed = unsafe { level.take(slot_idx) };
if level.erase(slot_idx) {
level.tombstones += 1;
}
level.len -= 1;
let needs_cleanup = level.tombstones as usize
> capacity::tombstone_cleanup_threshold(level.capacity());
self.len -= 1;
((removed.key, removed.value), needs_cleanup)
}
special => (
self.take_and_tombstone(special),
self.region_needs_cleanup(special),
),
};
self.shrink_max_populated_level();
if needs_cleanup {
self.resize(self.total_slots);
}
kv
}
#[inline]
fn tombstone_slot(&mut self, location: SlotLocation) {
self.erase_location(location);
}
#[inline]
fn extract_finish(&mut self, location: SlotLocation) {
self.finish_counted_removal(location);
}
type Scan = FunnelScan;
#[inline]
fn scan(&self) -> FunnelScan {
FunnelScan {
phase: ScanPhase::Levels,
level_idx: 0,
region: RegionCursor::new(),
}
}
#[inline]
fn scan_next(&self, scan: &mut FunnelScan) -> Option<(*mut SlotEntry<K, V>, SlotLocation)> {
if scan.region.started()
&& let Some((ptr, slot_idx)) = scan.region.step::<SlotEntry<K, V>>()
{
return Some((ptr, scan.location_at(slot_idx)));
}
self.scan_advance(scan)
}
#[inline]
fn with_capacity_and_reserve_fraction_and_hasher_in(
capacity: usize,
reserve_fraction: f64,
hash_builder: S,
alloc: A,
) -> Self {
Self::with_capacity_and_reserve_fraction_and_hasher_in(
capacity,
reserve_fraction,
hash_builder,
alloc,
)
}
#[inline]
fn resize(&mut self, new_capacity: usize) {
self.resize(new_capacity);
}
#[inline]
fn try_resize(&mut self, new_capacity: usize) -> Result<(), TryReserveError>
where
S: Clone,
{
self.try_resize(new_capacity)
}
#[inline]
fn clear(&mut self) {
self.clear();
}
fn wipe_all(&mut self) {
self.wipe_all();
}
fn clone_table(&self) -> Self
where
K: Clone,
V: Clone,
S: Clone,
{
self.clone_storage()
}
}
#[derive(Clone, Copy)]
enum ScanPhase {
Levels,
Primary,
Fallback,
Done,
}
#[derive(Clone)]
pub struct FunnelScan {
phase: ScanPhase,
level_idx: usize,
region: RegionCursor,
}
impl FunnelScan {
#[inline]
fn location_at(&self, slot_idx: usize) -> SlotLocation {
match self.phase {
ScanPhase::Levels => SlotLocation::Level {
level_idx: self.level_idx,
slot_idx,
},
ScanPhase::Primary => SlotLocation::SpecialPrimary { slot_idx },
ScanPhase::Fallback => SlotLocation::SpecialFallback { slot_idx },
ScanPhase::Done => unreachable!("cursor empty in Done phase"),
}
}
}
fn compute_level_count(reserve_fraction: f64) -> usize {
cast::ceil_to_usize((4.0 * (1.0 / reserve_fraction).log2() + 10.0).max(1.0))
}
fn compute_bucket_width(reserve_fraction: f64) -> usize {
cast::ceil_to_usize((2.0 * (1.0 / reserve_fraction).log2()).max(1.0))
}
fn choose_special_capacity(
total_capacity: usize,
reserve_fraction: f64,
bucket_size: usize,
) -> usize {
if total_capacity == 0 {
return 0;
}
let total_capacity_f64 = cast::usize_to_f64(total_capacity);
let lower_bound = cast::ceil_to_usize((reserve_fraction * total_capacity_f64) / 2.0);
let upper_bound = cast::floor_to_usize((3.0 * reserve_fraction * total_capacity_f64) / 4.0);
let lower_bound = lower_bound.min(total_capacity);
let upper_bound = upper_bound.min(total_capacity);
if lower_bound <= upper_bound {
for special_capacity in (lower_bound..=upper_bound).rev() {
if (total_capacity - special_capacity).is_multiple_of(bucket_size.max(1)) {
return special_capacity;
}
}
}
let target = cast::round_to_usize(
((5.0 * reserve_fraction * total_capacity_f64) / 8.0).clamp(0.0, total_capacity_f64),
);
let mut best_special_capacity = total_capacity % bucket_size.max(1);
let mut best_distance = usize::MAX;
for main_capacity in (0..=total_capacity).step_by(bucket_size.max(1)) {
let special_capacity = total_capacity - main_capacity;
let distance = special_capacity.abs_diff(target);
if distance < best_distance {
best_distance = distance;
best_special_capacity = special_capacity;
}
}
if best_special_capacity == 0 {
best_special_capacity = bucket_size.min(total_capacity);
}
best_special_capacity
}
fn partition_funnel_buckets(total_buckets: usize, level_count: usize) -> Vec<usize> {
if level_count == 0 {
return Vec::new();
}
if total_buckets == 0 {
return vec![0; level_count];
}
let first_level_guess = {
let ratio = 0.75f64;
let denom = 1.0 - ratio.powi(i32::try_from(level_count).expect("level count fits in i32"));
if denom <= 0.0 {
total_buckets.max(1)
} else {
cast::round_to_usize(
(((cast::usize_to_f64(total_buckets)) * (1.0 - ratio)) / denom).max(0.0),
)
}
};
for radius in 0..=total_buckets {
let lower = first_level_guess.saturating_sub(radius);
if let Some(bucket_counts) = build_funnel_bucket_sequence(total_buckets, level_count, lower)
{
return bucket_counts;
}
let upper = first_level_guess.saturating_add(radius).min(total_buckets);
if upper != lower
&& let Some(bucket_counts) =
build_funnel_bucket_sequence(total_buckets, level_count, upper)
{
return bucket_counts;
}
}
let mut fallback_counts = vec![0; level_count];
fallback_counts[0] = total_buckets;
fallback_counts
}
fn build_funnel_bucket_sequence(
total_buckets: usize,
level_count: usize,
first_level_bucket_count: usize,
) -> Option<Vec<usize>> {
if level_count == 0 || first_level_bucket_count > total_buckets {
return None;
}
let mut bucket_counts = Vec::with_capacity(level_count);
bucket_counts.push(first_level_bucket_count);
let mut remaining = total_buckets.saturating_sub(first_level_bucket_count);
let mut previous_bucket_count = first_level_bucket_count;
for level_idx in 1..level_count {
let levels_after = level_count - level_idx - 1;
let (min_next_bucket_count, max_next_bucket_count) =
next_bucket_count_bounds(previous_bucket_count);
let ideal_next_bucket_count = ((3 * previous_bucket_count + 2) / 4)
.clamp(min_next_bucket_count, max_next_bucket_count);
let mut chosen_bucket_count = None;
let mut best_distance = usize::MAX;
let candidate_upper_bound = max_next_bucket_count.min(remaining);
for candidate_bucket_count in min_next_bucket_count..=candidate_upper_bound {
let remaining_after_candidate = remaining - candidate_bucket_count;
let (tail_min_sum, tail_max_sum) =
possible_tail_sum_range(candidate_bucket_count, levels_after);
if remaining_after_candidate < tail_min_sum || remaining_after_candidate > tail_max_sum
{
continue;
}
let distance = candidate_bucket_count.abs_diff(ideal_next_bucket_count);
if distance < best_distance {
best_distance = distance;
chosen_bucket_count = Some(candidate_bucket_count);
if distance == 0 {
break;
}
}
}
let chosen_bucket_count = chosen_bucket_count?;
bucket_counts.push(chosen_bucket_count);
remaining -= chosen_bucket_count;
previous_bucket_count = chosen_bucket_count;
}
if remaining == 0 {
Some(bucket_counts)
} else {
None
}
}
fn next_bucket_count_bounds(current_bucket_count: usize) -> (usize, usize) {
let scaled = current_bucket_count.saturating_mul(3);
let min_next_bucket_count = scaled.saturating_sub(4).div_ceil(4);
let max_next_bucket_count = (scaled.saturating_add(4) / 4).min(current_bucket_count);
(
min_next_bucket_count,
max_next_bucket_count.max(min_next_bucket_count),
)
}
fn possible_tail_sum_range(start_bucket_count: usize, levels_after: usize) -> (usize, usize) {
let mut min_sum = 0;
let mut max_sum = 0;
let mut min_previous = start_bucket_count;
let mut max_previous = start_bucket_count;
for _ in 0..levels_after {
let (next_min, _) = next_bucket_count_bounds(min_previous);
let (_, next_max) = next_bucket_count_bounds(max_previous);
min_sum += next_min;
max_sum += next_max;
min_previous = next_min;
max_previous = next_max;
}
(min_sum, max_sum)
}
impl<K, V, S, A> FunnelTable<K, V, S, A>
where
K: Clone,
V: Clone,
S: Clone,
A: Allocator + Clone,
{
fn clone_storage(&self) -> Self {
let bucket_width = align::round_up_to_group(compute_bucket_width(self.reserve_fraction));
let primary_ctrl = self.overflow.primary().capacity as usize;
let fallback_ctrl = self.overflow.fallback().capacity as usize;
let level_bucket_counts: Vec<usize> = self
.levels
.iter()
.map(|l| l.bucket_count as usize)
.collect();
let fallback_bucket_size = (self.overflow.primary_probe_limit.saturating_mul(2)).max(2);
let (arena, levels, special) = FunnelGeometry::new(
&level_bucket_counts,
bucket_width,
primary_ctrl,
fallback_ctrl,
fallback_bucket_size,
)
.alloc(&self.alloc);
let mut guard = arena::ArenaDropGuard::new(
arena,
FunnelRegions { levels, special },
self.alloc.clone(),
);
for (dst, src_lvl) in guard
.regions_mut()
.levels
.iter_mut()
.zip(self.levels.iter())
{
dst.clone_region_from(src_lvl);
dst.len = src_lvl.len;
dst.tombstones = src_lvl.tombstones;
}
let special_mut = &mut guard.regions_mut().special;
{
let s = self.overflow.primary();
let d = &mut special_mut.primary;
d.clone_region_from(s);
d.len = s.len;
d.tombstones = s.tombstones;
}
{
let s = self.overflow.fallback();
let d = &mut special_mut.fallback;
d.clone_region_from(s);
d.len = s.len;
d.tombstones = s.tombstones;
}
special_mut.total_len = self.overflow.len();
let (arena, FunnelRegions { levels, special }) = guard.disarm();
let overflow = OverflowHandler::new(special, self.overflow.primary_probe_limit);
Self {
levels,
overflow,
len: self.len,
total_slots: self.total_slots,
max_insertions: self.max_insertions,
reserve_fraction: self.reserve_fraction,
max_populated_level: self.max_populated_level,
hash_builder: self.hash_builder.clone(),
alloc: self.alloc.clone(),
arena,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::hash::{BuildHasher, Hasher};
use crate::common::config::DEFAULT_RESERVE_FRACTION;
struct ConstHasher;
impl Hasher for ConstHasher {
fn finish(&self) -> u64 {
0
}
fn write(&mut self, _: &[u8]) {}
}
struct ConstHashBuilder;
impl BuildHasher for ConstHashBuilder {
type Hasher = ConstHasher;
fn build_hasher(&self) -> Self::Hasher {
ConstHasher
}
}
#[test]
fn resize_scheduler_owns_pending_entries_and_checked_growth() {
let mut scheduler = ResizeScheduler::new(64, vec![(1, 10), (2, 20)]);
assert_eq!(scheduler.target(), 64);
assert_eq!(scheduler.pending.len(), 2);
assert_eq!(scheduler.advance_target(), Ok(128));
assert_eq!(scheduler.target(), 128);
}
#[test]
fn funnel_layout_covers_capacity() {
let requested = 257;
let map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(requested);
assert!(
map.capacity() >= requested,
"capacity={} below requested={requested}",
map.capacity()
);
let table = map.table();
let level_capacity: usize = table.levels.iter().map(BucketLevel::capacity).sum();
let special_capacity =
table.overflow.special.primary.capacity() + table.overflow.special.fallback.capacity();
let total = level_capacity + special_capacity;
assert!(
total >= requested,
"total={total} below requested={requested}"
);
}
fn paper_geometry(total_slots: usize, reserve_fraction: f64) -> (usize, Vec<usize>) {
let level_count = compute_level_count(reserve_fraction);
let bucket_width = align::round_up_to_group(compute_bucket_width(reserve_fraction));
let special_capacity = choose_special_capacity(total_slots, reserve_fraction, bucket_width);
let mut main_capacity = total_slots.saturating_sub(special_capacity);
main_capacity -= main_capacity % bucket_width.max(1);
let total_main_buckets = main_capacity.checked_div(bucket_width).unwrap_or(0);
let bucket_width = bucket_width.next_power_of_two();
(
bucket_width,
partition_funnel_buckets(total_main_buckets, level_count),
)
}
#[test]
#[cfg_attr(miri, ignore)] fn funnel_layout_keeps_exact_bucket_counts_for_cold_levels() {
let map: FunnelHashMap<u64, u64> = FunnelHashMap::with_capacity(2_000_000);
let table = map.table();
let levels = &table.levels;
assert!(
levels.len() > FUNNEL_POW2_LEVELS,
"need a cold level; got {}",
levels.len()
);
let (_, paper_counts) = paper_geometry(table.total_slots, table.reserve_fraction);
assert!(
paper_counts[FUNNEL_POW2_LEVELS] > 0,
"cold level under test must be non-empty: {paper_counts:?}"
);
let cold = &levels[FUNNEL_POW2_LEVELS];
assert_eq!(
cold.bucket_count_mask,
u32::MAX,
"cold level must use modulo routing"
);
assert_eq!(cold.bucket_count as usize, paper_counts[FUNNEL_POW2_LEVELS]);
for hot in &levels[..FUNNEL_POW2_LEVELS] {
if hot.bucket_count != 0 {
assert_ne!(
hot.bucket_count_mask,
u32::MAX,
"hot level must use mask routing"
);
assert_eq!(
hot.bucket_count_mask,
hot.bucket_count - 1,
"pow2 level: mask == count - 1"
);
}
}
}
#[test]
#[cfg_attr(miri, ignore)] fn cold_exact_counts_shrink_the_arena() {
let map: FunnelHashMap<u64, u64> = FunnelHashMap::with_capacity(2_000_000);
let table = map.table();
let (bw, paper_counts) = paper_geometry(table.total_slots, table.reserve_fraction);
let pow2_ctrl: usize = paper_counts
.iter()
.map(|&bc| {
if bc == 0 {
0
} else {
bc.next_power_of_two() * bw
}
})
.sum();
let exact_ctrl: usize = paper_counts
.iter()
.enumerate()
.map(|(i, &bc)| {
if bc == 0 {
0
} else if i < FUNNEL_POW2_LEVELS {
bc.next_power_of_two() * bw
} else {
bc * bw
}
})
.sum();
assert!(
exact_ctrl < pow2_ctrl,
"cold-exact must cut ctrl bytes: {exact_ctrl} !< {pow2_ctrl}"
);
}
#[test]
fn partition_buckets_monotone_paper_invariant() {
for total in 0usize..=64 {
for levels in 1usize..=24 {
let p = partition_funnel_buckets(total, levels);
assert_eq!(p.len(), levels, "len mismatch for ({total}, {levels})");
assert_eq!(
p.iter().sum::<usize>(),
total,
"sum for ({total}, {levels})"
);
for w in p.windows(2) {
assert!(w[1] <= w[0], "non-monotone for ({total}, {levels}): {p:?}");
}
}
}
}
#[test]
fn special_primary_single_group_edge_case() {
let mut map: FunnelHashMap<u64, u64> = FunnelHashMap::with_capacity(1);
assert_eq!(
map.table().overflow.primary().group_count_mask,
0,
"regression assumes a single-group special primary"
);
for i in 0..16 {
map.insert(i, i * 3);
}
for i in 0..16 {
assert_eq!(map.get(&i), Some(&(i * 3)));
}
for i in 0..8 {
assert_eq!(map.remove(&i), Some(i * 3));
}
for i in 0..8 {
assert_eq!(map.get(&i), None);
}
for i in 8..16 {
assert_eq!(map.get(&i), Some(&(i * 3)));
}
}
#[test]
fn clear_then_reinsert_preserves_entries() {
let mut map: FunnelHashMap<u64, u64> = FunnelHashMap::with_capacity(512);
for i in 0..384 {
map.insert(i, i ^ 0xa5a5);
}
map.clear();
for i in 512..896 {
map.insert(i, i ^ 0x5a5a);
}
assert_eq!(map.len(), 384);
for i in 512..896 {
assert_eq!(map.get(&i), Some(&(i ^ 0x5a5a)));
}
}
#[test]
fn clear_empties_the_special_array() {
let mut map: FunnelHashMap<u64, u64, ConstHashBuilder> =
FunnelHashMap::with_capacity_and_hasher(512, ConstHashBuilder);
let budget = u64::try_from(map.capacity()).expect("capacity fits u64");
let mut inserted = 0;
while map.table().overflow.len() == 0 && inserted < budget {
map.insert(inserted, inserted);
inserted += 1;
}
assert!(
map.table().overflow.len() > 0,
"all-colliding keys should populate the special array"
);
map.clear();
assert_eq!(map.len(), 0);
assert_eq!(map.table().overflow.len(), 0);
}
#[test]
fn level_tombstone_reuse_decrements_counter() {
let mut map: FunnelHashMap<i32, i32, ConstHashBuilder> =
FunnelHashMap::with_capacity_and_reserve_fraction_and_hasher_in(
2048,
DEFAULT_RESERVE_FRACTION,
ConstHashBuilder,
Global,
);
let l0_bucket_size = 1usize << map.table().levels[0].bucket_size_log2;
for i in 0..i32::try_from(l0_bucket_size).unwrap() {
map.insert(i, i);
}
assert_eq!(map.table().levels[0].tombstones, 0);
assert_eq!(map.remove(&0), Some(0));
assert_eq!(map.table().levels[0].tombstones, 1);
map.insert(10_000, 10_000);
assert_eq!(map.table().levels[0].tombstones, 0);
assert_eq!(map.table().levels[0].len as usize, l0_bucket_size);
assert_eq!(map.get(&10_000), Some(&10_000));
}
#[test]
#[cfg_attr(miri, ignore)] fn delete_insert_cycles_trigger_rebuild() {
let n = 12_000;
let mut map = FunnelHashMap::with_capacity(n * 2);
for i in 0..n {
map.insert(i, i);
}
for i in 0..6000 {
assert!(map.remove(&i).is_some(), "remove {i} failed");
map.insert(i + n, i + n);
}
assert_eq!(map.len(), n);
for i in 6000..n {
assert_eq!(map.get(&i), Some(&i), "original key {i} missing");
}
for i in 0..6000 {
assert_eq!(
map.get(&(i + n)),
Some(&(i + n)),
"new key {} missing",
i + n
);
}
}
#[test]
fn retain_does_not_trigger_mid_iter_resize_with_clustered_tombstones() {
let mut map: FunnelHashMap<i32, i32> = FunnelHashMap::with_capacity(1024);
let max = i32::try_from(capacity::max_insertions(
map.capacity(),
DEFAULT_RESERVE_FRACTION,
))
.expect("test capacity fits i32");
for i in 0..max {
map.insert(i, i);
}
let initial_capacity = map.capacity();
map.retain(|k, _| k % 2 == 0);
let expected_count = (0..max).filter(|i| i % 2 == 0).count();
assert_eq!(map.len(), expected_count);
for i in 0..max {
if i % 2 == 0 {
assert_eq!(map.get(&i), Some(&i), "kept key {i} missing");
} else {
assert!(map.get(&i).is_none(), "dropped key {i} survived");
}
}
assert_eq!(
map.capacity(),
initial_capacity,
"retain must not change the slot count, only rehash in place"
);
}
#[test]
fn bucket_overflow_promotes_max_populated_level() {
let mut map: FunnelHashMap<i32, i32, ConstHashBuilder> =
FunnelHashMap::with_capacity_and_reserve_fraction_and_hasher_in(
2048,
DEFAULT_RESERVE_FRACTION,
ConstHashBuilder,
Global,
);
assert!(
map.table().levels.len() > 1,
"test requires multi-level layout"
);
let l0_bucket_size =
i32::try_from(1usize << map.table().levels[0].bucket_size_log2).unwrap();
for i in 0..=l0_bucket_size {
map.insert(i, i);
}
assert_eq!(
map.table().max_populated_level,
1,
"first bucket overflow should land in A_1, not the special array"
);
for i in 0..=l0_bucket_size {
assert_eq!(map.get(&i), Some(&i));
}
}
#[test]
#[cfg_attr(miri, ignore)] fn special_array_removal_updates_region_counts_once() {
let mut map: FunnelHashMap<i32, i32, ConstHashBuilder> =
FunnelHashMap::with_capacity_and_reserve_fraction_and_hasher_in(
2048,
DEFAULT_RESERVE_FRACTION,
ConstHashBuilder,
Global,
);
let mut inserted = 0i32;
let max_insertions = i32::try_from(map.capacity()).expect("test capacity fits i32");
while map.table().overflow.len() == 0 && inserted < max_insertions {
map.insert(inserted, inserted);
inserted += 1;
}
assert!(
map.table().overflow.len() > 0,
"test requires at least one special-array entry"
);
let fingerprint = control::control_fingerprint(0);
let special_key = (0..inserted)
.find(|key| {
matches!(
map.table()
.find_slot_location_with_hash(key, 0, fingerprint),
Some(
SlotLocation::SpecialPrimary { .. } | SlotLocation::SpecialFallback { .. }
)
)
})
.expect("inserted special entry must be findable");
let before_special = map.table().overflow.len();
let before_len = map.len();
assert_eq!(map.remove(&special_key), Some(special_key));
assert_eq!(map.table().overflow.len(), before_special - 1);
assert_eq!(map.len(), before_len - 1);
}
#[test]
fn special_fallback_insert_prefers_emptier_bucket() {
let mut table: FunnelTable<u64, u64, DefaultHashBuilder, Global> =
FunnelTable::with_capacity_and_reserve_fraction_and_hasher_in(
2048,
DEFAULT_RESERVE_FRACTION,
DefaultHashBuilder::default(),
Global,
);
let fallback = &mut table.overflow.special.fallback;
assert!(
fallback.bucket_count > 1,
"test requires at least two fallback buckets"
);
let key_hash = (0u64..10_000)
.map(|candidate| candidate.wrapping_mul(0x9E37_79B9_7F4A_7C15))
.find(|&candidate| fallback.bucket_a(candidate) != fallback.bucket_b(candidate))
.expect("must find two distinct fallback buckets");
let bucket_a = fallback.bucket_a(key_hash);
let bucket_b = fallback.bucket_b(key_hash);
let range_a = fallback.bucket_range(bucket_a);
let range_b = fallback.bucket_range(bucket_b);
assert!(
range_a.len() >= 3 && !range_b.is_empty(),
"test requires non-empty candidate buckets"
);
let occupied = control::control_fingerprint(0xabc);
fallback.set_control(range_a.start, occupied);
fallback.set_control(range_a.start + 1, occupied);
fallback.len = 2;
assert_eq!(
table.overflow.first_free_in_special_fallback(key_hash),
Some(range_b.start),
"fallback C should choose the emptier of the two paper buckets"
);
}
#[test]
fn fallback_tombstone_reuse_decrements_counter() {
let mut table: FunnelTable<u64, u64, DefaultHashBuilder, Global> =
FunnelTable::with_capacity_and_reserve_fraction_and_hasher_in(
2048,
DEFAULT_RESERVE_FRACTION,
DefaultHashBuilder::default(),
Global,
);
let slot_idx = 0;
table
.overflow
.special
.fallback
.set_control(slot_idx, CTRL_TOMBSTONE);
table.overflow.special.fallback.tombstones = 1;
table.overflow.place_new_special_fallback_entry(
slot_idx,
7,
11,
control::control_fingerprint(7),
);
assert_eq!(table.overflow.special.fallback.tombstones, 0);
}
#[test]
fn reserve_fraction_clamped_to_funnel_max() {
let map: FunnelHashMap<i32, i32> =
FunnelHashMap::with_capacity_and_reserve_fraction(256, 0.5);
assert!(
map.table().reserve_fraction <= MAX_FUNNEL_RESERVE_FRACTION,
"reserve_fraction={} not clamped to {MAX_FUNNEL_RESERVE_FRACTION}",
map.table().reserve_fraction
);
}
}