use parking_lot::Mutex;
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ptr::NonNull;
use std::sync::OnceLock;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering::SeqCst;
const NB_BUCKETS: usize = 1 << 12; const BUCKET_MASK: u64 = (1 << 12) - 1;
pub(crate) struct Set {
buckets: Box<[Mutex<Option<NonNull<Entry>>>]>,
}
pub(crate) struct Entry {
pub(crate) string: Box<str>,
pub(crate) hash: u64,
pub(crate) ref_count: AtomicIsize,
next_in_bucket: UnsafeCell<Option<NonNull<Entry>>>,
}
unsafe impl Send for Entry {}
unsafe impl Sync for Entry {}
unsafe impl Send for Set {}
unsafe impl Sync for Set {}
pub(crate) fn dynamic_set() -> &'static Set {
static DYNAMIC_SET: OnceLock<Set> = OnceLock::new();
DYNAMIC_SET.get_or_init(|| {
let buckets = (0..NB_BUCKETS).map(|_| Mutex::new(None)).collect();
Set { buckets }
})
}
impl Set {
pub(crate) fn insert(&self, string: Cow<str>, hash: u64) -> NonNull<Entry> {
let bucket_index = (hash & BUCKET_MASK) as usize;
let mut linked_list = self.buckets[bucket_index].lock();
{
let mut ptr: Option<NonNull<Entry>> = *linked_list;
while let Some(entry_ptr) = ptr {
let entry = unsafe { entry_ptr.as_ref() };
if entry.hash == hash && *entry.string == *string {
let old_size = entry.ref_count.fetch_add(1, SeqCst);
if old_size > 0 {
if old_size == isize::MAX {
std::process::abort();
}
return entry_ptr;
}
entry.ref_count.fetch_sub(1, SeqCst);
break;
}
ptr = unsafe { entry.next_in_bucket.get().read() };
}
}
let string = string.into_owned();
let entry = Box::new(Entry {
next_in_bucket: UnsafeCell::new(linked_list.take()),
hash,
ref_count: AtomicIsize::new(1),
string: string.into_boxed_str(),
});
let ptr = NonNull::from(Box::leak(entry));
*linked_list = Some(ptr);
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 lock_guard = self.buckets[bucket_index].lock();
debug_assert!(value.ref_count.load(SeqCst) == 0);
let mut current: &mut Option<NonNull<Entry>> = &mut lock_guard;
while let Some(entry_ptr) = *current {
if entry_ptr.as_ptr() == ptr {
let unlinked_entry = unsafe { Box::from_raw(entry_ptr.as_ptr()) };
*current = unlinked_entry.next_in_bucket.into_inner();
drop(lock_guard);
break;
}
let entry = unsafe { entry_ptr.as_ref() };
current = unsafe { &mut *entry.next_in_bucket.get() };
}
}
}
#[cfg(feature = "malloc_size_of")]
pub fn malloc_size_of_dynamic_set(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
let mut sum = 0;
for bucket in &dynamic_set().buckets {
let guard = bucket.lock();
let mut next: Option<NonNull<Entry>> = *guard;
while let Some(ptr) = next {
sum += unsafe { ops.malloc_size_of::<Entry>(ptr.as_ptr()) };
let entry = unsafe { ptr.as_ref() };
sum += <Box<str> as malloc_size_of::MallocSizeOf>::size_of(&entry.string, ops);
next = unsafe { *entry.next_in_bucket.get() };
}
}
sum
}