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;
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 maybe_changed_since(
308 &self,
309 db: &<Q as QueryDb<'_>>::DynDb,
310 input: DatabaseKeyIndex,
311 revision: Revision,
312 ) -> bool {
313 assert_eq!(input.group_index, self.group_index);
314 assert_eq!(input.query_index, Q::QUERY_INDEX);
315 let intern_id = InternId::from(input.key_index);
316 let slot = self.lookup_value(db, intern_id);
317 slot.maybe_changed_since(db, revision)
318 }
319
320 fn try_fetch(
321 &self,
322 db: &<Q as QueryDb<'_>>::DynDb,
323 key: &Q::Key,
324 ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
325 let slot = self.intern_index(db, key);
326 let changed_at = slot.interned_at;
327 let index = slot.index;
328 db.salsa_runtime().report_query_read(
329 slot.database_key_index,
330 INTERN_DURABILITY,
331 changed_at,
332 );
333 Ok(<Q::Value>::from_intern_id(index))
334 }
335
336 fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
337 INTERN_DURABILITY
338 }
339
340 fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
341 where
342 C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
343 {
344 let tables = self.tables.read();
345 tables
346 .map
347 .iter()
348 .map(|(key, index)| {
349 TableEntry::new(key.clone(), Some(<Q::Value>::from_intern_id(*index)))
350 })
351 .collect()
352 }
353}
354
355impl<Q> QueryStorageMassOps for InternedStorage<Q>
356where
357 Q: Query,
358 Q::Value: InternKey,
359{
360 fn sweep(&self, runtime: &Runtime, strategy: SweepStrategy) {
361 let mut tables = self.tables.write();
362 let last_changed = runtime.last_changed_revision(INTERN_DURABILITY);
363 let revision_now = runtime.current_revision();
364 let InternTables {
365 map,
366 values,
367 first_free,
368 } = &mut *tables;
369 map.retain(|key, intern_index| {
370 match strategy.discard_if {
371 DiscardIf::Never => true,
372
373 DiscardIf::Always | DiscardIf::Outdated => match &values[intern_index.as_usize()] {
386 InternValue::Present { slot, .. } => {
387 if slot.try_collect(last_changed, revision_now) {
388 values[intern_index.as_usize()] =
389 InternValue::Free { next: *first_free };
390 *first_free = Some(*intern_index);
391 false
392 } else {
393 true
394 }
395 }
396
397 InternValue::Free { .. } => {
398 panic!(
399 "key {:?} maps to index {:?} which is free",
400 key, intern_index
401 );
402 }
403 },
404 }
405 });
406 }
407 fn purge(&self) {
408 *self.tables.write() = Default::default();
409 }
410}
411
412#[doc(hidden)]
423pub trait EqualDynDb<'d, IQ>: QueryDb<'d>
424where
425 IQ: QueryDb<'d>,
426{
427 fn convert_db(d: &Self::DynDb) -> &IQ::DynDb;
428 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage;
429}
430
431impl<'d, IQ, Q> EqualDynDb<'d, IQ> for Q
432where
433 Q: QueryDb<'d, DynDb = IQ::DynDb, Group = IQ::Group, GroupStorage = IQ::GroupStorage>,
434 Q::DynDb: HasQueryGroup<Q::Group>,
435 IQ: QueryDb<'d>,
436{
437 fn convert_db(d: &Self::DynDb) -> &IQ::DynDb {
438 d
439 }
440 fn convert_group_storage(d: &Self::GroupStorage) -> &IQ::GroupStorage {
441 d
442 }
443}
444
445impl<Q, IQ> QueryStorageOps<Q> for LookupInternedStorage<Q, IQ>
446where
447 Q: Query,
448 Q::Key: InternKey,
449 Q::Value: Eq + Hash,
450 IQ: Query<Key = Q::Value, Value = Q::Key, Storage = InternedStorage<IQ>>,
451 for<'d> Q: EqualDynDb<'d, IQ>,
452{
453 fn new(_group_index: u16) -> Self {
454 LookupInternedStorage {
455 phantom: std::marker::PhantomData,
456 }
457 }
458
459 fn fmt_index(
460 &self,
461 db: &<Q as QueryDb<'_>>::DynDb,
462 index: DatabaseKeyIndex,
463 fmt: &mut std::fmt::Formatter<'_>,
464 ) -> std::fmt::Result {
465 let group_storage =
466 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
467 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
468 interned_storage.fmt_index(Q::convert_db(db), index, fmt)
469 }
470
471 fn maybe_changed_since(
472 &self,
473 db: &<Q as QueryDb<'_>>::DynDb,
474 input: DatabaseKeyIndex,
475 revision: Revision,
476 ) -> bool {
477 let group_storage =
478 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
479 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
480 interned_storage.maybe_changed_since(Q::convert_db(db), input, revision)
481 }
482
483 fn try_fetch(
484 &self,
485 db: &<Q as QueryDb<'_>>::DynDb,
486 key: &Q::Key,
487 ) -> Result<Q::Value, CycleError<DatabaseKeyIndex>> {
488 let index = key.as_intern_id();
489 let group_storage =
490 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
491 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
492 let slot = interned_storage.lookup_value(Q::convert_db(db), index);
493 let value = slot.value.clone();
494 let interned_at = slot.interned_at;
495 db.salsa_runtime().report_query_read(
496 slot.database_key_index,
497 INTERN_DURABILITY,
498 interned_at,
499 );
500 Ok(value)
501 }
502
503 fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, _key: &Q::Key) -> Durability {
504 INTERN_DURABILITY
505 }
506
507 fn entries<C>(&self, db: &<Q as QueryDb<'_>>::DynDb) -> C
508 where
509 C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
510 {
511 let group_storage =
512 <<Q as QueryDb<'_>>::DynDb as HasQueryGroup<Q::Group>>::group_storage(db);
513 let interned_storage = IQ::query_storage(Q::convert_group_storage(group_storage));
514 let tables = interned_storage.tables.read();
515 tables
516 .map
517 .iter()
518 .map(|(key, index)| {
519 TableEntry::new(<Q::Key>::from_intern_id(*index), Some(key.clone()))
520 })
521 .collect()
522 }
523}
524
525impl<Q, IQ> QueryStorageMassOps for LookupInternedStorage<Q, IQ>
526where
527 Q: Query,
528 Q::Key: InternKey,
529 Q::Value: Eq + Hash,
530 IQ: Query<Key = Q::Value, Value = Q::Key>,
531{
532 fn sweep(&self, _: &Runtime, _strategy: SweepStrategy) {}
533 fn purge(&self) {}
534}
535
536impl<K> Slot<K> {
537 fn maybe_changed_since<DB: ?Sized + Database>(&self, db: &DB, revision: Revision) -> bool {
538 let revision_now = db.salsa_runtime().current_revision();
539 if !self.try_update_accessed_at(revision_now) {
540 true
542 } else {
543 self.interned_at > revision
545 }
546 }
547
548 fn try_update_accessed_at(&self, revision_now: Revision) -> bool {
552 if let Some(accessed_at) = self.accessed_at.load() {
553 match self
554 .accessed_at
555 .compare_exchange(Some(accessed_at), Some(revision_now))
556 {
557 Ok(_) => true,
558 Err(Some(r)) => {
559 debug_assert_eq!(r, revision_now);
562 true
563 }
564 Err(None) => {
565 false
568 }
569 }
570 } else {
571 false
572 }
573 }
574
575 fn try_collect(&self, last_changed: Revision, revision_now: Revision) -> bool {
582 let accessed_at = self.accessed_at.load().unwrap();
583 if accessed_at < last_changed {
584 match self.accessed_at.compare_exchange(Some(accessed_at), None) {
585 Ok(_) => true,
586 Err(r) => {
587 debug_assert_eq!(r, Some(revision_now));
591 false
592 }
593 }
594 } else {
595 false
596 }
597 }
598}
599
600#[allow(dead_code)]
604fn check_send_sync<K>()
605where
606 K: Send + Sync,
607{
608 fn is_send_sync<T: Send + Sync>() {}
609 is_send_sync::<Slot<K>>();
610}
611
612#[allow(dead_code)]
616fn check_static<K>()
617where
618 K: 'static,
619{
620 fn is_static<T: 'static>() {}
621 is_static::<Slot<K>>();
622}