#[cfg(not(feature = "sync"))]
use std::cell::RefCell as RC;
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::hash::{BuildHasher, Hash, Hasher};
use std::mem;
use std::ops::Deref;
use std::pin::Pin;
use std::ptr::NonNull;
use bumpalo::Bump;
use bumpalo::boxed::Box;
use fxhash::FxBuildHasher;
use hashbrown::HashMap;
#[cfg(feature = "sync")]
use parking_lot::RwLock as RC;
#[cfg(feature = "sync")]
mod rc {
use super::RC;
#[inline(always)]
pub(crate) fn read_table<T>(table: &RC<T>) -> parking_lot::RwLockReadGuard<'_, T> {
table.read()
}
#[inline(always)]
pub(crate) fn write_table<T>(table: &RC<T>) -> parking_lot::RwLockWriteGuard<'_, T> {
table.write()
}
}
#[cfg(not(feature = "sync"))]
mod rc {
use super::RC;
#[inline(always)]
pub(crate) fn read_table<T>(table: &RC<T>) -> std::cell::Ref<'_, T> {
table.borrow()
}
#[inline(always)]
pub(crate) fn write_table<T>(table: &RC<T>) -> std::cell::RefMut<'_, T> {
table.borrow_mut()
}
}
pub struct HashConsArena<T> {
bump: Bump,
table: RC<HashMap<AsVal<NonNull<T>>, (), FxBuildHasher>>,
}
pub struct HRef<'a, T> {
ptr: &'a T,
}
impl<'a, T> Clone for HRef<'a, T> {
fn clone(&self) -> Self {
Self { ptr: self.ptr }
}
}
impl<'a, T> Copy for HRef<'a, T> {}
impl<'a, T> HRef<'a, T> {
pub(crate) fn new(ptr: &'a T) -> Self {
Self { ptr }
}
pub(crate) fn as_ptr(&self) -> *const T {
self.ptr as *const T
}
pub fn as_ref(&self) -> &'a T {
self.ptr
}
}
impl<'a, T> Deref for HRef<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.ptr
}
}
impl<'a, T> PartialEq for HRef<'a, T> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.ptr, other.ptr)
}
}
impl<'a, T> PartialEq<T> for HRef<'a, T>
where
T: PartialEq,
{
fn eq(&self, other: &T) -> bool {
self.as_ref() == other
}
}
impl<'a, T> Eq for HRef<'a, T> {}
impl<'a, T> Hash for HRef<'a, T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_ptr().hash(state);
}
}
impl<'a, T> PartialOrd for HRef<'a, T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a, T> Ord for HRef<'a, T> {
fn cmp(&self, other: &Self) -> Ordering {
self.as_ptr().cmp(&other.as_ptr())
}
}
impl<'a, T> Debug for HRef<'a, T>
where
T: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HRef")
.field("ptr", &(self.as_ptr() as usize))
.field("val", &self.ptr)
.finish()
}
}
impl<'a, T> Display for HRef<'a, T>
where
T: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.ptr.fmt(f)
}
}
#[repr(transparent)]
pub struct AsVal<T>(T);
impl<T> PartialEq for AsVal<NonNull<T>>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
unsafe { self.0.as_ref() == other.0.as_ref() }
}
}
impl<T> Eq for AsVal<NonNull<T>> where T: Eq {}
impl<T> Hash for AsVal<NonNull<T>>
where
T: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
unsafe { self.0.as_ref() }.hash(state);
}
}
impl<T> HashConsArena<T>
where
T: Eq + Hash,
{
pub fn new() -> Self {
Self {
bump: Bump::new(),
table: RC::new(HashMap::<_, _, FxBuildHasher>::default()),
}
}
pub fn intern<'a>(&'a self, value: T) -> HRef<'a, T>
where
T: Debug,
{
let hash = {
let table = rc::read_table(&self.table);
let mut hasher = table.hasher().build_hasher();
value.hash(&mut hasher);
let hash = hasher.finish();
if let Some((AsVal(existing), _)) = table
.raw_entry()
.from_hash(hash, |AsVal(ptr)| unsafe { *ptr.as_ref() == value })
{
return HRef::new(unsafe { existing.as_ref() });
}
hash
};
let allocated = &*self.bump.alloc(value);
let ptr = NonNull::from(allocated);
rc::write_table(&self.table)
.raw_entry_mut()
.from_hash(hash, |AsVal(ptr)| unsafe { ptr.as_ref() == allocated })
.or_insert(AsVal(ptr), ());
HRef::new(allocated)
}
pub fn reset(&mut self) {
rc::write_table(&self.table).clear();
self.bump.reset();
}
}
pub struct BoxedHashConsArena<T>
where
T: 'static,
{
bump: Bump,
table: RC<HashMap<Pin<Box<'static, T>>, (), FxBuildHasher>>,
}
impl<T> BoxedHashConsArena<T>
where
T: Eq + Hash + 'static,
{
pub fn new() -> Self {
Self {
bump: Bump::new(),
table: RC::new(HashMap::<_, _, FxBuildHasher>::default()),
}
}
pub fn intern<'a>(&'a self, value: T) -> HRef<'a, T> {
let hash = {
let table = rc::read_table(&self.table);
let mut hasher = table.hasher().build_hasher();
value.hash(&mut hasher);
let hash = hasher.finish();
if let Some((existing, _)) = table
.raw_entry()
.from_hash(hash, |ptr| *ptr.as_ref() == value)
{
let existing_ref = unsafe { mem::transmute::<&T, &'a T>(existing.deref()) };
return HRef::new(existing_ref);
}
hash
};
let allocated = Box::pin_in(value, &self.bump);
let static_box =
unsafe { mem::transmute::<Pin<Box<'_, T>>, Pin<Box<'static, T>>>(allocated) };
let mut table = rc::write_table(&self.table);
let (stored_box, _) = table
.raw_entry_mut()
.from_hash(hash, |ptr| *ptr.as_ref() == *static_box.deref())
.or_insert(static_box, ());
let allocated_ref = unsafe { mem::transmute::<&T, &'a T>(stored_box.deref()) };
HRef::new(allocated_ref)
}
pub fn reset(&mut self) {
rc::write_table(&self.table).clear();
self.bump.reset();
}
}
impl<T> Drop for BoxedHashConsArena<T> {
fn drop(&mut self) {
rc::write_table(&self.table).clear();
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_interning() {
let arena = HashConsArena::new();
let a = arena.intern("hello");
let b = arena.intern("hello");
let c = arena.intern("world");
assert!(a == b); assert!(a != c); assert_eq!(a, "hello");
assert_eq!(b, "hello");
assert_eq!(c, "world");
}
#[test]
fn test_multiple_arenas() {
let arena1 = HashConsArena::new();
let arena2 = HashConsArena::new();
let a1 = arena1.intern("hello");
let a2 = arena2.intern("hello");
assert!(a1 != a2); assert_eq!(a1, "hello");
assert_eq!(a2, "hello");
}
#[test]
fn test_drop() {
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
let arena = BoxedHashConsArena::new();
let drop_ctr = Arc::new(AtomicUsize::new(0));
struct MyStr {
value: String,
drop_ctr: Arc<AtomicUsize>,
}
impl MyStr {
fn new(value: impl Into<String>, drop_ctr: Arc<AtomicUsize>) -> Self {
Self {
value: value.into(),
drop_ctr,
}
}
}
impl PartialEq for MyStr {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl Eq for MyStr {}
impl Hash for MyStr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl Drop for MyStr {
fn drop(&mut self) {
self.drop_ctr
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
let a = arena.intern(MyStr::new("hello", drop_ctr.clone()));
let b = arena.intern(MyStr::new("world", drop_ctr.clone()));
assert!(a != b);
drop(arena);
assert_eq!(drop_ctr.load(std::sync::atomic::Ordering::SeqCst), 2);
}
#[test]
fn test_reset() {
let mut arena = HashConsArena::new();
let _a = arena.intern("hello");
assert_eq!(rc::read_table(&arena.table).len(), 1);
arena.reset();
assert_eq!(rc::read_table(&arena.table).len(), 0);
}
}