#[cfg(not(feature = "sync"))]
use std::cell::RefCell as RC;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display};
use std::hash::{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::hash64;
#[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<BTreeMap<u64, Vec<NonNull<T>>>>,
}
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)
}
}
impl<T> HashConsArena<T>
where
T: Eq + Hash,
{
pub fn new() -> Self {
Self {
bump: Bump::new(),
table: RC::new(BTreeMap::new()),
}
}
pub fn intern<'a>(&'a self, value: T) -> HRef<'a, T> {
let hash = hash64(&value);
{
let table = rc::read_table(&self.table);
if let Some(candidates) = table.get(&hash) {
for &ptr in candidates {
let existing = unsafe { ptr.as_ref() };
if *existing == value {
return HRef::new(existing);
}
}
}
}
let allocated = &*self.bump.alloc(value);
let ptr = NonNull::from(allocated);
rc::write_table(&self.table)
.entry(hash)
.or_insert_with(Vec::new)
.push(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<BTreeMap<u64, Vec<Pin<Box<'static, T>>>>>,
}
impl<T> BoxedHashConsArena<T>
where
T: Eq + Hash + 'static,
{
pub fn new() -> Self {
Self {
bump: Bump::new(),
table: RC::new(BTreeMap::new()),
}
}
pub fn intern<'a>(&'a self, value: T) -> HRef<'a, T> {
let hash = hash64(&value);
{
let table = rc::read_table(&self.table);
if let Some(candidates) = table.get(&hash) {
for existing in candidates {
if **existing == value {
let existing_ref = unsafe { mem::transmute::<&T, &'a T>(existing.deref()) };
return HRef::new(existing_ref);
}
}
}
}
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 entry = table.entry(hash).or_insert_with(Vec::new);
entry.push(static_box);
let stored_box = entry.last().unwrap();
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");
assert!(a == b); assert_eq!(a, "hello");
}
#[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);
}
}