extern crate alloc;
use crate::sync::spin_hint;
use crate::sync::{AtomicBool, AtomicUsize};
use core::borrow::Borrow;
use core::hash::{BuildHasher, Hash};
use core::sync::atomic::Ordering;
use displace::{InsertResult, Pending};
use foldhash::fast::FixedState;
use kovan::{Atomic, CachePadded, alloc_node, drop_node, pin, retire_node};
use table::{HOP_MASK, HomeGuard, Table, Word, hop_bits};
pub use iter::{HopscotchIntoIter, HopscotchIter, HopscotchKeys, HopscotchValues};
mod conditional;
mod displace;
mod iter;
mod resize;
mod std_traits;
mod table;
const NEIGHBORHOOD_SIZE: usize = 32;
const INITIAL_CAPACITY: usize = 64;
#[inline(always)]
fn overfull(count: usize, capacity: usize) -> bool {
4 * count > 3 * capacity
}
#[inline(always)]
fn shrink_at(capacity: usize) -> usize {
if capacity > MIN_CAPACITY {
capacity.div_ceil(4)
} else {
0
}
}
const MIN_CAPACITY: usize = 64;
const MAX_PROBE_DISTANCE: usize = 512;
pub struct HopscotchMap<K: 'static, V: 'static, S = FixedState> {
table: Atomic<Table<K, V>>,
count: CachePadded<AtomicUsize>,
resizing: AtomicBool,
hasher: S,
}
impl<K, V, S> core::panic::UnwindSafe for HopscotchMap<K, V, S>
where
K: core::panic::UnwindSafe,
V: core::panic::UnwindSafe,
S: core::panic::UnwindSafe,
{
}
impl<K, V, S> core::panic::RefUnwindSafe for HopscotchMap<K, V, S>
where
K: core::panic::RefUnwindSafe,
V: core::panic::RefUnwindSafe,
S: core::panic::RefUnwindSafe,
{
}
enum Outcome<R, V> {
Linked(R),
Replaced(V),
Present(V),
}
type HeldHome<'g, K, V> = (&'g Table<K, V>, HomeGuard<'g>);
enum Blocked<'g, K, V> {
Resizing,
Vacant,
Held(&'g Table<K, V>),
}
impl<K: 'static, V: 'static, S> HopscotchMap<K, V, S> {
pub fn with_hasher(hasher: S) -> Self {
Self::with_capacity_and_hasher(INITIAL_CAPACITY, hasher)
}
pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
let table = Table::new(capacity);
Self {
table: Atomic::new(alloc_node(table)),
count: CachePadded::new(AtomicUsize::new(0)),
resizing: AtomicBool::new(false),
hasher,
}
}
pub fn len(&self) -> usize {
self.count.load(Ordering::Relaxed)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
let guard = pin();
let table_ptr = self.table.load(Ordering::Acquire, &guard);
unsafe { (*table_ptr.as_raw()).capacity }
}
pub fn hasher(&self) -> &S {
&self.hasher
}
}
#[cfg(feature = "std")]
impl<K: 'static, V: 'static> HopscotchMap<K, V, FixedState> {
pub fn new() -> Self {
Self::with_hasher(FixedState::default())
}
pub fn with_capacity(capacity: usize) -> Self {
Self::with_capacity_and_hasher(capacity, FixedState::default())
}
}
impl<K, V, S> HopscotchMap<K, V, S>
where
K: Hash + Eq + Clone + Send + 'static,
V: Clone + Send + 'static,
S: BuildHasher,
{
#[inline]
fn wait_for_resize(&self) {
while self.resizing.load(Ordering::Acquire) {
spin_hint();
}
}
pub fn get<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.get_hashed(self.hasher.hash_one(key), key)
}
#[inline(always)]
fn get_hashed<Q>(&self, hash: u64, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let guard = pin();
let table_ptr = self.table.load(Ordering::Acquire, &guard);
let table = unsafe { &*table_ptr.as_raw() };
table
.lookup(hash, key, &guard)
.map(|entry| entry.value.clone())
}
pub fn insert(&self, key: K, value: V) -> Option<V> {
let hash = self.hasher.hash_one(&key);
match self.insert_impl(hash, key, value, false, |_| ()) {
Outcome::Linked(()) => None,
Outcome::Replaced(old) | Outcome::Present(old) => Some(old),
}
}
#[inline(always)]
fn home_of<'g>(
&self,
hash: u64,
guard: &'g kovan::Guard,
vacant_answers: bool,
) -> Result<HeldHome<'g, K, V>, Blocked<'g, K, V>> {
let table_ptr = self.table.load(Ordering::Acquire, guard);
let table = unsafe { &*table_ptr.as_raw() };
if self.resizing.load(Ordering::Acquire) {
return Err(Blocked::Resizing);
}
match table.home_guard_unless(table.bucket_index(hash), |word| {
vacant_answers && hop_bits(word) == 0
}) {
Ok(home) => Ok((table, home)),
Err(word) if vacant_answers && hop_bits(word) == 0 => Err(Blocked::Vacant),
Err(_) => Err(Blocked::Held(table)),
}
}
#[inline(always)]
fn count_in(&self, table: &Table<K, V>) -> Option<usize> {
let count = self.count.fetch_add(1, Ordering::Relaxed) + 1;
overfull(count, table.capacity).then_some(table.capacity * 2)
}
#[inline(always)]
fn unlink_held(
&self,
table: &Table<K, V>,
home: &mut HomeGuard<'_>,
offset: usize,
) -> Option<usize> {
table
.get_bucket(home.idx + offset)
.store(Word::free(), Ordering::Release);
home.stage_unlinked(offset);
let prev = self.count.fetch_sub(1, Ordering::Relaxed);
debug_assert!(prev > 0, "an unlinked entry was never counted");
(prev - 1 < table.shrink_at.load(Ordering::Relaxed)).then_some(table.capacity / 2)
}
fn insert_impl<R>(
&self,
hash: u64,
key: K,
value: V,
only_if_absent: bool,
on_insert: impl FnOnce(&V) -> R,
) -> Outcome<R, V> {
let mut pending = Pending::Parts { hash, key, value };
loop {
self.wait_for_resize();
let guard = pin();
let (table, mut home) = match self.home_of(hash, &guard, false) {
Ok(held) => held,
Err(Blocked::Held(table)) => {
#[cfg(test)]
pause::at(pause::Point::WriterMetHeldGuard);
if only_if_absent && let Some(entry) = table.lookup(hash, pending.key(), &guard)
{
return Outcome::Present(entry.value.clone());
}
spin_hint();
continue;
}
Err(_) => continue,
};
let outcome = Self::try_insert(table, &mut home, pending, only_if_absent, &guard);
let grow_to = match outcome {
InsertResult::Linked(_) => self.count_in(table),
_ => None,
};
drop(home);
match outcome {
InsertResult::Linked(entry) => {
#[cfg(test)]
pause::at(pause::Point::AfterLanding);
let answer = on_insert(unsafe { &(*entry).value });
if let Some(capacity) = grow_to {
drop(guard);
self.try_resize(capacity);
}
return Outcome::Linked(answer);
}
InsertResult::Replaced(old) => return Outcome::Replaced(old),
InsertResult::Exists(existing) => return Outcome::Present(existing),
InsertResult::NeedResize(back) => {
pending = back;
let current_capacity = table.capacity;
drop(guard);
self.try_resize(current_capacity * 2);
}
InsertResult::Retry(back) => {
pending = back;
spin_hint();
}
}
}
}
pub fn get_or_insert(&self, key: K, value: V) -> V {
let hash = self.hasher.hash_one(&key);
if let Some(v) = self.get_hashed(hash, &key) {
return v;
}
match self.insert_impl(hash, key, value, true, V::clone) {
Outcome::Linked(own) | Outcome::Present(own) | Outcome::Replaced(own) => own,
}
}
pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
let hash = self.hasher.hash_one(&key);
if let Some(existing) = self.get_hashed(hash, &key) {
return Some(existing);
}
match self.insert_impl(hash, key, value, true, |_| ()) {
Outcome::Linked(()) => None,
Outcome::Present(existing) | Outcome::Replaced(existing) => Some(existing),
}
}
pub fn force_remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut newest = None;
loop {
match self.remove(key) {
Some(v) => {
if newest.is_none() {
newest = Some(v);
}
}
None => return newest,
}
}
}
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.remove_where(key, |_| true)
}
pub fn clear(&self) {
while self
.resizing
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
spin_hint();
}
let guard = pin();
let table_ptr = self.table.load(Ordering::Acquire, &guard);
let table = unsafe { &*table_ptr.as_raw() };
Self::hold_writers(table);
for i in 0..(table.capacity + NEIGHBORHOOD_SIZE) {
let bucket = table.get_bucket(i);
let word = bucket.load(Ordering::Acquire, &guard);
if !word.is_free()
&& bucket.replace(
word,
Word::free(),
Ordering::Release,
Ordering::Relaxed,
&guard,
)
{
unsafe { retire_node(word.ptr()) };
}
if i < table.capacity {
let b = table.get_bucket(i);
b.control.fetch_and(!HOP_MASK, Ordering::Release);
}
}
self.count.store(0, Ordering::Release);
table
.shrink_at
.store(shrink_at(table.capacity), Ordering::Relaxed);
Self::release_writers(table);
self.resizing.store(false, Ordering::Release);
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.get(key).is_some()
}
pub fn extend<I: IntoIterator<Item = (K, V)>>(&self, iter: I) {
for (k, v) in iter {
self.insert(k, v);
}
}
}
unsafe impl<K: Send, V: Send, S: Send> Send for HopscotchMap<K, V, S> {}
unsafe impl<K: Send + Sync, V: Send + Sync, S: Send + Sync> Sync for HopscotchMap<K, V, S> {}
impl<K, V, S> Drop for HopscotchMap<K, V, S> {
fn drop(&mut self) {
let guard = pin();
let table_ptr = self.table.load(Ordering::Acquire, &guard);
unsafe {
drop_node(table_ptr.as_raw());
}
drop(guard);
kovan::flush_pending();
}
}
#[cfg(test)]
mod pause;
#[cfg(test)]
mod tests;