use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
use crossbeam_epoch::{Atomic, Guard, Owned, Shared};
use parking_lot::Mutex;
use crate::core::Versioned;
use crate::engine::hash::hash_one;
use crate::engine::store::Slot;
pub(crate) const SHARDS: usize = 64;
const INITIAL_CAPACITY: usize = 16;
pub(crate) struct Record<T: Versioned> {
pub(crate) slot: Slot<T>,
pub(crate) key: T::Key,
}
struct Entry<T: Versioned> {
hash: AtomicU64,
record: AtomicPtr<Record<T>>,
}
impl<T: Versioned> Entry<T> {
fn empty() -> Self {
Entry {
hash: AtomicU64::new(0),
record: AtomicPtr::new(std::ptr::null_mut()),
}
}
}
struct Buckets<T: Versioned> {
mask: usize,
entries: Box<[Entry<T>]>,
}
impl<T: Versioned> Buckets<T> {
fn with_capacity(capacity: usize) -> Self {
debug_assert!(capacity.is_power_of_two());
Buckets {
mask: capacity - 1,
entries: (0..capacity).map(|_| Entry::empty()).collect(),
}
}
}
const CHUNK: usize = 64;
struct Chunk<T: Versioned> {
records: [AtomicPtr<Record<T>>; CHUNK],
next: Atomic<Chunk<T>>,
}
impl<T: Versioned> Chunk<T> {
fn new() -> Self {
Chunk {
records: [const { AtomicPtr::new(std::ptr::null_mut()) }; CHUNK],
next: Atomic::null(),
}
}
}
struct Shard<T: Versioned> {
buckets: Atomic<Buckets<T>>,
chunks: Atomic<Chunk<T>>,
writers: Mutex<usize>,
}
pub(crate) struct SlotMap<T: Versioned> {
shards: Box<[Shard<T>]>,
}
impl<T: Versioned> SlotMap<T> {
pub(crate) fn new() -> Self {
SlotMap {
shards: (0..SHARDS)
.map(|_| Shard {
buckets: Atomic::new(Buckets::with_capacity(INITIAL_CAPACITY)),
chunks: Atomic::new(Chunk::new()),
writers: Mutex::new(0),
})
.collect(),
}
}
#[inline]
fn hash(key: &T::Key) -> u64 {
match hash_one(key) {
0 => 1,
h => h,
}
}
#[inline]
fn shard(&self, hash: u64) -> &Shard<T> {
&self.shards[(hash >> 32) as usize % SHARDS]
}
pub(crate) fn get<'a>(&'a self, key: &T::Key, guard: &Guard) -> Option<&'a Slot<T>> {
let hash = Self::hash(key);
Some(&Self::probe(self.shard(hash), hash, key, guard)?.slot)
}
fn probe<'a>(
shard: &Shard<T>,
hash: u64,
key: &T::Key,
guard: &Guard,
) -> Option<&'a Record<T>> {
let buckets = unsafe { shard.buckets.load(Ordering::Acquire, guard).deref() };
let mut i = hash as usize & buckets.mask;
loop {
let entry = &buckets.entries[i];
match entry.hash.load(Ordering::Acquire) {
0 => return None,
h if h == hash => {
let record = entry.record.load(Ordering::Acquire);
let record = unsafe { &*record };
if record.key == *key {
return Some(record);
}
}
_ => {}
}
i = (i + 1) & buckets.mask;
}
}
pub(crate) fn get_or_create<'a>(&'a self, key: &T::Key, guard: &Guard) -> &'a Slot<T> {
let hash = Self::hash(key);
let shard = self.shard(hash);
if let Some(record) = Self::probe(shard, hash, key, guard) {
return &record.slot;
}
let mut count = shard.writers.lock();
if let Some(record) = Self::probe(shard, hash, key, guard) {
return &record.slot;
}
let buckets = unsafe { shard.buckets.load(Ordering::Acquire, guard).deref() };
if (*count + 1) * 2 > buckets.entries.len() {
self.grow(shard, guard);
}
let buckets = unsafe { shard.buckets.load(Ordering::Acquire, guard).deref() };
let record = Box::into_raw(Box::new(Record {
slot: Slot::new(),
key: key.clone(),
}));
Self::place(buckets, hash, record);
Self::append(shard, *count, record, guard);
*count += 1;
&unsafe { &*record }.slot
}
fn append(shard: &Shard<T>, index: usize, record: *mut Record<T>, guard: &Guard) {
let mut chunk = shard.chunks.load(Ordering::Acquire, guard);
for _ in 0..index / CHUNK {
let next = unsafe { chunk.deref() }.next.load(Ordering::Acquire, guard);
chunk = if next.is_null() {
let fresh = Owned::new(Chunk::new()).into_shared(guard);
unsafe { chunk.deref() }
.next
.store(fresh, Ordering::Release);
fresh
} else {
next
};
}
unsafe { chunk.deref() }.records[index % CHUNK].store(record, Ordering::Release);
}
fn place(buckets: &Buckets<T>, hash: u64, record: *mut Record<T>) {
let mut i = hash as usize & buckets.mask;
loop {
let entry = &buckets.entries[i];
if entry.hash.load(Ordering::Relaxed) == 0 {
entry.record.store(record, Ordering::Release);
entry.hash.store(hash, Ordering::Release);
return;
}
i = (i + 1) & buckets.mask;
}
}
fn grow(&self, shard: &Shard<T>, guard: &Guard) {
let old = shard.buckets.load(Ordering::Acquire, guard);
let old_ref = unsafe { old.deref() };
let grown = Buckets::with_capacity(old_ref.entries.len() * 2);
for entry in &old_ref.entries {
let hash = entry.hash.load(Ordering::Relaxed);
if hash != 0 {
Self::place(&grown, hash, entry.record.load(Ordering::Relaxed));
}
}
shard.buckets.store(Owned::new(grown), Ordering::Release);
unsafe { guard.defer_destroy(old) };
}
pub(crate) fn compact(&self) -> usize {
let guard = unsafe { crossbeam_epoch::unprotected() };
let mut reclaimed = 0;
for shard in &self.shards {
let mut count = shard.writers.lock();
let mut survivors: Vec<*mut Record<T>> = Vec::with_capacity(*count);
let mut chunk = shard.chunks.swap(Shared::null(), Ordering::Relaxed, guard);
while !chunk.is_null() {
let owned = unsafe { chunk.into_owned() };
for entry in &owned.records {
let record = entry.load(Ordering::Relaxed);
if record.is_null() {
break;
}
let empty = unsafe { &*record }
.slot
.latest
.load(Ordering::Relaxed, guard)
.is_null();
if empty {
drop(unsafe { Box::from_raw(record) });
reclaimed += 1;
} else {
survivors.push(record);
}
}
chunk = owned.next.load(Ordering::Relaxed, guard);
drop(owned);
}
let capacity = (survivors.len() * 2)
.next_power_of_two()
.max(INITIAL_CAPACITY);
let buckets = Buckets::with_capacity(capacity);
let fresh = Owned::new(Chunk::new()).into_shared(guard);
shard.chunks.store(fresh, Ordering::Relaxed);
for (index, &record) in survivors.iter().enumerate() {
let hash = Self::hash(&unsafe { &*record }.key);
Self::place(&buckets, hash, record);
Self::append(shard, index, record, guard);
}
let old = shard
.buckets
.swap(Owned::new(buckets), Ordering::Relaxed, guard);
drop(unsafe { old.into_owned() });
*count = survivors.len();
}
reclaimed
}
pub(crate) fn for_each_in_shard<'a>(
&'a self,
round: usize,
guard: &Guard,
f: impl FnMut(&'a Record<T>),
) {
Self::walk(&self.shards[round % SHARDS], guard, f);
}
pub(crate) fn for_each<'a>(&'a self, guard: &Guard, mut f: impl FnMut(&'a Record<T>)) {
for shard in &self.shards {
Self::walk(shard, guard, &mut f);
}
}
fn walk<'a>(shard: &'a Shard<T>, guard: &Guard, mut f: impl FnMut(&'a Record<T>)) {
let mut chunk = shard.chunks.load(Ordering::Acquire, guard);
'chunks: while !chunk.is_null() {
let current = unsafe { chunk.deref() };
for entry in ¤t.records {
let record = entry.load(Ordering::Acquire);
if record.is_null() {
break 'chunks;
}
f(unsafe { &*record });
}
chunk = current.next.load(Ordering::Acquire, guard);
}
}
}
impl<T: Versioned> Drop for SlotMap<T> {
fn drop(&mut self) {
let guard = unsafe { crossbeam_epoch::unprotected() };
for shard in &mut self.shards {
let buckets = shard.buckets.swap(Shared::null(), Ordering::Relaxed, guard);
drop(unsafe { buckets.into_owned() });
let mut chunk = shard.chunks.swap(Shared::null(), Ordering::Relaxed, guard);
while !chunk.is_null() {
let owned = unsafe { chunk.into_owned() };
for entry in &owned.records {
let record = entry.load(Ordering::Relaxed);
if record.is_null() {
break;
}
drop(unsafe { Box::from_raw(record) });
}
chunk = owned.next.load(Ordering::Relaxed, guard);
drop(owned);
}
}
}
}