1use crate::debug::TableEntry;
2use crate::durability::Durability;
3use crate::intern_id::InternId;
4use crate::plumbing::CycleRecoveryStrategy;
5use crate::plumbing::HasQueryGroup;
6use crate::plumbing::QueryStorageMassOps;
7use crate::plumbing::QueryStorageOps;
8use crate::revision::Revision;
9use crate::Query;
10use crate::QueryTable;
11use crate::{Database, DatabaseKeyIndex, QueryDb};
12use parking_lot::RwLock;
13use rustc_hash::FxHashMap;
14use std::collections::hash_map::Entry;
15use std::fmt::Debug;
16use std::hash::Hash;
17use triomphe::Arc;
18
19const INTERN_DURABILITY: Durability = Durability::HIGH;
20
21pub struct InternedStorage<Q>
24where
25 Q: Query,
26 Q::Key: InternValue,
27 Q::Value: InternKey,
28{
29 group_index: u16,
30 tables: RwLock<InternTables<MappedKey<Q>, Q::Key>>,
31}
32
33pub struct LookupInternedStorage<Q, IQ>
35where
36 Q: Query,
37 Q::Key: InternKey,
38 Q::Value: InternValue,
39{
40 phantom: std::marker::PhantomData<(Q::Key, IQ)>,
41}
42
43struct InternTables<K, V> {
44 map: FxHashMap<K, InternId>,
46
47 values: Vec<Arc<Slot<V>>>,
49}
50
51pub trait InternKey {
55 fn from_intern_id(v: InternId) -> Self;
57
58 fn as_intern_id(&self) -> InternId;
60}
61
62impl InternKey for InternId {
63 fn from_intern_id(v: InternId) -> InternId {
64 v
65 }
66
67 fn as_intern_id(&self) -> InternId {
68 *self
69 }
70}
71
72pub trait InternValue {
74 type Key: Eq + Hash + Debug + Clone;
76 fn into_key(&self) -> Self::Key;
78 #[inline]
82 fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
83 f(&self.into_key())
84 }
85}
86
87impl<A: InternValue + Eq + Hash + Debug + Clone, B: InternValue + Eq + Hash + Debug + Clone>
88 InternValue for (A, B)
89{
90 type Key = Self;
91 #[inline]
92 fn into_key(&self) -> Self::Key {
93 self.clone()
94 }
95 #[inline]
96 fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
97 f(self)
98 }
99}
100
101pub trait InternValueTrivial
102where
103 Self: Eq + Hash + Debug + Clone,
104{
105}
106
107impl<V: InternValueTrivial> InternValue for V {
109 type Key = Self;
110 #[inline]
111 fn into_key(&self) -> Self::Key {
112 self.clone()
113 }
114 #[inline]
115 fn with_key<F: FnOnce(&Self::Key) -> T, T>(&self, f: F) -> T {
116 f(self)
117 }
118}
119
120impl InternValueTrivial for String {}
121
122#[derive(Debug)]
123struct Slot<V> {
124 key_index: u32,
126
127 value: V,
129
130 interned_at: Revision,
134}
135
136impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q>
137where
138 Q: Query,
139 Q::Key: InternValue,
140 Q::Key: std::panic::RefUnwindSafe,
141 Q::Value: InternKey,
142 Q::Value: std::panic::RefUnwindSafe,
143{
144}
145
146impl<K: Debug + Hash + Eq, V> InternTables<K, V> {
147 fn slot_for_key(&self, key: &K) -> Option<(Arc<Slot<V>>, InternId)> {
149 let &index = self.map.get(key)?;
150 Some((self.slot_for_index(index), index))
151 }
152
153 fn slot_for_index(&self, index: InternId) -> Arc<Slot<V>> {
155 let slot = &self.values[index.as_usize()];
156 slot.clone()
157 }
158}
159
160impl<K, V> Default for InternTables<K, V>
161where
162 K: Eq + Hash,
163{
164 fn default() -> Self {
165 Self { map: Default::default(), values: Default::default() }
166 }
167}
168
169type MappedKey<Q> = <<Q as Query>::Key as InternValue>::Key;
170
171impl<Q> InternedStorage<Q>
172where
173 Q: Query,
174 Q::Key: InternValue,
175 Q::Value: InternKey,
176{
177 fn intern_index(
179 &self,
180 db: &<Q as QueryDb<'_>>::DynDb,
181 mapped_key: MappedKey<Q>,
182 insert: impl FnOnce(Q::Value) -> Q::Key,
183 ) -> (Arc<Slot<Q::Key>>, InternId) {
184 let revision_now = db.salsa_runtime().current_revision();
185
186 let mut tables = self.tables.write();
187 let tables = &mut *tables;
188 let entry = match tables.map.entry(mapped_key) {
189 Entry::Vacant(entry) => entry,
190 Entry::Occupied(entry) => {
191 let index = *entry.get();
196 let slot = &tables.values[index.as_usize()];
197 return (slot.clone(), index);
198 }
199 };
200
201 let create_slot = |index: InternId| {
202 Arc::new(Slot {
203 key_index: index.as_u32(),
204 value: insert(Q::Value::from_intern_id(index)),
205 interned_at: revision_now,
206 })
207 };
208
209 let index = InternId::from(tables.values.len());
210 let slot = create_slot(index);
211 tables.values.push(slot.clone());
212 entry.insert(index);
213
214 (slot, index)
215 }
216
217 fn intern_check(&self, key: &MappedKey<Q>) -> Option<(Arc<Slot<Q::Key>>, InternId)> {
218 self.tables.read().slot_for_key(key)
219 }
220
221 fn lookup_value(&self, index: InternId) -> Arc<Slot<Q::Key>> {
224 self.tables.read().slot_for_index(index)
225 }
226
227 fn fetch_or_insert(
228 &self,
229 db: &<Q as QueryDb<'_>>::DynDb,
230 key: MappedKey<Q>,
231 insert: impl FnOnce(Q::Value) -> Q::Key,
232 ) -> Q::Value {
233 db.unwind_if_cancelled();
234 let (slot, index) = match self.intern_check(&key) {
235 Some(i) => i,
236 None => self.intern_index(db, key, insert),
237 };
238 let changed_at = slot.interned_at;
239 db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
240 DatabaseKeyIndex {
241 group_index: self.group_index,
242 query_index: Q::QUERY_INDEX,
243 key_index: slot.key_index,
244 },
245 INTERN_DURABILITY,
246 changed_at,
247 );
248 <Q::Value>::from_intern_id(index)
249 }
250}
251
252impl<Q> QueryStorageOps<Q> for InternedStorage<Q>
253where
254 Q: Query,
255 Q::Key: InternValue,
256 Q::Value: InternKey,
257{
258 const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
259
260 fn new(group_index: u16) -> Self {
261 InternedStorage { group_index, tables: RwLock::new(InternTables::default()) }
262 }
263
264 fn fmt_index(
265 &self,
266 _db: &<Q as QueryDb<'_>>::DynDb,
267 index: u32,
268 fmt: &mut std::fmt::Formatter<'_>,
269 ) -> std::fmt::Result {
270 let intern_id = InternId::from(index);
271 let slot = self.lookup_value(intern_id);
272 write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value)
273 }
274
275 fn maybe_changed_after(
276 &self,
277 db: &<Q as QueryDb<'_>>::DynDb,
278 input: u32,
279 revision: Revision,
280 ) -> bool {
281 debug_assert!(revision < db.salsa_runtime().current_revision());
282 let intern_id = InternId::from(input);
283 let slot = self.lookup_value(intern_id);
284 slot.maybe_changed_after(revision)
285 }
286
287 fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
288 db.unwind_if_cancelled();
289
290 let (slot, index) = match key.with_key(|key| self.intern_check(key)) {
291 Some(i) => i,
292 None => self.intern_index(db, key.into_key(), |_| key.clone()),
293 };
294 let changed_at = slot.interned_at;
295 db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
296 DatabaseKeyIndex {
297 group_index: self.group_index,
298 query_index: Q::QUERY_INDEX,
299 key_index: slot.key_index,
300 },
301 INTERN_DURABILITY,
302 changed_at,
303 );
304 <Q::Value>::from_intern_id(index)
305 }
306
307 fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
308 INTERN_DURABILITY
309 }
310
311 fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
312 where
313 C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
314 {
315 let tables = self.tables.read();
316 tables
317 .map
318 .values()
319 .map(|index| {
320 TableEntry::new(
321 tables.values[index.as_usize()].value.clone(),
322 Some(<Q::Value>::from_intern_id(*index)),
323 )
324 })
325 .collect()
326 }
327}
328
329impl<Q> QueryStorageMassOps for InternedStorage<Q>
330where
331 Q: Query,
332 Q::Key: InternValue,
333 Q::Value: InternKey,
334{
335 fn purge(&self) {
336 *self.tables.write() = Default::default();
337 }
338}
339
340#[doc(hidden)]
351pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
352where
353 IQ: QueryDb<'d>,
354{
355 fn convert_db(d: &Self::DynDb) -> &IQ::DynDb;
356 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
357}
358
359impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
360where
361 Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>,
362 Q::DynDb: HasQueryGroup<Q::Group>,
363 IQ: QueryDb<'d>,
364{
365 fn convert_db(d: &Self::DynDb) -> &IQ::DynDb {
366 d
367 }
368 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
369 d
370 }
371}
372
373impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
374where
375 Q: Query,
376 Q::Key: InternKey,
377 Q::Value: InternValue,
378 IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
379 for<'d> Q: EqualDynDb<'d, IQ>,
380{
381 const CYCLE_STRATEGY: CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
382
383 fn new(_group_index: u16) -> Self {
384 LookupInternedStorage { phantom: std::marker::PhantomData }
385 }
386
387 fn fmt_index(
388 &self,
389 db: &<Q as QueryDb<'_>>::DynDb,
390 index: u32,
391 fmt: &mut std::fmt::Formatter<'_>,
392 ) -> std::fmt::Result {
393 let group_storage =
394 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
395 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
396 interned_storage.fmt_index(Q::convert_db(db), index, fmt)
397 }
398
399 fn maybe_changed_after(
400 &self,
401 db: &<Q as QueryDb<'_>>::DynDb,
402 input: u32,
403 revision: Revision,
404 ) -> bool {
405 let group_storage =
406 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
407 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
408 interned_storage.maybe_changed_after(Q::convert_db(db), input, revision)
409 }
410
411 fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
412 let index = key.as_intern_id();
413 let group_storage =
414 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
415 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
416 let slot = interned_storage.lookup_value(index);
417 let value = slot.value.clone();
418 let interned_at = slot.interned_at;
419 db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
420 DatabaseKeyIndex {
421 group_index: interned_storage.group_index,
422 query_index: Q::QUERY_INDEX,
423 key_index: slot.key_index,
424 },
425 INTERN_DURABILITY,
426 interned_at,
427 );
428 value
429 }
430
431 fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
432 INTERN_DURABILITY
433 }
434
435 fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
436 where
437 C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
438 {
439 let group_storage =
440 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
441 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
442 let tables = interned_storage.tables.read();
443 tables
444 .map
445 .values()
446 .map(|index| {
447 TableEntry::new(
448 <Q::Key>::from_intern_id(*index),
449 Some(tables.values[index.as_usize()].value.clone()),
450 )
451 })
452 .collect()
453 }
454}
455
456impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
457where
458 Q: Query,
459 Q::Key: InternKey,
460 Q::Value: InternValue,
461 IQ: Query<Key = Q::Value, Value = Q::Key>,
462{
463 fn purge(&self) {}
464}
465
466impl<K> Slot<K> {
467 fn maybe_changed_after(&self, revision: Revision) -> bool {
468 self.interned_at > revision
469 }
470}
471
472#[allow(dead_code)]
476fn check_send_sync<K>()
477where
478 K: Send + Sync,
479{
480 fn is_send_sync<T: Send + Sync>() {}
481 is_send_sync::<Slot<K>>();
482}
483
484#[allow(dead_code)]
488fn check_static<K>()
489where
490 K: 'static,
491{
492 fn is_static<T: 'static>() {}
493 is_static::<Slot<K>>();
494}
495
496impl<Q> QueryTable<'_, Q>
497where
498 Q: Query<Storage = InternedStorage<Q>>,
499 Q::Key: InternValue,
500 Q::Value: InternKey,
501{
502 pub fn get_or_insert(
504 &self,
505 key: MappedKey<Q>,
506 insert: impl FnOnce(Q::Value) -> Q::Key,
507 ) -> Q::Value {
508 self.storage.fetch_or_insert(self.db, key, insert)
509 }
510}