1 //! Global `Arc`-based object interning infrastructure. 2 //! 3 //! Eventually this should probably be replaced with salsa-based interning. 4 5 use std::{ 6 collections::HashMap, 7 fmt::{self, Debug, Display}, 8 hash::{BuildHasherDefault, Hash, Hasher}, 9 ops::Deref, 10 sync::Arc, 11 }; 12 13 use dashmap::{lock::RwLockWriteGuard, DashMap, SharedValue}; 14 use once_cell::sync::OnceCell; 15 use rustc_hash::FxHasher; 16 17 use crate::generics::GenericParams; 18 19 type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>; 20 type Guard<T> = 21 RwLockWriteGuard<'static, HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>>; 22 23 pub struct Interned<T: Internable + ?Sized> { 24 arc: Arc<T>, 25 } 26 27 impl<T: Internable> Interned<T> { new(obj: T) -> Self28 pub fn new(obj: T) -> Self { 29 match Interned::lookup(&obj) { 30 Ok(this) => this, 31 Err(shard) => { 32 let arc = Arc::new(obj); 33 Self::alloc(arc, shard) 34 } 35 } 36 } 37 } 38 39 impl<T: Internable + ?Sized> Interned<T> { lookup(obj: &T) -> Result<Self, Guard<T>>40 fn lookup(obj: &T) -> Result<Self, Guard<T>> { 41 let storage = T::storage().get(); 42 let shard_idx = storage.determine_map(obj); 43 let shard = &storage.shards()[shard_idx]; 44 let shard = shard.write(); 45 46 // Atomically, 47 // - check if `obj` is already in the map 48 // - if so, clone its `Arc` and return it 49 // - if not, box it up, insert it, and return a clone 50 // This needs to be atomic (locking the shard) to avoid races with other thread, which could 51 // insert the same object between us looking it up and inserting it. 52 53 // FIXME: avoid double lookup/hashing by using raw entry API (once stable, or when 54 // hashbrown can be plugged into dashmap) 55 match shard.get_key_value(obj) { 56 Some((arc, _)) => Ok(Self { arc: arc.clone() }), 57 None => Err(shard), 58 } 59 } 60 alloc(arc: Arc<T>, mut shard: Guard<T>) -> Self61 fn alloc(arc: Arc<T>, mut shard: Guard<T>) -> Self { 62 let arc2 = arc.clone(); 63 64 shard.insert(arc2, SharedValue::new(())); 65 66 Self { arc } 67 } 68 } 69 70 impl Interned<str> { new_str(s: &str) -> Self71 pub fn new_str(s: &str) -> Self { 72 match Interned::lookup(s) { 73 Ok(this) => this, 74 Err(shard) => { 75 let arc = Arc::<str>::from(s); 76 Self::alloc(arc, shard) 77 } 78 } 79 } 80 } 81 82 impl<T: Internable + ?Sized> Drop for Interned<T> { 83 #[inline] drop(&mut self)84 fn drop(&mut self) { 85 // When the last `Ref` is dropped, remove the object from the global map. 86 if Arc::strong_count(&self.arc) == 2 { 87 // Only `self` and the global map point to the object. 88 89 self.drop_slow(); 90 } 91 } 92 } 93 94 impl<T: Internable + ?Sized> Interned<T> { 95 #[cold] drop_slow(&mut self)96 fn drop_slow(&mut self) { 97 let storage = T::storage().get(); 98 let shard_idx = storage.determine_map(&self.arc); 99 let shard = &storage.shards()[shard_idx]; 100 let mut shard = shard.write(); 101 102 // FIXME: avoid double lookup 103 let (arc, _) = shard.get_key_value(&self.arc).expect("interned value removed prematurely"); 104 105 if Arc::strong_count(arc) != 2 { 106 // Another thread has interned another copy 107 return; 108 } 109 110 shard.remove(&self.arc); 111 112 // Shrink the backing storage if the shard is less than 50% occupied. 113 if shard.len() * 2 < shard.capacity() { 114 shard.shrink_to_fit(); 115 } 116 } 117 } 118 119 /// Compares interned `Ref`s using pointer equality. 120 impl<T: Internable> PartialEq for Interned<T> { 121 // NOTE: No `?Sized` because `ptr_eq` doesn't work right with trait objects. 122 123 #[inline] eq(&self, other: &Self) -> bool124 fn eq(&self, other: &Self) -> bool { 125 Arc::ptr_eq(&self.arc, &other.arc) 126 } 127 } 128 129 impl<T: Internable> Eq for Interned<T> {} 130 131 impl PartialEq for Interned<str> { eq(&self, other: &Self) -> bool132 fn eq(&self, other: &Self) -> bool { 133 Arc::ptr_eq(&self.arc, &other.arc) 134 } 135 } 136 137 impl Eq for Interned<str> {} 138 139 impl<T: Internable + ?Sized> Hash for Interned<T> { hash<H: Hasher>(&self, state: &mut H)140 fn hash<H: Hasher>(&self, state: &mut H) { 141 // NOTE: Cast disposes vtable pointer / slice/str length. 142 state.write_usize(Arc::as_ptr(&self.arc) as *const () as usize) 143 } 144 } 145 146 impl<T: Internable + ?Sized> AsRef<T> for Interned<T> { 147 #[inline] as_ref(&self) -> &T148 fn as_ref(&self) -> &T { 149 &self.arc 150 } 151 } 152 153 impl<T: Internable + ?Sized> Deref for Interned<T> { 154 type Target = T; 155 156 #[inline] deref(&self) -> &Self::Target157 fn deref(&self) -> &Self::Target { 158 &self.arc 159 } 160 } 161 162 impl<T: Internable + ?Sized> Clone for Interned<T> { clone(&self) -> Self163 fn clone(&self) -> Self { 164 Self { arc: self.arc.clone() } 165 } 166 } 167 168 impl<T: Debug + Internable + ?Sized> Debug for Interned<T> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 170 (*self.arc).fmt(f) 171 } 172 } 173 174 impl<T: Display + Internable + ?Sized> Display for Interned<T> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 176 (*self.arc).fmt(f) 177 } 178 } 179 180 pub struct InternStorage<T: ?Sized> { 181 map: OnceCell<InternMap<T>>, 182 } 183 184 impl<T: ?Sized> InternStorage<T> { new() -> Self185 pub const fn new() -> Self { 186 Self { map: OnceCell::new() } 187 } 188 } 189 190 impl<T: Internable + ?Sized> InternStorage<T> { get(&self) -> &InternMap<T>191 fn get(&self) -> &InternMap<T> { 192 self.map.get_or_init(DashMap::default) 193 } 194 } 195 196 pub trait Internable: Hash + Eq + 'static { storage() -> &'static InternStorage<Self>197 fn storage() -> &'static InternStorage<Self>; 198 } 199 200 /// Implements `Internable` for a given list of types, making them usable with `Interned`. 201 #[macro_export] 202 #[doc(hidden)] 203 macro_rules! _impl_internable { 204 ( $($t:path),+ $(,)? ) => { $( 205 impl Internable for $t { 206 fn storage() -> &'static InternStorage<Self> { 207 static STORAGE: InternStorage<$t> = InternStorage::new(); 208 &STORAGE 209 } 210 } 211 )+ }; 212 } 213 214 pub use crate::_impl_internable as impl_internable; 215 216 impl_internable!( 217 crate::type_ref::TypeRef, 218 crate::type_ref::TraitRef, 219 crate::type_ref::TypeBound, 220 crate::path::ModPath, 221 crate::path::GenericArgs, 222 crate::attr::AttrInput, 223 GenericParams, 224 str, 225 ); 226