1 //! Global `Arc`-based object interning infrastructure.
3 //! Eventually this should probably be replaced with salsa-based interning.
7 fmt::{self, Debug, Display},
8 hash::{BuildHasherDefault, Hash, Hasher},
13 use dashmap::{DashMap, SharedValue};
14 use lock_api::RwLockWriteGuard;
15 use once_cell::sync::OnceCell;
16 use parking_lot::RawRwLock;
17 use rustc_hash::FxHasher;
19 use crate::generics::GenericParams;
21 type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
22 type Guard<T> = RwLockWriteGuard<
25 HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
28 pub struct Interned<T: Internable + ?Sized> {
32 impl<T: Internable> Interned<T> {
33 pub fn new(obj: T) -> Self {
34 match Interned::lookup(&obj) {
37 let arc = Arc::new(obj);
38 Self::alloc(arc, shard)
44 impl<T: Internable + ?Sized> Interned<T> {
45 fn lookup(obj: &T) -> Result<Self, Guard<T>> {
46 let storage = T::storage().get();
47 let shard_idx = storage.determine_map(obj);
48 let shard = &storage.shards()[shard_idx];
49 let shard = shard.write();
52 // - check if `obj` is already in the map
53 // - if so, clone its `Arc` and return it
54 // - if not, box it up, insert it, and return a clone
55 // This needs to be atomic (locking the shard) to avoid races with other thread, which could
56 // insert the same object between us looking it up and inserting it.
58 // FIXME: avoid double lookup/hashing by using raw entry API (once stable, or when
59 // hashbrown can be plugged into dashmap)
60 match shard.get_key_value(obj) {
61 Some((arc, _)) => Ok(Self { arc: arc.clone() }),
66 fn alloc(arc: Arc<T>, mut shard: Guard<T>) -> Self {
67 let arc2 = arc.clone();
69 shard.insert(arc2, SharedValue::new(()));
76 pub fn new_str(s: &str) -> Self {
77 match Interned::lookup(s) {
80 let arc = Arc::<str>::from(s);
81 Self::alloc(arc, shard)
87 impl<T: Internable + ?Sized> Drop for Interned<T> {
90 // When the last `Ref` is dropped, remove the object from the global map.
91 if Arc::strong_count(&self.arc) == 2 {
92 // Only `self` and the global map point to the object.
99 impl<T: Internable + ?Sized> Interned<T> {
101 fn drop_slow(&mut self) {
102 let storage = T::storage().get();
103 let shard_idx = storage.determine_map(&self.arc);
104 let shard = &storage.shards()[shard_idx];
105 let mut shard = shard.write();
107 // FIXME: avoid double lookup
108 let (arc, _) = shard.get_key_value(&self.arc).expect("interned value removed prematurely");
110 if Arc::strong_count(arc) != 2 {
111 // Another thread has interned another copy
115 shard.remove(&self.arc);
117 // Shrink the backing storage if the shard is less than 50% occupied.
118 if shard.len() * 2 < shard.capacity() {
119 shard.shrink_to_fit();
124 /// Compares interned `Ref`s using pointer equality.
125 impl<T: Internable> PartialEq for Interned<T> {
126 // NOTE: No `?Sized` because `ptr_eq` doesn't work right with trait objects.
129 fn eq(&self, other: &Self) -> bool {
130 Arc::ptr_eq(&self.arc, &other.arc)
134 impl<T: Internable> Eq for Interned<T> {}
136 impl PartialEq for Interned<str> {
137 fn eq(&self, other: &Self) -> bool {
138 Arc::ptr_eq(&self.arc, &other.arc)
142 impl Eq for Interned<str> {}
144 impl<T: Internable + ?Sized> Hash for Interned<T> {
145 fn hash<H: Hasher>(&self, state: &mut H) {
146 // NOTE: Cast disposes vtable pointer / slice/str length.
147 state.write_usize(Arc::as_ptr(&self.arc) as *const () as usize)
151 impl<T: Internable + ?Sized> AsRef<T> for Interned<T> {
153 fn as_ref(&self) -> &T {
158 impl<T: Internable + ?Sized> Deref for Interned<T> {
162 fn deref(&self) -> &Self::Target {
167 impl<T: Internable + ?Sized> Clone for Interned<T> {
168 fn clone(&self) -> Self {
169 Self { arc: self.arc.clone() }
173 impl<T: Debug + Internable + ?Sized> Debug for Interned<T> {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 impl<T: Display + Internable + ?Sized> Display for Interned<T> {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 pub struct InternStorage<T: ?Sized> {
186 map: OnceCell<InternMap<T>>,
189 impl<T: ?Sized> InternStorage<T> {
190 pub const fn new() -> Self {
191 Self { map: OnceCell::new() }
195 impl<T: Internable + ?Sized> InternStorage<T> {
196 fn get(&self) -> &InternMap<T> {
197 self.map.get_or_init(DashMap::default)
201 pub trait Internable: Hash + Eq + 'static {
202 fn storage() -> &'static InternStorage<Self>;
205 /// Implements `Internable` for a given list of types, making them usable with `Interned`.
208 macro_rules! _impl_internable {
209 ( $($t:path),+ $(,)? ) => { $(
210 impl Internable for $t {
211 fn storage() -> &'static InternStorage<Self> {
212 static STORAGE: InternStorage<$t> = InternStorage::new();
219 pub use crate::_impl_internable as impl_internable;
222 crate::type_ref::TypeRef,
223 crate::type_ref::TraitRef,
224 crate::type_ref::TypeBound,
225 crate::path::ModPath,
226 crate::path::GenericArgs,
227 crate::attr::AttrInput,