use super::common::{self, Interned, RawInterned, UnsafeLock};
use bumpalo::Bump;
use hashbrown::{hash_table::Entry, HashTable};
use std::{
alloc::Layout,
any::TypeId,
borrow,
cell::Cell,
hash::Hasher,
hash::{BuildHasher, Hash},
mem,
ptr::NonNull,
};
pub struct AnyInterner<S = fxhash::FxBuildHasher> {
inner: UnsafeLock<AnyInternSet<S>>,
}
impl AnyInterner {
pub fn of<K: 'static>() -> Self {
let inner = unsafe { UnsafeLock::new(AnyInternSet::of::<K>()) };
Self { inner }
}
}
impl<S: BuildHasher> AnyInterner<S> {
pub fn with_hasher<K: 'static>(hash_builder: S) -> Self {
let inner = unsafe { UnsafeLock::new(AnyInternSet::with_hasher::<K>(hash_builder)) };
Self { inner }
}
pub fn len(&self) -> usize {
self.with_inner(|set| set.len())
}
pub fn is_empty(&self) -> bool {
self.with_inner(|set| set.is_empty())
}
pub unsafe fn intern<K>(&self, value: K) -> Interned<'_, K>
where
K: Hash + Eq + 'static,
{
self.with_inner(|set| unsafe { set.intern(value) })
}
pub unsafe fn intern_with<K, Q, F>(&self, key: &Q, make_value: F) -> Interned<'_, K>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
F: FnOnce() -> K,
{
self.with_inner(|set| unsafe { set.intern_with(key, make_value) })
}
pub unsafe fn get<K, Q>(&self, key: &Q) -> Option<Interned<'_, K>>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
{
self.with_inner(|set| unsafe { set.get(key) })
}
pub fn is_type_of<K: 'static>(&self) -> bool {
self.with_inner(|set| set.is_type_of::<K>())
}
pub fn clear(&mut self) {
self.with_inner(|set| set.clear())
}
fn with_inner<'this, F, R>(&'this self, f: F) -> R
where
F: FnOnce(&'this mut AnyInternSet<S>) -> R,
R: 'this,
{
unsafe {
let set = self.inner.lock().as_mut();
let ret = f(set);
self.inner.unlock();
ret
}
}
}
pub struct AnyInternSet<S = fxhash::FxBuildHasher> {
arena: AnyArena,
set: HashTable<RawInterned>,
hash_builder: S,
}
impl AnyInternSet {
pub fn of<K: 'static>() -> Self {
Self {
arena: AnyArena::of::<K>(),
set: HashTable::new(),
hash_builder: Default::default(),
}
}
}
impl<S: Default> AnyInternSet<S> {
pub fn default_of<K: 'static>() -> Self {
Self {
arena: AnyArena::of::<K>(),
set: HashTable::new(),
hash_builder: Default::default(),
}
}
}
impl<S: BuildHasher> AnyInternSet<S> {
pub fn with_hasher<K: 'static>(hash_builder: S) -> Self {
Self {
arena: AnyArena::of::<K>(),
set: HashTable::new(),
hash_builder,
}
}
pub fn len(&self) -> usize {
self.arena.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub unsafe fn intern<K>(&mut self, value: K) -> Interned<'_, K>
where
K: Hash + Eq + 'static,
{
debug_assert!(self.is_type_of::<K>());
unsafe {
let hash = Self::hash(&self.hash_builder, &value);
let eq = Self::table_eq::<K, K>(&value);
let hasher = Self::table_hasher::<K, K>(&self.hash_builder);
match self.set.entry(hash, eq, hasher) {
Entry::Occupied(entry) => Interned::from_erased_raw(*entry.get()),
Entry::Vacant(entry) => {
let ref_ = self.arena.alloc(value);
let interned = Interned::unique(ref_);
let raw = interned.erased_raw();
entry.insert(raw);
interned
}
}
}
}
pub unsafe fn intern_with<K, Q, F>(&mut self, key: &Q, make_value: F) -> Interned<'_, K>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
F: FnOnce() -> K,
{
debug_assert!(self.is_type_of::<K>());
unsafe {
let hash = Self::hash(&self.hash_builder, key);
let eq = Self::table_eq::<K, Q>(key);
let hasher = Self::table_hasher::<K, Q>(&self.hash_builder);
match self.set.entry(hash, eq, hasher) {
Entry::Occupied(entry) => Interned::from_erased_raw(*entry.get()),
Entry::Vacant(entry) => {
let value = make_value();
let ref_ = self.arena.alloc(value);
let interned = Interned::unique(ref_);
let raw = interned.erased_raw();
entry.insert(raw);
interned
}
}
}
}
pub unsafe fn get<K, Q>(&self, key: &Q) -> Option<Interned<'_, K>>
where
K: borrow::Borrow<Q> + 'static,
Q: Hash + Eq + ?Sized,
{
debug_assert!(self.is_type_of::<K>());
unsafe {
let hash = Self::hash(&self.hash_builder, key);
let eq = Self::table_eq::<K, Q>(key);
self.set
.find(hash, eq)
.map(|raw| Interned::from_erased_raw(*raw))
}
}
pub fn is_type_of<K: 'static>(&self) -> bool {
self.arena.is_type_of::<K>()
}
pub fn clear(&mut self) {
self.arena.clear();
self.set.clear();
}
unsafe fn table_eq<'a, K, Q>(key: &'a Q) -> impl FnMut(&RawInterned) -> bool + 'a
where
K: borrow::Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
move |entry: &RawInterned| unsafe {
let value = entry.cast::<K>().as_ref();
value.borrow() == key
}
}
unsafe fn table_hasher<'a, K, Q>(hash_builder: &'a S) -> impl Fn(&RawInterned) -> u64 + 'a
where
K: borrow::Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
|entry: &RawInterned| unsafe {
let value = entry.cast::<K>().as_ref();
Self::hash(hash_builder, value.borrow())
}
}
fn hash<K: Hash + ?Sized>(hash_builder: &S, value: &K) -> u64 {
let mut hasher = hash_builder.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
pub struct AnyArena {
bump: Bump,
ty: TypeId,
stride: usize,
raw_drop_slice: Option<unsafe fn(*mut u8, usize)>,
len: Cell<usize>,
}
impl AnyArena {
pub fn of<T: 'static>() -> Self {
Self {
bump: Bump::new(),
ty: TypeId::of::<T>(),
stride: Layout::new::<T>().pad_to_align().size(),
raw_drop_slice: if mem::needs_drop::<T>() {
Some(common::cast_then_drop_slice::<T>)
} else {
None
},
len: Cell::new(0),
}
}
pub fn is_type_of<T: 'static>(&self) -> bool {
TypeId::of::<T>() == self.ty
}
pub fn len(&self) -> usize {
self.len.get()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn alloc<T: 'static>(&self, value: T) -> &mut T {
debug_assert!(self.is_type_of::<T>());
self.len.set(self.len() + 1);
self.bump.alloc(value)
}
pub fn clear(&mut self) {
self.drop_all();
self.bump.reset();
self.len.set(0);
}
fn drop_all(&mut self) {
if let Some(raw_drop_slice) = self.raw_drop_slice {
if self.stride > 0 {
unsafe {
for (ptr, len) in self.bump.iter_allocated_chunks_raw() {
let num_elems = len / self.stride;
raw_drop_slice(ptr, num_elems);
}
}
} else {
let ptr = NonNull::<()>::dangling(); unsafe {
raw_drop_slice(ptr.as_ptr().cast(), self.len());
}
}
}
}
}
impl Drop for AnyArena {
fn drop(&mut self) {
self.drop_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_any_interner() {
#[derive(PartialEq, Eq, Hash, Debug)]
struct A(i32);
let interner = AnyInterner::of::<A>();
unsafe {
let a = interner.intern(A(0));
let b = interner.intern(A(0));
let c = interner.intern(A(1));
assert_eq!(a, b);
assert_ne!(a, c);
}
}
#[test]
fn test_arena() {
test_arena_alloc();
test_arena_drop();
}
fn test_arena_alloc() {
const START: u32 = 0;
const END: u32 = 100;
const EXPECTED: u32 = (END + START) * (END - START + 1) / 2;
let arena = AnyArena::of::<u32>();
let mut refs = Vec::new();
for i in START..=END {
let ref_ = arena.alloc(i);
refs.push(ref_);
}
let acc = refs.into_iter().map(|ref_| *ref_).sum::<u32>();
assert_eq!(acc, EXPECTED);
}
fn test_arena_drop() {
macro_rules! test {
($arr_len:literal, $align:literal) => {{
thread_local! {
static SUM: Cell<u32> = Cell::new(0);
static CNT: Cell<u32> = Cell::new(0);
}
#[repr(align($align))]
struct A([u8; $arr_len]);
const _: () = const { assert!($arr_len < 256) };
impl A {
fn new() -> Self {
Self(std::array::from_fn(|i| i as u8))
}
fn sum() -> u32 {
($arr_len - 1) * $arr_len / 2
}
}
impl Drop for A {
fn drop(&mut self) {
let sum = self.0.iter().map(|n| *n as u32).sum::<u32>();
SUM.set(SUM.get() + sum);
CNT.set(CNT.get() + 1);
}
}
struct Zst;
impl Drop for Zst {
fn drop(&mut self) {
CNT.set(CNT.get() + 1);
}
}
const REPEAT: u32 = 10;
let arena = AnyArena::of::<A>();
for _ in 0..REPEAT {
arena.alloc(A::new());
}
drop(arena);
assert_eq!(SUM.get(), A::sum() * REPEAT);
assert_eq!(CNT.get(), REPEAT);
SUM.set(0);
CNT.set(0);
let arena = AnyArena::of::<Zst>();
for _ in 0..REPEAT {
arena.alloc(Zst);
}
drop(arena);
assert_eq!(CNT.get(), REPEAT);
CNT.set(0);
}};
}
test!(1, 1);
test!(1, 2);
test!(1, 4);
test!(1, 8);
test!(1, 16);
test!(1, 32);
test!(1, 64);
test!(1, 128);
test!(1, 256);
test!(100, 1);
test!(100, 2);
test!(100, 4);
test!(100, 8);
test!(100, 16);
test!(100, 32);
test!(100, 64);
test!(100, 128);
test!(100, 256);
}
}