use super::node::{Node, is_frozen, is_marked, ptr, word};
use super::table::TableRef;
use super::{Backoff, HashMap};
use crate::MaybeSend;
use core::borrow::Borrow;
use core::hash::{BuildHasher, Hash};
use core::sync::atomic::Ordering;
use kovan::{Atomic, Guard, retire_node};
pub(super) enum Found<'g, K: 'static, V: 'static> {
Hit {
prev: &'g Atomic<Node<K, V>>,
node: &'g Node<K, V>,
next: *mut Node<K, V>,
},
Miss { tail: &'g Atomic<Node<K, V>> },
Frozen,
}
impl<K: 'static, V: 'static, S> HashMap<K, V, S> {
#[inline(always)]
pub(super) fn table_ref(&self, guard: &Guard) -> TableRef<K, V> {
TableRef::from_raw(self.table.load(Ordering::Acquire, guard).as_raw())
}
}
impl<K, V, S> HashMap<K, V, S>
where
K: Hash + Eq + Clone + MaybeSend + 'static,
V: Clone + MaybeSend + 'static,
S: BuildHasher,
{
pub(super) fn find<'g, Q>(
&self,
hash: u64,
key: &Q,
guard: &'g Guard,
) -> (TableRef<K, V>, Found<'g, K, V>)
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let mut backoff = Backoff::new();
'retry: loop {
let table = self.table_ref(guard);
let mut prev: &'g Atomic<Node<K, V>> = table.bucket(table.bucket_index(hash));
let head = prev.load(Ordering::Acquire, guard).as_raw();
if is_frozen(head) {
return (table, Found::Frozen);
}
let mut cur = ptr(head);
loop {
if cur.is_null() {
return (table, Found::Miss { tail: prev });
}
let node: &'g Node<K, V> = unsafe { &*cur };
let next = node.next.load(Ordering::Acquire, guard).as_raw();
if is_frozen(next) {
return (table, Found::Frozen);
}
if is_marked(next) {
let succ = ptr(next);
if prev
.compare_exchange(
word(cur),
word(succ),
Ordering::AcqRel,
Ordering::Relaxed,
guard,
)
.is_err()
{
backoff.spin();
continue 'retry;
}
unsafe { retire_node(cur) };
cur = succ;
continue;
}
let next = ptr(next);
if node.hash == hash && node.key.borrow() == key {
return (table, Found::Hit { prev, node, next });
}
prev = &node.next;
cur = next;
}
}
}
#[cold]
pub(super) fn cleanup<Q>(&self, hash: u64, key: &Q, guard: &Guard)
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let _ = self.find(hash, key, guard);
}
#[inline]
pub(super) fn lookup<'g, Q>(
&self,
hash: u64,
key: &Q,
guard: &'g Guard,
) -> Option<&'g Node<K, V>>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
'restart: loop {
let table = self.table_ref(guard);
let mut prev: &'g Atomic<Node<K, V>> = table.bucket(table.bucket_index(hash));
let mut cur = ptr(prev.load(Ordering::Acquire, guard).as_raw());
let mut run: *mut Node<K, V> = core::ptr::null_mut();
loop {
if cur.is_null() {
return None;
}
let node: &'g Node<K, V> = unsafe { &*cur };
let next = node.next.load(Ordering::Acquire, guard).as_raw();
if !is_marked(next) {
if node.hash == hash && node.key.borrow() == key {
return Some(node);
}
prev = &node.next;
run = core::ptr::null_mut();
cur = ptr(next);
continue;
}
if run.is_null() {
run = cur;
}
if !still_links(prev, run, guard) {
continue 'restart;
}
cur = ptr(next);
}
}
}
}
#[inline]
pub(super) fn still_links<K, V>(
prev: &Atomic<Node<K, V>>,
run: *mut Node<K, V>,
guard: &Guard,
) -> bool {
let w = prev.load(Ordering::Acquire, guard).as_raw();
ptr(w) == run && !is_marked(w)
}