1use crate::debug::TableEntry;
2use crate::durability::Durability;
3use crate::intern_id::InternId;
4use crate::plumbing::HasQueryGroup;
5use crate::plumbing::QueryStorageMassOps;
6use crate::plumbing::{QueryStorageOps, QueryStorageOpsSync};
7use crate::revision::Revision;
8use crate::Query;
9use crate::{CycleError, Database, DatabaseKeyIndex, DiscardIf, QueryDb, Runtime, SweepStrategy};
10use crossbeam_utils::atomic::AtomicCell;
11use parking_lot::RwLock;
12use rustc_hash::FxHashMap;
13use std::collections::hash_map::Entry;
14use std::convert::From;
15use std::fmt::Debug;
16use std::hash::Hash;
17use std::sync::Arc;
18
19const INTERN_DURABILITY: Durability = Durability::HIGH;
20
21pub struct InternedStorage<Q>
24where
25 Q: Query,
26 Q::Value: InternKey,
27{
28 group_index: u16,
29 tables: RwLock<InternTables<Q::Key>>,
30}
31
32pub struct LookupInternedStorage<Q, IQ>
34where
35 Q: Query,
36 Q::Key: InternKey,
37 Q::Value: Eq + Hash,
38{
39 phantom: std::marker::PhantomData<(Q::Key, IQ)>,
40}
41
42struct InternTables<K> {
43 map: FxHashMap<K, InternId>,
45
46 values: Vec<InternValue<K>>,
50
51 first_free: Option<InternId>,
53}
54
55pub trait InternKey {
59 fn from_intern_id(v: InternId) -> Self;
61
62 fn as_intern_id(&self) -> InternId;
64}
65
66impl InternKey for InternId {
67 fn from_intern_id(v: InternId) -> InternId {
68 v
69 }
70
71 fn as_intern_id(&self) -> InternId {
72 *self
73 }
74}
75
76enum InternValue<K> {
77 Present { slot: Arc<Slot<K>> },
79
80 Free { next: Option<InternId> },
82}
83
84#[derive(Debug)]
85struct Slot<K> {
86 index: InternId,
89
90 database_key_index: DatabaseKeyIndex,
92
93 value: K,
95
96 interned_at: Revision,
100
101 accessed_at: AtomicCell<Option<Revision>>,
112}
113
114impl<Q> std::panic::RefUnwindSafe for InternedStorage<Q>
115where
116 Q: Query,
117 Q::Key: std::panic::RefUnwindSafe,
118 Q::Value: InternKey,
119 Q::Value: std::panic::RefUnwindSafe,
120{
121}
122
123impl<K: Debug + Hash + Eq> InternTables<K> {
124 fn slot_for_key(&self, key: &K, revision_now: Revision) -> Option<Arc<Slot<K>>> {
129 let index = self.map.get(key)?;
130 Some(self.slot_for_index(*index, revision_now))
131 }
132
133 fn slot_for_index(&self, index: InternId, revision_now: Revision) -> Arc<Slot<K>> {
138 match &self.values[index.as_usize()] {
139 InternValue::Present { slot } => {
140 let updated = slot.try_update_accessed_at(revision_now);
144 assert!(
145 updated,
146 "failed to update slot {:?} while holding read lock",
147 slot
148 );
149 slot.clone()
150 }
151 InternValue::Free { .. } => {
152 panic!("index {:?} is free but should not be", index);
153 }
154 }
155 }
156}
157
158impl<K> Default for InternTables<K>
159where
160 K: Eq + Hash,
161{
162 fn default() -> Self {
163 Self {
164 map: Default::default(),
165 values: Default::default(),
166 first_free: Default::default(),
167 }
168 }
169}
170
171impl<Q> InternedStorage<Q>
172where
173 Q: Query,
174 Q::Key: Eq + Hash + Clone,
175 Q::Value: InternKey,
176{
177 fn intern_index(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Arc<Slot<Q::Key>> {
183 if let Some(i) = self.intern_check(db, key) {
184 return i;
185 }
186
187 let owned_key1 = key.to_owned();
188 let owned_key2 = owned_key1.clone();
189 let revision_now = db.salsa_runtime().current_revision();
190
191 let mut tables = self.tables.write();
192 let tables = &mut *tables;
193 let entry = match tables.map.entry(owned_key1) {
194 Entry::Vacant(entry) => entry,
195 Entry::Occupied(entry) => {
196 let index = *entry.get();
201 match &tables.values[index.as_usize()] {
202 InternValue::Present { slot } => {
203 debug_assert_eq!(owned_key2, slot.value);
204 debug_assert_eq!(slot.accessed_at.load(), Some(revision_now));
205 return slot.clone();
206 }
207
208 InternValue::Free { .. } => {
209 panic!("key {:?} should be present but is not", key,);
210 }
211 }
212 }
213 };
214
215 let create_slot = |index: InternId| {
216 let database_key_index = DatabaseKeyIndex {
217 group_index: self.group_index,
218 query_index: Q::QUERY_INDEX,
219 key_index: index.as_u32(),
220 };
221 Arc::new(Slot {
222 index,
223 database_key_index,
224 value: owned_key2,
225 interned_at: revision_now,
226 accessed_at: AtomicCell::new(Some(revision_now)),
227 })
228 };
229
230 let (slot, index);
231 match tables.first_free {
232 None => {
233 index = InternId::from(tables.values.len());
234 slot = create_slot(index);
235 tables
236 .values
237 .push(InternValue::Present { slot: slot.clone() });
238 }
239
240 Some(i) => {
241 index = i;
242 slot = create_slot(index);
243
244 let next_free = match &tables.values[i.as_usize()] {
245 InternValue::Free { next } => *next,
246 InternValue::Present { slot } => {
247 panic!(
248 "index {:?} was supposed to be free but contains {:?}",
249 i, slot.value
250 );
251 }
252 };
253
254 tables.values[index.as_usize()] = InternValue::Present { slot: slot.clone() };
255 tables.first_free = next_free;
256 }
257 }
258
259 entry.insert(index);
260
261 slot
262 }
263
264 fn intern_check(
265 &self,
266 db: &<Q as QueryDb<'_>>::DynDb,
267 key: &Q::Key,
268 ) -> Option<Arc<Slot<Q::Key>>> {
269 let revision_now = db.salsa_runtime().current_revision();
270 let slot = self.tables.read().slot_for_key(key, revision_now)?;
271 Some(slot)
272 }
273
274 fn lookup_value(&self, db: &<Q as QueryDb<'_>>::DynDb, index: InternId) -> Arc<Slot<Q::Key>> {
277 let revision_now = db.salsa_runtime().current_revision();
278 self.tables.read().slot_for_index(index, revision_now)
279 }
280}
281
282impl<Q> QueryStorageOps<Q> for InternedStorage<Q>
283where
284 Q: Query,
285 Q::Value: InternKey,
286{
287 fn new(group_index: u16) -> Self {
288 InternedStorage {
289 group_index,
290 tables: RwLock::new(InternTables::default()),
291 }
292 }
293
294 fn fmt_index(
295 &self,
296 db: &<Q as QueryDb<'_>>::DynDb,
297 index: DatabaseKeyIndex,
298 fmt: &mut std::fmt::Formatter<'_>,
299 ) -> std::fmt::Result {
300 assert_eq!(index.group_index, self.group_index);
301 assert_eq!(index.query_index, Q::QUERY_INDEX);
302 let intern_id = InternId::from(index.key_index);
303 let slot = self.lookup_value(db, intern_id);
304 write!(fmt, "{}({:?})", Q::QUERY_NAME, slot.value)
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 .iter()
319 .map(|(key, index)| {
320 TableEntry::new(key.clone(), Some(<Q::Value>::from_intern_id(*index)))
321 })
322 .collect()
323 }
324
325 fn peek(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Option<Q::Value> {
326 self.intern_check(db, key).map(|slot| {
327 let index = slot.index;
328 <Q::Value>::from_intern_id(index)
329 })
330 }
331}
332
333impl<Q> QueryStorageOpsSync<Q> for InternedStorage<Q>
334where
335 Q: Query,
336 Q::Value: InternKey,
337{
338 fn maybe_changed_since(
339 &self,
340 db: &mut <Q as QueryDb<'_>>::Db,
341 input: DatabaseKeyIndex,
342 revision: Revision,
343 ) -> bool {
344 assert_eq!(input.group_index, self.group_index);
345 assert_eq!(input.query_index, Q::QUERY_INDEX);
346 let intern_id = InternId::from(input.key_index);
347 let slot = self.lookup_value(db, intern_id);
348 slot.maybe_changed_since(db, revision)
349 }
350
351 fn try_fetch(
352 &self,
353 db: &mut <Q as QueryDb<'_>>::Db,
354 key: &Q::Key,
355 ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
356 let slot = self.intern_index(db, key);
357 let changed_at = slot.interned_at;
358 let index = slot.index;
359 db.salsa_runtime().report_query_read(
360 slot.database_key_index,
361 INTERN_DURABILITY,
362 changed_at,
363 );
364 Ok(<Q::Value>::from_intern_id(index))
365 }
366}
367
368impl<Q> QueryStorageMassOps for InternedStorage<Q>
369where
370 Q: Query,
371 Q::Value: InternKey,
372{
373 fn sweep(&self, runtime: &Runtime, strategy: SweepStrategy) {
374 let mut tables = self.tables.write();
375 let last_changed = runtime.last_changed_revision(INTERN_DURABILITY);
376 let revision_now = runtime.current_revision();
377 let InternTables {
378 map,
379 values,
380 first_free,
381 } = &mut *tables;
382 map.retain(|key, intern_index| {
383 match strategy.discard_if {
384 DiscardIf::Never => true,
385
386 DiscardIf::Always | DiscardIf::Outdated => match &values[intern_index.as_usize()] {
399 InternValue::Present { slot, .. } => {
400 if slot.try_collect(last_changed, revision_now) {
401 values[intern_index.as_usize()] =
402 InternValue::Free { next: *first_free };
403 *first_free = Some(*intern_index);
404 false
405 } else {
406 true
407 }
408 }
409
410 InternValue::Free { .. } => {
411 panic!(
412 "key {:?} maps to index {:?} which is free",
413 key, intern_index
414 );
415 }
416 },
417 }
418 });
419 }
420 fn purge(&self) {
421 *self.tables.write() = Default::default();
422 }
423}
424
425#[doc(hidden)]
436pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
437where
438 IQ: QueryDb<'d>,
439{
440 fn convert_db(d: &mut Self::Db) -> &mut IQ::Db;
441 fn convert_dyn_db(d: &Self::DynDb) -> &IQ::DynDb;
442 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
443}
444
445impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
446where
447 Q: QueryDb<
448 'd,
449 Db = IQ::Db,
450 DynDb = IQ::DynDb,
451 Group = IQ::Group,
452 GroupStorage = IQ::GroupStorage,
453 >,
454 Q::DynDb: HasQueryGroup<Q::Group>,
455 IQ: QueryDb<'d>,
456{
457 fn convert_db(d: &mut Self::Db) -> &mut IQ::Db {
458 d
459 }
460 fn convert_dyn_db(d: &Self::DynDb) -> &IQ::DynDb {
461 d
462 }
463 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
464 d
465 }
466}
467
468impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
469where
470 Q: Query,
471 Q::Key: InternKey,
472 Q::Value: Eq + Hash,
473 IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
474 for<'d> Q: EqualDynDb<'d, IQ>,
475{
476 fn new(_group_index: u16) -> Self {
477 LookupInternedStorage {
478 phantom: std::marker::PhantomData,
479 }
480 }
481
482 fn fmt_index(
483 &self,
484 db: &<Q as QueryDb<'_>>::DynDb,
485 index: DatabaseKeyIndex,
486 fmt: &mut std::fmt::Formatter<'_>,
487 ) -> std::fmt::Result {
488 let group_storage =
489 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
490 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage)).clone();
491 interned_storage.fmt_index(Q::convert_dyn_db(db), index, fmt)
492 }
493
494 fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
495 INTERN_DURABILITY
496 }
497
498 fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
499 where
500 C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
501 {
502 let group_storage =
503 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
504 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
505 let tables = interned_storage.tables.read();
506 tables
507 .map
508 .iter()
509 .map(|(key, index)| {
510 TableEntry::new(<Q::Key>::from_intern_id(*index), Some(key.clone()))
511 })
512 .collect()
513 }
514
515 fn peek(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Option<Q::Value> {
516 let index = key.as_intern_id();
517 let interned_storage = query_storage::<Q, IQ>(db);
518 let slot = interned_storage.lookup_value(Q::convert_dyn_db(db), index);
519 let value = slot.value.clone();
520 Some(value)
521 }
522}
523
524fn query_storage<Q, IQ>(db: &<Q as QueryDb<'_>>::DynDb) -> Arc<InternedStorage<IQ>>
525where
526 Q: Query,
527 Q::Key: InternKey,
528 Q::Value: Eq + Hash,
529 IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
530 for<'d> Q: EqualDynDb<'d, IQ>,
531{
532 let group_storage =
533 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db).clone();
534 IQ::query_storage(Q::convert_group_storage(group_storage)).clone()
535}
536
537impl<Q, IQ> QueryStorageOpsSync<Q> for LookupInternedStorage<Q, IQ>
538where
539 Q: Query,
540 Q::Key: InternKey,
541 Q::Value: Eq + Hash,
542 IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
543 for<'d> Q: EqualDynDb<'d, IQ>,
544{
545 fn maybe_changed_since(
546 &self,
547 db: &mut <Q as QueryDb<'_>>::Db,
548 input: DatabaseKeyIndex,
549 revision: Revision,
550 ) -> bool {
551 let interned_storage = query_storage::<Q, IQ>(db);
552 interned_storage.maybe_changed_since(Q::convert_db(db), input, revision)
553 }
554
555 fn try_fetch(
556 &self,
557 db: &mut <Q as QueryDb<'_>>::Db,
558 key: &Q::Key,
559 ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
560 let index = key.as_intern_id();
561 let interned_storage = query_storage::<Q, IQ>(db);
562 let slot = interned_storage.lookup_value(Q::convert_db(db), index);
563 let value = slot.value.clone();
564 let interned_at = slot.interned_at;
565 db.salsa_runtime().report_query_read(
566 slot.database_key_index,
567 INTERN_DURABILITY,
568 interned_at,
569 );
570 Ok(value)
571 }
572}
573
574impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
575where
576 Q: Query,
577 Q::Key: InternKey,
578 Q::Value: Eq + Hash,
579 IQ: Query<Key = Q::Value, Value = Q::Key>,
580{
581 fn sweep(&self, _: &Runtime, _strategy: SweepStrategy) {}
582 fn purge(&self) {}
583}
584
585impl<K> Slot<K> {
586 fn maybe_changed_since<DB>(&self, db: &mut DB, revision: Revision) -> bool
587 where
588 DB: std::ops::Deref,
589 DB::Target: Database,
590 {
591 let revision_now = db.salsa_runtime().current_revision();
592 if !self.try_update_accessed_at(revision_now) {
593 true
595 } else {
596 self.interned_at > revision
598 }
599 }
600
601 fn try_update_accessed_at(&self, revision_now: Revision) -> bool {
605 if let Some(accessed_at) = self.accessed_at.load() {
606 match self
607 .accessed_at
608 .compare_exchange(Some(accessed_at), Some(revision_now))
609 {
610 Ok(_) => true,
611 Err(Some(r)) => {
612 debug_assert_eq!(r, revision_now);
615 true
616 }
617 Err(None) => {
618 false
621 }
622 }
623 } else {
624 false
625 }
626 }
627
628 fn try_collect(&self, last_changed: Revision, revision_now: Revision) -> bool {
635 let accessed_at = self.accessed_at.load().unwrap();
636 if accessed_at < last_changed {
637 match self.accessed_at.compare_exchange(Some(accessed_at), None) {
638 Ok(_) => true,
639 Err(r) => {
640 debug_assert_eq!(r, Some(revision_now));
644 false
645 }
646 }
647 } else {
648 false
649 }
650 }
651}
652
653#[allow(dead_code)]
657fn check_send_sync<K>()
658where
659 K: Send + Sync,
660{
661 fn is_send_sync<T: Send + Sync>() {}
662 is_send_sync::<Slot<K>>();
663}
664
665#[allow(dead_code)]
669fn check_static<K>()
670where
671 K: 'static,
672{
673 fn is_static<T: 'static>() {}
674 is_static::<Slot<K>>();
675}