extern crate alloc;
use super::table::{Entry, HomeGuard, Table, Word, link};
use super::{HopscotchMap, MAX_PROBE_DISTANCE, NEIGHBORHOOD_SIZE};
use alloc::boxed::Box;
use core::hash::{BuildHasher, Hash};
use core::sync::atomic::Ordering;
use kovan::{RetiredNode, retire};
#[cfg(test)]
use super::pause;
impl<K, V, S> HopscotchMap<K, V, S>
where
K: Hash + Eq + Clone + 'static,
V: Clone + 'static,
S: BuildHasher,
{
pub(super) fn try_insert(
table: &Table<K, V>,
home: &mut HomeGuard<'_>,
pending: Pending<K, V>,
only_if_absent: bool,
guard: &kovan::Guard,
) -> InsertResult<K, V> {
let hash = pending.key_hash();
let existing = table.find(home.idx, home.hops(), hash, pending.key(), guard);
if let Some((offset, found_word)) = existing {
let found_ptr = found_word.ptr();
let found = unsafe { &*found_ptr };
if only_if_absent {
return InsertResult::Exists(found.value.clone());
}
let old_value = found.value.clone();
table
.get_bucket(home.idx + offset)
.store(Word::of(pending.into_entry()), Ordering::Release);
unsafe { retire(found_ptr) };
return InsertResult::Replaced(old_value);
}
#[cfg(test)]
pause::at(pause::Point::InGuardBeforeClaim);
let mut entry = pending.into_entry();
for offset in 0..NEIGHBORHOOD_SIZE {
if !table.looks_free(home.idx + offset, guard) {
continue;
}
match Self::link_in(table, home, offset, entry, guard) {
Ok(linked) => return InsertResult::Linked(linked),
Err(back) => entry = back,
}
}
match Self::displace(table, home.idx, guard) {
Freed::Slot(offset) => match Self::link_in(table, home, offset, entry, guard) {
Ok(linked) => InsertResult::Linked(linked),
Err(back) => InsertResult::Retry(Pending::Built(back)),
},
Freed::Contended => InsertResult::Retry(Pending::Built(entry)),
Freed::Full => InsertResult::NeedResize(Pending::Built(entry)),
}
}
fn link_in(
table: &Table<K, V>,
home: &mut HomeGuard<'_>,
offset: usize,
entry: Box<Entry<K, V>>,
guard: &kovan::Guard,
) -> Result<*const Entry<K, V>, Box<Entry<K, V>>> {
home.publish_linked(offset);
link(table.get_bucket(home.idx + offset), entry, guard).inspect_err(|_| {
home.retract_linked(offset);
})
}
fn displace(table: &Table<K, V>, home: usize, guard: &kovan::Guard) -> Freed {
let reach = table.buckets.len().min(home + MAX_PROBE_DISTANCE);
let mut probe = home + NEIGHBORHOOD_SIZE..reach;
let Some(mut free) = probe.find(|&idx| table.looks_free(idx, guard)) else {
return Freed::Full;
};
while free >= home + NEIGHBORHOOD_SIZE {
free = match Self::move_toward(table, free, guard) {
Ok(nearer) => nearer,
Err(end) => return end,
};
}
Freed::Slot(free - home)
}
fn move_toward(table: &Table<K, V>, free: usize, guard: &kovan::Guard) -> Result<usize, Freed> {
let mut contended = false;
let nearest = free + 1 - NEIGHBORHOOD_SIZE;
for from in nearest..free {
let word = table.get_bucket(from).load(Ordering::Acquire, guard);
let Some(entry) = word.entry() else {
return Ok(from);
};
let owner = table.bucket_index(entry.hash);
if free >= owner + NEIGHBORHOOD_SIZE {
continue;
}
let Some(mut owner_guard) = table.home_guard(owner) else {
contended = true;
continue;
};
if Self::move_entry(table, &mut owner_guard, from, free, word, guard)? {
return Ok(from);
}
contended = true;
}
if contended {
Err(Freed::Contended)
} else {
Err(Freed::Full)
}
}
fn move_entry(
table: &Table<K, V>,
owner: &mut HomeGuard<'_>,
from: usize,
to: usize,
word: Word<'_, K, V>,
guard: &kovan::Guard,
) -> Result<bool, Freed> {
let from_bucket = table.get_bucket(from);
if from_bucket.load(Ordering::Relaxed, guard) != word {
return Ok(false);
}
let linked = table.get_bucket(to).replace(
Word::free(),
word,
Ordering::Release,
Ordering::Relaxed,
guard,
);
if !linked {
return Err(Freed::Contended);
}
owner.publish_moved_in(to - owner.idx);
#[cfg(test)]
pause::at(pause::Point::MoveLinkedTwice);
from_bucket.store(Word::free(), Ordering::Release);
#[cfg(test)]
pause::at(pause::Point::MoveUnlinked);
owner.stage_unlinked(from - owner.idx);
Ok(true)
}
}
pub(super) enum Pending<K, V> {
Parts { hash: u64, key: K, value: V },
Built(Box<Entry<K, V>>),
}
impl<K, V> Pending<K, V> {
fn key_hash(&self) -> u64 {
match self {
Self::Parts { hash, .. } => *hash,
Self::Built(entry) => entry.hash,
}
}
pub(super) fn key(&self) -> &K {
match self {
Self::Parts { key, .. } => key,
Self::Built(entry) => &entry.key,
}
}
fn into_entry(self) -> Box<Entry<K, V>> {
match self {
Self::Parts { hash, key, value } => Box::new(Entry {
retired: RetiredNode::new(),
hash,
key,
value,
}),
Self::Built(entry) => entry,
}
}
}
pub(super) enum InsertResult<K, V> {
Linked(*const Entry<K, V>),
Replaced(V),
Exists(V),
NeedResize(Pending<K, V>),
Retry(Pending<K, V>),
}
enum Freed {
Slot(usize),
Contended,
Full,
}