extern crate alloc;
#[cfg(test)]
use super::pause;
use super::{MIN_CAPACITY, NEIGHBORHOOD_SIZE};
use crate::sync::{AtomicU64, AtomicUsize};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::sync::atomic::Ordering;
use kovan::{Atomic, NodeBox, RetiredNode, Shared, drop_node, pin, retire_node};
pub(super) const HOP_MASK: u64 = (1u64 << NEIGHBORHOOD_SIZE) - 1;
pub(super) const GUARD: u64 = 1u64 << NEIGHBORHOOD_SIZE;
const STAMP_UNIT: u64 = GUARD << 1;
pub(super) const STAMP_MASK: u64 = !(HOP_MASK | GUARD);
const _: () = assert!(NEIGHBORHOOD_SIZE <= 32, "a home's hop bits fit a u32");
#[inline(always)]
pub(super) fn hop_bits(word: u64) -> u32 {
(word & HOP_MASK) as u32
}
#[inline(always)]
pub(super) fn hop_bit(offset: usize) -> u64 {
1u64 << offset
}
pub(super) struct Bucket<K, V> {
pub(super) control: AtomicU64,
slot: Atomic<Entry<K, V>>,
}
impl<K, V> Bucket<K, V> {
#[inline(always)]
pub(super) fn load<'g>(&self, order: Ordering, guard: &'g kovan::Guard) -> Word<'g, K, V> {
Word(self.slot.load(order, guard))
}
#[inline(always)]
pub(super) unsafe fn load_held<'g>(
&self,
order: Ordering,
guard: &'g kovan::Guard,
) -> Word<'g, K, V> {
Word(unsafe { self.slot.load_unprotected(order, guard) })
}
#[inline(always)]
pub(super) fn store(&self, word: Word<'_, K, V>, order: Ordering) {
self.slot.store(word.0, order);
}
#[inline(always)]
pub(super) fn replace(
&self,
current: Word<'_, K, V>,
new: Word<'_, K, V>,
success: Ordering,
failure: Ordering,
guard: &kovan::Guard,
) -> bool {
self.slot
.compare_exchange(current.0, new.0, success, failure, guard)
.is_ok()
}
}
const TAG_BITS: u32 = 4;
const TAG_MASK: usize = (1 << TAG_BITS) - 1;
#[inline(always)]
pub(super) fn tag(hash: u64) -> usize {
(hash >> (u64::BITS - TAG_BITS)) as usize
}
pub(super) struct Word<'g, K, V>(Shared<'g, Entry<K, V>>);
impl<K, V> Clone for Word<'_, K, V> {
fn clone(&self) -> Self {
*self
}
}
impl<K, V> Copy for Word<'_, K, V> {}
impl<K, V> PartialEq for Word<'_, K, V> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'g, K, V> Word<'g, K, V> {
#[inline(always)]
pub(super) fn free() -> Self {
Self(unsafe { Shared::from_raw(core::ptr::null_mut()) })
}
#[inline(always)]
pub(super) fn reserved() -> Self {
Self(unsafe { Shared::from_raw(core::ptr::without_provenance_mut(1)) })
}
#[inline(always)]
pub(super) fn of(entry: NodeBox<Entry<K, V>>) -> Self {
let tag = tag(entry.hash);
let raw = NodeBox::into_raw(entry);
Self(unsafe { Shared::from_raw(raw.map_addr(|addr| addr | tag)) })
}
#[inline(always)]
pub(super) fn is_free(self) -> bool {
self.0.is_null()
}
#[inline(always)]
pub(super) fn has_tag(self, tag: usize) -> bool {
self.0.as_raw().addr() & TAG_MASK == tag
}
#[inline(always)]
pub(super) fn ptr(self) -> *mut Entry<K, V> {
self.0.as_raw().map_addr(|addr| addr & !TAG_MASK)
}
#[inline(always)]
pub(super) fn entry(self) -> Option<&'g Entry<K, V>> {
unsafe { self.ptr().as_ref() }
}
#[inline(always)]
pub(super) unsafe fn into_entry(self) -> NodeBox<Entry<K, V>> {
unsafe { NodeBox::from_raw(self.ptr()) }
}
}
pub(super) struct HomeGuard<'t> {
control: &'t AtomicU64,
pub(super) idx: usize,
word: u64,
}
impl HomeGuard<'_> {
#[inline(always)]
pub(super) fn hops(&self) -> u32 {
hop_bits(self.word)
}
#[inline(always)]
pub(super) fn publish_linked(&mut self, offset: usize) {
self.word |= hop_bit(offset);
self.control.store(self.word, Ordering::Relaxed);
}
#[inline(always)]
pub(super) fn retract_linked(&mut self, offset: usize) {
self.word &= !hop_bit(offset);
self.control.store(self.word, Ordering::Relaxed);
}
#[inline(always)]
pub(super) fn stage_unlinked(&mut self, offset: usize) {
self.word &= !hop_bit(offset);
}
#[inline]
pub(super) fn publish_moved_in(&mut self, offset: usize) {
self.word = (self.word | hop_bit(offset)).wrapping_add(STAMP_UNIT);
self.control.store(self.word, Ordering::Release);
}
}
impl Drop for HomeGuard<'_> {
fn drop(&mut self) {
self.control.store(self.word & !GUARD, Ordering::Release);
}
}
#[repr(C, align(16))]
pub(super) struct Entry<K, V> {
pub(super) retired: RetiredNode,
pub(super) hash: u64,
pub(super) key: K,
pub(super) value: V,
}
impl<K, V> Entry<K, V> {
#[inline]
pub(super) fn boxed(hash: u64, key: K, value: V) -> NodeBox<Self> {
NodeBox::new(Self {
retired: RetiredNode::new(),
hash,
key,
value,
})
}
}
unsafe impl<K: Send, V: Send> Send for Entry<K, V> {}
unsafe impl<K: Send + Sync, V: Send + Sync> Sync for Entry<K, V> {}
const _: () = assert!(core::mem::align_of::<Entry<(), ()>>() == 1 << TAG_BITS);
pub(super) const WALK_AHEAD: usize = 16;
const _: () = assert!(MIN_CAPACITY + NEIGHBORHOOD_SIZE >= WALK_AHEAD);
#[inline(always)]
fn prefetch<T>(ptr: *const T) {
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::x86_64::_mm_prefetch::<{ core::arch::x86_64::_MM_HINT_T0 }>(ptr.cast());
}
#[cfg(all(target_arch = "x86", target_feature = "sse"))]
unsafe {
core::arch::x86::_mm_prefetch::<{ core::arch::x86::_MM_HINT_T0 }>(ptr.cast());
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!(
"prfm pldl1keep, [{0}]",
in(reg) ptr,
options(nostack, readonly, preserves_flags)
);
}
#[cfg(not(any(
target_arch = "x86_64",
all(target_arch = "x86", target_feature = "sse"),
target_arch = "aarch64"
)))]
let _ = ptr;
}
pub(super) struct Walk<K, V> {
next: usize,
ahead: [*mut Entry<K, V>; WALK_AHEAD],
}
impl<K, V> Walk<K, V> {
pub(super) fn new(table: &Table<K, V>, guard: &kovan::Guard) -> Self {
let mut ahead = [core::ptr::null_mut(); WALK_AHEAD];
for (idx, entry) in ahead.iter_mut().enumerate() {
*entry = table.read_ahead(idx, guard);
}
Self { next: 0, ahead }
}
#[inline]
pub(super) fn next(
&mut self,
table: &Table<K, V>,
guard: &kovan::Guard,
) -> Option<(usize, *mut Entry<K, V>)> {
let idx = self.next;
let len = table.buckets.len();
if idx == len {
return None;
}
self.next = idx + 1;
let cell = &mut self.ahead[idx % WALK_AHEAD];
let entry = *cell;
if idx + WALK_AHEAD < len {
*cell = table.read_ahead(idx + WALK_AHEAD, guard);
}
Some((idx, entry))
}
}
#[repr(C)]
pub(super) struct Table<K, V> {
retired: RetiredNode,
pub(super) buckets: Box<[Bucket<K, V>]>,
pub(super) capacity: usize,
mask: usize,
pub(super) shrink_at: AtomicUsize,
}
unsafe impl<K: Send, V: Send> Send for Table<K, V> {}
unsafe impl<K: Send + Sync, V: Send + Sync> Sync for Table<K, V> {}
impl<K, V> Table<K, V> {
pub(super) fn new(capacity: usize) -> Self {
let capacity = capacity.next_power_of_two().max(MIN_CAPACITY);
let mut buckets = Vec::with_capacity(capacity + NEIGHBORHOOD_SIZE);
for _ in 0..(capacity + NEIGHBORHOOD_SIZE) {
buckets.push(Bucket {
control: AtomicU64::new(0),
slot: Atomic::null(),
});
}
Self {
retired: RetiredNode::new(),
buckets: buckets.into_boxed_slice(),
capacity,
mask: capacity - 1,
shrink_at: AtomicUsize::new(super::shrink_at(capacity)),
}
}
#[inline(always)]
pub(super) fn bucket_index(&self, hash: u64) -> usize {
(hash as usize) & self.mask
}
#[inline(always)]
pub(super) fn home_mask(&self) -> usize {
self.mask
}
#[inline(always)]
pub(super) fn get_bucket(&self, idx: usize) -> &Bucket<K, V> {
unsafe { self.buckets.get_unchecked(idx) }
}
#[inline(always)]
fn read_ahead(&self, idx: usize, guard: &kovan::Guard) -> *mut Entry<K, V> {
let entry = self.get_bucket(idx).load(Ordering::Acquire, guard).ptr();
self.prefetch_entry(entry);
entry
}
#[inline(always)]
pub(super) fn prefetch_entry(&self, entry: *const Entry<K, V>) {
let base: *const u8 = core::hint::select_unpredictable(
entry.is_null(),
(self as *const Self).cast(),
entry.cast(),
);
prefetch(base.wrapping_add(core::mem::offset_of!(Entry<K, V>, hash)));
prefetch(base.wrapping_add(core::mem::size_of::<Entry<K, V>>() - 1));
}
#[inline(always)]
pub(super) fn looks_free(&self, idx: usize, guard: &kovan::Guard) -> bool {
self.get_bucket(idx)
.load(Ordering::Relaxed, guard)
.is_free()
}
#[inline]
pub(super) fn home_guard(&self, idx: usize) -> Option<HomeGuard<'_>> {
self.home_guard_unless(idx, |_| false).ok()
}
#[inline(always)]
pub(super) fn home_guard_unless(
&self,
idx: usize,
refuse: impl Fn(u64) -> bool,
) -> Result<HomeGuard<'_>, u64> {
let control = &self.get_bucket(idx).control;
let mut word = control.load(Ordering::Relaxed);
loop {
if word & GUARD != 0 || refuse(word) {
return Err(word);
}
match control.compare_exchange_weak(
word,
word | GUARD,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => {
return Ok(HomeGuard {
control,
idx,
word: word | GUARD,
});
}
Err(now) => word = now,
}
}
}
#[inline]
pub(super) fn lookup<'g, Q>(
&self,
hash: u64,
key: &Q,
guard: &'g kovan::Guard,
) -> Option<&'g Entry<K, V>>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let home = self.bucket_index(hash);
let control = &self.get_bucket(home).control;
let mut word = control.load(Ordering::Acquire);
loop {
let hops = hop_bits(word);
if hops == 0 {
return None;
}
#[cfg(test)]
pause::at(pause::Point::LookupReadHops);
if let Some((_, found)) = self.find(home, hops, hash, key, guard) {
return found.entry();
}
let again = control.load(Ordering::Acquire);
if (again ^ word) & STAMP_MASK == 0 {
return None;
}
word = again;
}
}
#[inline]
pub(super) fn find<'g, Q>(
&self,
home: usize,
hops: u32,
hash: u64,
key: &Q,
guard: &'g kovan::Guard,
) -> Option<(usize, Word<'g, K, V>)>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
self.scan(home, hops, hash, key, |bucket| {
bucket.load(Ordering::Acquire, guard)
})
}
#[inline]
pub(super) fn find_held<'h, Q>(
&self,
held: &'h HomeGuard<'_>,
hash: u64,
key: &Q,
guard: &'h kovan::Guard,
) -> Option<(usize, Word<'h, K, V>)>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
debug_assert!(
core::ptr::eq(held.control, &self.get_bucket(held.idx).control),
"a home guard of another table"
);
self.scan(held.idx, held.hops(), hash, key, |bucket| {
let word = unsafe { bucket.load_held(Ordering::Acquire, guard) };
#[cfg(test)]
pause::at(pause::Point::HeldScanLoaded);
word
})
}
#[inline(always)]
pub(super) unsafe fn replace_held(
&self,
held: &HomeGuard<'_>,
offset: usize,
old: *mut Entry<K, V>,
entry: NodeBox<Entry<K, V>>,
) where
K: 'static,
V: 'static,
{
self.get_bucket(held.idx + offset)
.store(Word::of(entry), Ordering::Release);
unsafe { retire_node(old) };
}
#[inline(always)]
fn scan<'g, Q>(
&self,
home: usize,
hops: u32,
hash: u64,
key: &Q,
load: impl Fn(&Bucket<K, V>) -> Word<'g, K, V>,
) -> Option<(usize, Word<'g, K, V>)>
where
K: Borrow<Q>,
Q: Eq + ?Sized,
{
let tag = tag(hash);
let mut rest = hops;
while rest != 0 {
let offset = rest.trailing_zeros() as usize;
rest &= rest - 1;
let word = load(self.get_bucket(home + offset));
if word.has_tag(tag)
&& let Some(entry) = word.entry()
&& entry.hash == hash
&& entry.key.borrow() == key
{
return Some((offset, word));
}
}
None
}
}
impl<K, V> Drop for Table<K, V> {
fn drop(&mut self) {
let guard = pin();
let mut walk = Walk::new(self, &guard);
while let Some((_, entry_ptr)) = walk.next(self, &guard) {
if !entry_ptr.is_null() {
unsafe { drop_node(entry_ptr) };
}
}
}
}