use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::borrow::Cow;
use std::mem;
use std::ptr::NonNull;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering::SeqCst;
const NB_BUCKETS: usize = 1 << 12; const BUCKET_MASK: u32 = (1 << 12) - 1;
pub(crate) struct Set {
buckets: Box<[Mutex<Option<Box<Entry>>>]>,
}
pub(crate) struct Entry {
pub(crate) string: Box<str>,
pub(crate) hash: u32,
pub(crate) ref_count: AtomicIsize,
next_in_bucket: Option<Box<Entry>>,
}
pub(crate) const ENTRY_ALIGNMENT: usize = 4;
#[test]
fn entry_alignment_is_sufficient() {
assert!(mem::align_of::<Entry>() >= ENTRY_ALIGNMENT);
}
pub(crate) static DYNAMIC_SET: Lazy<Set> = Lazy::new(|| {
let buckets = (0..NB_BUCKETS).map(|_| Mutex::new(None)).collect();
Set { buckets }
});
impl Set {
pub(crate) fn insert(&self, string: Cow<str>, hash: u32) -> NonNull<Entry> {
let bucket_index = (hash & BUCKET_MASK) as usize;
let mut linked_list = self.buckets[bucket_index].lock();
{
let mut ptr: Option<&mut Box<Entry>> = linked_list.as_mut();
while let Some(entry) = ptr.take() {
if entry.hash == hash && *entry.string == *string {
if entry.ref_count.fetch_add(1, SeqCst) > 0 {
return NonNull::from(&mut **entry);
}
entry.ref_count.fetch_sub(1, SeqCst);
break;
}
ptr = entry.next_in_bucket.as_mut();
}
}
debug_assert!(mem::align_of::<Entry>() >= ENTRY_ALIGNMENT);
let string = string.into_owned();
let mut entry = Box::new(Entry {
next_in_bucket: linked_list.take(),
hash,
ref_count: AtomicIsize::new(1),
string: string.into_boxed_str(),
});
let ptr = NonNull::from(&mut *entry);
*linked_list = Some(entry);
ptr
}
pub(crate) fn remove(&self, ptr: *mut Entry) {
let value: &Entry = unsafe { &*ptr };
let bucket_index = (value.hash & BUCKET_MASK) as usize;
let mut linked_list = self.buckets[bucket_index].lock();
debug_assert!(value.ref_count.load(SeqCst) == 0);
let mut current: &mut Option<Box<Entry>> = &mut linked_list;
while let Some(entry_ptr) = current.as_mut() {
let entry_ptr: *mut Entry = &mut **entry_ptr;
if entry_ptr == ptr {
mem::drop(mem::replace(current, unsafe {
(*entry_ptr).next_in_bucket.take()
}));
break;
}
current = unsafe { &mut (*entry_ptr).next_in_bucket };
}
}
}