mod bucket;
#[cfg(test)]
mod tests;
use bucket::{Bucket, BucketArray, InsertOrModifyState, KeyOrOwnedBucket};
use std::{
borrow::Borrow,
hash::{BuildHasher, Hash},
sync::atomic::{self, AtomicUsize, Ordering},
};
use ahash::RandomState;
use crossbeam_epoch::{self, Atomic, CompareAndSetError, Guard, Owned, Shared};
pub type DefaultHashBuilder = RandomState;
#[derive(Default)]
pub struct HashMap<K: Hash + Eq, V, S: BuildHasher = DefaultHashBuilder> {
bucket_array: Atomic<bucket::BucketArray<K, V>>,
build_hasher: S,
len: AtomicUsize,
}
impl<K: Hash + Eq, V> HashMap<K, V, DefaultHashBuilder> {
pub fn new() -> HashMap<K, V, DefaultHashBuilder> {
HashMap::with_capacity_and_hasher(0, DefaultHashBuilder::default())
}
pub fn with_capacity(capacity: usize) -> HashMap<K, V, DefaultHashBuilder> {
HashMap::with_capacity_and_hasher(capacity, DefaultHashBuilder::default())
}
}
impl<K: Hash + Eq, V, S: BuildHasher> HashMap<K, V, S> {
pub fn with_hasher(build_hasher: S) -> HashMap<K, V, S> {
HashMap::with_capacity_and_hasher(0, build_hasher)
}
pub fn with_capacity_and_hasher(capacity: usize, build_hasher: S) -> HashMap<K, V, S> {
let bucket_array = if capacity == 0 {
Atomic::null()
} else {
Atomic::new(BucketArray::with_capacity(0, capacity))
};
Self {
bucket_array,
build_hasher,
len: AtomicUsize::new(0),
}
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
let guard = &crossbeam_epoch::pin();
let bucket_array_ptr = self.bucket_array.load_consume(guard);
unsafe { bucket_array_ptr.as_ref() }
.map(BucketArray::capacity)
.unwrap_or(0)
}
pub fn get<Q: Hash + Eq + ?Sized>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
V: Clone,
{
self.get_key_value_and(key, |_, v| v.clone())
}
pub fn get_key_value<Q: Hash + Eq + ?Sized>(&self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q> + Clone,
V: Clone,
{
self.get_key_value_and(key, |k, v| (k.clone(), v.clone()))
}
pub fn get_and<Q: Hash + Eq + ?Sized, F: FnOnce(&V) -> T, T>(
&self,
key: &Q,
with_value: F,
) -> Option<T>
where
K: Borrow<Q>,
{
self.get_key_value_and(key, move |_, v| with_value(v))
}
pub fn get_key_value_and<Q: Hash + Eq + ?Sized, F: FnOnce(&K, &V) -> T, T>(
&self,
key: &Q,
with_entry: F,
) -> Option<T>
where
K: Borrow<Q>,
{
let guard = &crossbeam_epoch::pin();
let current_ref = self.bucket_array(guard);
let mut bucket_array_ref = current_ref;
let hash = bucket::hash(&self.build_hasher, key);
let result;
loop {
match bucket_array_ref
.get(guard, hash, key)
.map(|p| unsafe { p.as_ref() })
{
Ok(Some(Bucket {
key,
maybe_value: value,
})) => {
result = Some(with_entry(key, unsafe { &*value.as_ptr() }));
break;
}
Ok(None) => {
result = None;
break;
}
Err(_) => {
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
}
}
self.swing_bucket_array(guard, current_ref, bucket_array_ref);
result
}
pub fn insert(&self, key: K, value: V) -> Option<V>
where
V: Clone,
{
self.insert_entry_and(key, value, |_, v| v.clone())
}
pub fn insert_entry(&self, key: K, value: V) -> Option<(K, V)>
where
K: Clone,
V: Clone,
{
self.insert_entry_and(key, value, |k, v| (k.clone(), v.clone()))
}
pub fn insert_and<F: FnOnce(&V) -> T, T>(
&self,
key: K,
value: V,
with_previous_value: F,
) -> Option<T> {
self.insert_entry_and(key, value, move |_, v| with_previous_value(v))
}
pub fn insert_entry_and<F: FnOnce(&K, &V) -> T, T>(
&self,
key: K,
value: V,
with_previous_entry: F,
) -> Option<T> {
let guard = &crossbeam_epoch::pin();
let current_ref = self.bucket_array(guard);
let mut bucket_array_ref = current_ref;
let hash = bucket::hash(&self.build_hasher, &key);
let mut bucket_ptr = Owned::new(Bucket::new(key, value));
let result;
loop {
while self.len.load(Ordering::Relaxed) > bucket_array_ref.capacity() {
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
match bucket_array_ref.insert(guard, hash, bucket_ptr) {
Ok(previous_bucket_ptr) => {
if let Some(previous_bucket_ref) = unsafe { previous_bucket_ptr.as_ref() } {
if previous_bucket_ptr.tag() & bucket::TOMBSTONE_TAG != 0 {
self.len.fetch_add(1, Ordering::Relaxed);
result = None;
} else {
let Bucket {
key,
maybe_value: value,
} = previous_bucket_ref;
result = Some(with_previous_entry(key, unsafe { &*value.as_ptr() }));
}
unsafe { bucket::defer_destroy_bucket(guard, previous_bucket_ptr) };
} else {
self.len.fetch_add(1, Ordering::Relaxed);
result = None;
}
break;
}
Err(p) => {
bucket_ptr = p;
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
}
}
self.swing_bucket_array(guard, current_ref, bucket_array_ref);
result
}
pub fn remove<Q: Hash + Eq + ?Sized>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
V: Clone,
{
self.remove_entry_if_and(key, |_, _| true, |_, v| v.clone())
}
pub fn remove_entry<Q: Hash + Eq + ?Sized>(&self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q> + Clone,
V: Clone,
{
self.remove_entry_if_and(key, |_, _| true, |k, v| (k.clone(), v.clone()))
}
pub fn remove_and<Q: Hash + Eq + ?Sized, F: FnOnce(&V) -> T, T>(
&self,
key: &Q,
with_previous_value: F,
) -> Option<T>
where
K: Borrow<Q>,
{
self.remove_entry_if_and(key, |_, _| true, move |_, v| with_previous_value(v))
}
pub fn remove_entry_and<Q: Hash + Eq + ?Sized, F: FnOnce(&K, &V) -> T, T>(
&self,
key: &Q,
with_previous_entry: F,
) -> Option<T>
where
K: Borrow<Q>,
{
self.remove_entry_if_and(key, |_, _| true, with_previous_entry)
}
pub fn remove_if<Q: Hash + Eq + ?Sized, F: FnMut(&K, &V) -> bool>(
&self,
key: &Q,
condition: F,
) -> Option<V>
where
K: Borrow<Q>,
V: Clone,
{
self.remove_entry_if_and(key, condition, move |_, v| v.clone())
}
pub fn remove_entry_if<Q: Hash + Eq + ?Sized, F: FnMut(&K, &V) -> bool>(
&self,
key: &Q,
condition: F,
) -> Option<(K, V)>
where
K: Clone + Borrow<Q>,
V: Clone,
{
self.remove_entry_if_and(key, condition, move |k, v| (k.clone(), v.clone()))
}
pub fn remove_if_and<Q: Hash + Eq + ?Sized, F: FnMut(&K, &V) -> bool, G: FnOnce(&V) -> T, T>(
&self,
key: &Q,
condition: F,
with_previous_value: G,
) -> Option<T>
where
K: Borrow<Q>,
{
self.remove_entry_if_and(key, condition, move |_, v| with_previous_value(v))
}
pub fn remove_entry_if_and<
Q: Hash + Eq + ?Sized,
F: FnMut(&K, &V) -> bool,
G: FnOnce(&K, &V) -> T,
T,
>(
&self,
key: &Q,
mut condition: F,
with_previous_entry: G,
) -> Option<T>
where
K: Borrow<Q>,
{
let guard = &crossbeam_epoch::pin();
let current_ref = self.bucket_array(guard);
let mut bucket_array_ref = current_ref;
let hash = bucket::hash(&self.build_hasher, &key);
let result;
loop {
match bucket_array_ref.remove_if(guard, hash, key, condition) {
Ok(previous_bucket_ptr) => {
if let Some(previous_bucket_ref) = unsafe { previous_bucket_ptr.as_ref() } {
let Bucket {
key,
maybe_value: value,
} = previous_bucket_ref;
self.len.fetch_sub(1, Ordering::Relaxed);
result = Some(with_previous_entry(key, unsafe { &*value.as_ptr() }));
unsafe { bucket::defer_destroy_tombstone(guard, previous_bucket_ptr) };
} else {
result = None;
}
break;
}
Err(c) => {
condition = c;
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
}
}
self.swing_bucket_array(guard, current_ref, bucket_array_ref);
result
}
pub fn insert_or_modify<F: FnMut(&K, &V) -> V>(
&self,
key: K,
value: V,
on_modify: F,
) -> Option<V>
where
V: Clone,
{
self.insert_with_or_modify_entry_and(key, move || value, on_modify, |_, v| v.clone())
}
pub fn insert_or_modify_entry<F: FnMut(&K, &V) -> V>(
&self,
key: K,
value: V,
on_modify: F,
) -> Option<(K, V)>
where
K: Clone,
V: Clone,
{
self.insert_with_or_modify_entry_and(
key,
move || value,
on_modify,
|k, v| (k.clone(), v.clone()),
)
}
pub fn insert_with_or_modify<F: FnOnce() -> V, G: FnMut(&K, &V) -> V>(
&self,
key: K,
on_insert: F,
on_modify: G,
) -> Option<V>
where
V: Clone,
{
self.insert_with_or_modify_entry_and(key, on_insert, on_modify, |_, v| v.clone())
}
pub fn insert_with_or_modify_entry<F: FnOnce() -> V, G: FnMut(&K, &V) -> V>(
&self,
key: K,
on_insert: F,
on_modify: G,
) -> Option<(K, V)>
where
K: Clone,
V: Clone,
{
self.insert_with_or_modify_entry_and(key, on_insert, on_modify, |k, v| {
(k.clone(), v.clone())
})
}
pub fn insert_or_modify_and<F: FnMut(&K, &V) -> V, G: FnOnce(&V) -> T, T>(
&self,
key: K,
value: V,
on_modify: F,
with_old_value: G,
) -> Option<T> {
self.insert_with_or_modify_entry_and(
key,
move || value,
on_modify,
move |_, v| with_old_value(v),
)
}
pub fn insert_or_modify_entry_and<F: FnMut(&K, &V) -> V, G: FnOnce(&K, &V) -> T, T>(
&self,
key: K,
value: V,
on_modify: F,
with_old_entry: G,
) -> Option<T> {
self.insert_with_or_modify_entry_and(key, move || value, on_modify, with_old_entry)
}
pub fn insert_with_or_modify_and<
F: FnOnce() -> V,
G: FnMut(&K, &V) -> V,
H: FnOnce(&V) -> T,
T,
>(
&self,
key: K,
on_insert: F,
on_modify: G,
with_old_value: H,
) -> Option<T> {
self.insert_with_or_modify_entry_and(key, on_insert, on_modify, move |_, v| {
with_old_value(v)
})
}
pub fn insert_with_or_modify_entry_and<
F: FnOnce() -> V,
G: FnMut(&K, &V) -> V,
H: FnOnce(&K, &V) -> T,
T,
>(
&self,
key: K,
on_insert: F,
mut on_modify: G,
with_old_entry: H,
) -> Option<T> {
let guard = &crossbeam_epoch::pin();
let current_ref = self.bucket_array(guard);
let mut bucket_array_ref = current_ref;
let hash = bucket::hash(&self.build_hasher, &key);
let mut state = InsertOrModifyState::New(key, on_insert);
let result;
loop {
while self.len.load(Ordering::Relaxed) > bucket_array_ref.capacity() {
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
match bucket_array_ref.insert_or_modify(guard, hash, state, on_modify) {
Ok(previous_bucket_ptr) => {
if let Some(previous_bucket_ref) = unsafe { previous_bucket_ptr.as_ref() } {
if previous_bucket_ptr.tag() & bucket::TOMBSTONE_TAG != 0 {
self.len.fetch_add(1, Ordering::Relaxed);
result = None;
} else {
let Bucket {
key,
maybe_value: value,
} = previous_bucket_ref;
result = Some(with_old_entry(key, unsafe { &*value.as_ptr() }));
}
unsafe { bucket::defer_destroy_bucket(guard, previous_bucket_ptr) };
} else {
self.len.fetch_add(1, Ordering::Relaxed);
result = None;
}
break;
}
Err((s, f)) => {
state = s;
on_modify = f;
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
}
}
self.swing_bucket_array(guard, current_ref, bucket_array_ref);
result
}
pub fn modify<F: FnMut(&K, &V) -> V>(&self, key: K, on_modify: F) -> Option<V>
where
V: Clone,
{
self.modify_entry_and(key, on_modify, |_, v| v.clone())
}
pub fn modify_entry<F: FnMut(&K, &V) -> V>(&self, key: K, on_modify: F) -> Option<(K, V)>
where
K: Clone,
V: Clone,
{
self.modify_entry_and(key, on_modify, |k, v| (k.clone(), v.clone()))
}
pub fn modify_and<F: FnMut(&K, &V) -> V, G: FnOnce(&V) -> T, T>(
&self,
key: K,
on_modify: F,
with_old_value: G,
) -> Option<T> {
self.modify_entry_and(key, on_modify, move |_, v| with_old_value(v))
}
pub fn modify_entry_and<F: FnMut(&K, &V) -> V, G: FnOnce(&K, &V) -> T, T>(
&self,
key: K,
mut on_modify: F,
with_old_entry: G,
) -> Option<T> {
let guard = &crossbeam_epoch::pin();
let current_ref = self.bucket_array(guard);
let mut bucket_array_ref = current_ref;
let hash = bucket::hash(&self.build_hasher, &key);
let mut key_or_owned_bucket = KeyOrOwnedBucket::Key(key);
let result;
loop {
match bucket_array_ref.modify(guard, hash, key_or_owned_bucket, on_modify) {
Ok(previous_bucket_ptr) => {
if let Some(previous_bucket_ref) = unsafe { previous_bucket_ptr.as_ref() } {
let Bucket {
key,
maybe_value: value,
} = previous_bucket_ref;
result = Some(with_old_entry(key, unsafe { &*value.as_ptr() }));
unsafe { bucket::defer_destroy_bucket(guard, previous_bucket_ptr) };
} else {
result = None;
}
break;
}
Err((kb, f)) => {
key_or_owned_bucket = kb;
on_modify = f;
bucket_array_ref = bucket_array_ref.rehash(guard, &self.build_hasher);
}
}
}
self.swing_bucket_array(guard, current_ref, bucket_array_ref);
result
}
}
impl<K: Eq + Hash, V, S: BuildHasher> HashMap<K, V, S> {
fn bucket_array<'g>(&self, guard: &'g Guard) -> &'g BucketArray<K, V> {
const DEFAULT_CAPACITY: usize = 64;
let mut maybe_new_bucket_array = None;
loop {
let bucket_array_ptr = self.bucket_array.load_consume(guard);
if let Some(bucket_array_ref) = unsafe { bucket_array_ptr.as_ref() } {
return bucket_array_ref;
}
let new_bucket_array = maybe_new_bucket_array
.unwrap_or_else(|| Owned::new(BucketArray::with_capacity(0, DEFAULT_CAPACITY)));
match self.bucket_array.compare_and_set_weak(
Shared::null(),
new_bucket_array,
(Ordering::Release, Ordering::Relaxed),
guard,
) {
Ok(b) => return unsafe { b.as_ref() }.unwrap(),
Err(CompareAndSetError { new, .. }) => maybe_new_bucket_array = Some(new),
}
}
}
fn swing_bucket_array<'g>(
&self,
guard: &'g Guard,
mut current_ref: &'g BucketArray<K, V>,
min_ref: &'g BucketArray<K, V>,
) {
let min_epoch = min_ref.epoch;
let mut current_ptr = (current_ref as *const BucketArray<K, V>).into();
let min_ptr: Shared<'g, _> = (min_ref as *const BucketArray<K, V>).into();
loop {
if current_ref.epoch >= min_epoch {
return;
}
match self.bucket_array.compare_and_set_weak(
current_ptr,
min_ptr,
(Ordering::Release, Ordering::Relaxed),
guard,
) {
Ok(_) => unsafe { bucket::defer_acquire_destroy(guard, current_ptr) },
Err(_) => {
let new_ptr = self.bucket_array.load_consume(guard);
assert!(!new_ptr.is_null());
current_ptr = new_ptr;
current_ref = unsafe { new_ptr.as_ref() }.unwrap();
}
}
}
}
}
impl<K: Eq + Hash, V, S: BuildHasher> Drop for HashMap<K, V, S> {
fn drop(&mut self) {
let guard = unsafe { &crossbeam_epoch::unprotected() };
atomic::fence(Ordering::Acquire);
let mut current_ptr = self.bucket_array.load(Ordering::Relaxed, guard);
while let Some(current_ref) = unsafe { current_ptr.as_ref() } {
let next_ptr = current_ref.next.load(Ordering::Relaxed, guard);
for this_bucket_ptr in current_ref
.buckets
.iter()
.map(|b| b.load(Ordering::Relaxed, guard))
.filter(|p| !p.is_null())
.filter(|p| next_ptr.is_null() || p.tag() & bucket::TOMBSTONE_TAG == 0)
{
unsafe { bucket::defer_acquire_destroy(guard, this_bucket_ptr) };
}
unsafe { bucket::defer_acquire_destroy(guard, current_ptr) };
current_ptr = next_ptr;
}
}
}