2021-04-01 12:46:43 -05:00
|
|
|
//! Global `Arc`-based object interning infrastructure.
|
|
|
|
//!
|
|
|
|
//! Eventually this should probably be replaced with salsa-based interning.
|
|
|
|
|
|
|
|
use std::{
|
2021-05-19 08:17:57 -05:00
|
|
|
fmt::{self, Debug, Display},
|
2021-04-05 10:07:53 -05:00
|
|
|
hash::{BuildHasherDefault, Hash, Hasher},
|
2021-04-01 12:46:43 -05:00
|
|
|
ops::Deref,
|
|
|
|
};
|
|
|
|
|
2022-02-09 01:19:57 -06:00
|
|
|
use dashmap::{DashMap, SharedValue};
|
2022-06-10 08:59:46 -05:00
|
|
|
use hashbrown::HashMap;
|
2021-04-01 12:46:43 -05:00
|
|
|
use once_cell::sync::OnceCell;
|
|
|
|
use rustc_hash::FxHasher;
|
2023-05-02 09:12:22 -05:00
|
|
|
use triomphe::Arc;
|
2021-04-01 12:46:43 -05:00
|
|
|
|
|
|
|
type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
|
2022-06-10 08:59:46 -05:00
|
|
|
type Guard<T> = dashmap::RwLockWriteGuard<
|
2022-02-09 01:19:57 -06:00
|
|
|
'static,
|
|
|
|
HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
|
|
|
|
>;
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
pub struct Interned<T: Internable + ?Sized> {
|
2021-04-01 12:46:43 -05:00
|
|
|
arc: Arc<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Internable> Interned<T> {
|
|
|
|
pub fn new(obj: T) -> Self {
|
2021-04-02 18:00:45 -05:00
|
|
|
match Interned::lookup(&obj) {
|
|
|
|
Ok(this) => this,
|
|
|
|
Err(shard) => {
|
|
|
|
let arc = Arc::new(obj);
|
|
|
|
Self::alloc(arc, shard)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Internable + ?Sized> Interned<T> {
|
|
|
|
fn lookup(obj: &T) -> Result<Self, Guard<T>> {
|
2021-04-01 12:46:43 -05:00
|
|
|
let storage = T::storage().get();
|
2021-04-02 18:00:45 -05:00
|
|
|
let shard_idx = storage.determine_map(obj);
|
2021-04-01 12:46:43 -05:00
|
|
|
let shard = &storage.shards()[shard_idx];
|
2021-04-02 18:00:45 -05:00
|
|
|
let shard = shard.write();
|
2021-04-01 12:46:43 -05:00
|
|
|
|
|
|
|
// Atomically,
|
|
|
|
// - check if `obj` is already in the map
|
|
|
|
// - if so, clone its `Arc` and return it
|
|
|
|
// - if not, box it up, insert it, and return a clone
|
|
|
|
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
|
|
|
|
// insert the same object between us looking it up and inserting it.
|
|
|
|
|
2021-04-02 18:00:45 -05:00
|
|
|
// FIXME: avoid double lookup/hashing by using raw entry API (once stable, or when
|
|
|
|
// hashbrown can be plugged into dashmap)
|
|
|
|
match shard.get_key_value(obj) {
|
|
|
|
Some((arc, _)) => Ok(Self { arc: arc.clone() }),
|
|
|
|
None => Err(shard),
|
2021-04-01 12:46:43 -05:00
|
|
|
}
|
2021-04-02 18:00:45 -05:00
|
|
|
}
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-02 18:00:45 -05:00
|
|
|
fn alloc(arc: Arc<T>, mut shard: Guard<T>) -> Self {
|
2021-04-01 12:46:43 -05:00
|
|
|
let arc2 = arc.clone();
|
|
|
|
|
2021-04-02 13:46:37 -05:00
|
|
|
shard.insert(arc2, SharedValue::new(()));
|
2021-04-01 12:46:43 -05:00
|
|
|
|
|
|
|
Self { arc }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 18:00:45 -05:00
|
|
|
impl Interned<str> {
|
|
|
|
pub fn new_str(s: &str) -> Self {
|
|
|
|
match Interned::lookup(s) {
|
|
|
|
Ok(this) => this,
|
|
|
|
Err(shard) => {
|
|
|
|
let arc = Arc::<str>::from(s);
|
|
|
|
Self::alloc(arc, shard)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Internable + ?Sized> Drop for Interned<T> {
|
2021-04-02 11:11:08 -05:00
|
|
|
#[inline]
|
2021-04-01 12:46:43 -05:00
|
|
|
fn drop(&mut self) {
|
|
|
|
// When the last `Ref` is dropped, remove the object from the global map.
|
2023-05-02 09:12:22 -05:00
|
|
|
if Arc::count(&self.arc) == 2 {
|
2021-04-01 12:46:43 -05:00
|
|
|
// Only `self` and the global map point to the object.
|
|
|
|
|
2021-04-02 11:11:08 -05:00
|
|
|
self.drop_slow();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Internable + ?Sized> Interned<T> {
|
|
|
|
#[cold]
|
|
|
|
fn drop_slow(&mut self) {
|
|
|
|
let storage = T::storage().get();
|
|
|
|
let shard_idx = storage.determine_map(&self.arc);
|
|
|
|
let shard = &storage.shards()[shard_idx];
|
|
|
|
let mut shard = shard.write();
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-02 11:11:08 -05:00
|
|
|
// FIXME: avoid double lookup
|
|
|
|
let (arc, _) = shard.get_key_value(&self.arc).expect("interned value removed prematurely");
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2023-05-02 09:12:22 -05:00
|
|
|
if Arc::count(arc) != 2 {
|
2021-04-02 11:11:08 -05:00
|
|
|
// Another thread has interned another copy
|
|
|
|
return;
|
|
|
|
}
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-02 11:11:08 -05:00
|
|
|
shard.remove(&self.arc);
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-02 11:11:08 -05:00
|
|
|
// Shrink the backing storage if the shard is less than 50% occupied.
|
|
|
|
if shard.len() * 2 < shard.capacity() {
|
|
|
|
shard.shrink_to_fit();
|
2021-04-01 12:46:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Compares interned `Ref`s using pointer equality.
|
2021-04-02 11:26:34 -05:00
|
|
|
impl<T: Internable> PartialEq for Interned<T> {
|
|
|
|
// NOTE: No `?Sized` because `ptr_eq` doesn't work right with trait objects.
|
|
|
|
|
2021-04-01 12:46:43 -05:00
|
|
|
#[inline]
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
Arc::ptr_eq(&self.arc, &other.arc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 11:26:34 -05:00
|
|
|
impl<T: Internable> Eq for Interned<T> {}
|
2021-04-01 12:46:43 -05:00
|
|
|
|
2021-04-02 18:00:45 -05:00
|
|
|
impl PartialEq for Interned<str> {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
Arc::ptr_eq(&self.arc, &other.arc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for Interned<str> {}
|
|
|
|
|
2021-04-05 10:07:53 -05:00
|
|
|
impl<T: Internable + ?Sized> Hash for Interned<T> {
|
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
|
|
// NOTE: Cast disposes vtable pointer / slice/str length.
|
|
|
|
state.write_usize(Arc::as_ptr(&self.arc) as *const () as usize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Internable + ?Sized> AsRef<T> for Interned<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
#[inline]
|
|
|
|
fn as_ref(&self) -> &T {
|
|
|
|
&self.arc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Internable + ?Sized> Deref for Interned<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
type Target = T;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.arc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Internable + ?Sized> Clone for Interned<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self { arc: self.arc.clone() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Debug + Internable + ?Sized> Debug for Interned<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
(*self.arc).fmt(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 08:17:57 -05:00
|
|
|
impl<T: Display + Internable + ?Sized> Display for Interned<T> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
(*self.arc).fmt(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
pub struct InternStorage<T: ?Sized> {
|
2021-04-01 12:46:43 -05:00
|
|
|
map: OnceCell<InternMap<T>>,
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: ?Sized> InternStorage<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
pub const fn new() -> Self {
|
|
|
|
Self { map: OnceCell::new() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
impl<T: Internable + ?Sized> InternStorage<T> {
|
2021-04-01 12:46:43 -05:00
|
|
|
fn get(&self) -> &InternMap<T> {
|
|
|
|
self.map.get_or_init(DashMap::default)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 15:24:40 -05:00
|
|
|
pub trait Internable: Hash + Eq + 'static {
|
2021-04-01 12:46:43 -05:00
|
|
|
fn storage() -> &'static InternStorage<Self>;
|
|
|
|
}
|
|
|
|
|
2021-04-05 09:59:03 -05:00
|
|
|
/// Implements `Internable` for a given list of types, making them usable with `Interned`.
|
|
|
|
#[macro_export]
|
|
|
|
#[doc(hidden)]
|
|
|
|
macro_rules! _impl_internable {
|
2021-04-02 11:26:34 -05:00
|
|
|
( $($t:path),+ $(,)? ) => { $(
|
2023-01-09 12:29:28 -06:00
|
|
|
impl $crate::Internable for $t {
|
|
|
|
fn storage() -> &'static $crate::InternStorage<Self> {
|
|
|
|
static STORAGE: $crate::InternStorage<$t> = $crate::InternStorage::new();
|
2021-04-01 12:46:43 -05:00
|
|
|
&STORAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)+ };
|
|
|
|
}
|
|
|
|
|
2021-04-05 09:59:03 -05:00
|
|
|
pub use crate::_impl_internable as impl_internable;
|
|
|
|
|
2023-01-09 12:29:28 -06:00
|
|
|
impl_internable!(str,);
|