Skip to content

Commit a86db59

Browse files
Enable Garbage Collection for Interned Values (#602)
* remove table-wide dependencies * add plumbing to reuse interned slots * record durabilities on interned values * appease clippy * remove immortal interned value logic * pass correct revision when tracking interned reads * force new revision when resetting interned values * avoid unnecessary calls to `Database::zalsa` * add log events for value internment * Only log event kind because thread id can differ between runs/computers * cargo fmt --------- Co-authored-by: Micha Reiser <[email protected]>
1 parent 1d1523b commit a86db59

27 files changed

+435
-305
lines changed

book/src/plumbing/jars_and_ingredients.md

-3
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,6 @@ It combines an [`IngredientIndex`] with a `key_index`, which is a `salsa::Id`:
146146
{{#include ../../../src/key.rs:DatabaseKeyIndex}}
147147
```
148148

149-
A `DependencyIndex` is similar, but the `key_index` is optional.
150-
This is used when we sometimes wish to refer to the ingredient as a whole, and not any specific value within the ingredient.
151-
152149
These kinds of indices are used to store connetions between ingredients.
153150
For example, each memoized value has to track its inputs.
154151
Those inputs are stored as dependency indices.

src/accumulator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
138138
) {
139139
}
140140

141-
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
141+
fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
142142
fmt_index(A::DEBUG_NAME, index, fmt)
143143
}
144144

src/active_query.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ use std::ops::Not;
22

33
use super::zalsa_local::{QueryEdges, QueryOrigin, QueryRevisions};
44
use crate::accumulator::accumulated_map::AtomicInputAccumulatedValues;
5-
use crate::key::OutputDependencyIndex;
65
use crate::tracked_struct::{DisambiguatorMap, IdentityHash, IdentityMap};
76
use crate::zalsa_local::QueryEdge;
87
use crate::{
98
accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues},
109
cycle::CycleHeads,
1110
durability::Durability,
1211
hash::FxIndexSet,
13-
key::{DatabaseKeyIndex, InputDependencyIndex},
12+
key::DatabaseKeyIndex,
1413
tracked_struct::Disambiguator,
1514
Revision,
1615
};
@@ -81,7 +80,7 @@ impl ActiveQuery {
8180

8281
pub(super) fn add_read(
8382
&mut self,
84-
input: InputDependencyIndex,
83+
input: DatabaseKeyIndex,
8584
durability: Durability,
8685
revision: Revision,
8786
accumulated: InputAccumulatedValues,
@@ -96,7 +95,7 @@ impl ActiveQuery {
9695

9796
pub(super) fn add_read_simple(
9897
&mut self,
99-
input: InputDependencyIndex,
98+
input: DatabaseKeyIndex,
10099
durability: Durability,
101100
revision: Revision,
102101
) {
@@ -118,12 +117,12 @@ impl ActiveQuery {
118117
}
119118

120119
/// Adds a key to our list of outputs.
121-
pub(super) fn add_output(&mut self, key: OutputDependencyIndex) {
120+
pub(super) fn add_output(&mut self, key: DatabaseKeyIndex) {
122121
self.input_outputs.insert(QueryEdge::Output(key));
123122
}
124123

125124
/// True if the given key was output by this query.
126-
pub(super) fn is_output(&self, key: OutputDependencyIndex) -> bool {
125+
pub(super) fn is_output(&self, key: DatabaseKeyIndex) -> bool {
127126
self.input_outputs.contains(&QueryEdge::Output(key))
128127
}
129128

src/database.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub trait Database: Send + AsDynDatabase + Any + ZalsaDatabase {
5656
/// which are the fine-grained components we use to track data. This is intended
5757
/// for debugging and the contents of the returned string are not semver-guaranteed.
5858
///
59-
/// Ingredient indices can be extracted from [`DependencyIndex`](`crate::DependencyIndex`) values.
59+
/// Ingredient indices can be extracted from [`DatabaseKeyIndex`](`crate::DatabaseKeyIndex`) values.
6060
fn ingredient_debug_name(&self, ingredient_index: IngredientIndex) -> Cow<'_, str> {
6161
Cow::Borrowed(
6262
self.zalsa()

src/durability.rs

+19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ enum DurabilityVal {
3535
High = 2,
3636
}
3737

38+
impl From<u8> for DurabilityVal {
39+
fn from(value: u8) -> Self {
40+
match value {
41+
0 => DurabilityVal::Low,
42+
1 => DurabilityVal::Medium,
43+
2 => DurabilityVal::High,
44+
_ => panic!("invalid durability"),
45+
}
46+
}
47+
}
48+
3849
impl Durability {
3950
/// Low durability: things that change frequently.
4051
///
@@ -68,6 +79,14 @@ impl Durability {
6879
pub(crate) fn index(self) -> usize {
6980
self.0 as usize
7081
}
82+
83+
pub(crate) fn as_u8(self) -> u8 {
84+
self.0 as u8
85+
}
86+
87+
pub(crate) fn from_u8(value: u8) -> Self {
88+
Self(DurabilityVal::from(value))
89+
}
7190
}
7291

7392
impl Default for Durability {

src/event.rs

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use std::thread::ThreadId;
22

3-
use crate::{
4-
key::DatabaseKeyIndex,
5-
key::{InputDependencyIndex, OutputDependencyIndex},
6-
};
3+
use crate::{key::DatabaseKeyIndex, Id, Revision};
74

85
/// The `Event` struct identifies various notable things that can
96
/// occur during salsa execution. Instances of this struct are given
@@ -76,7 +73,7 @@ pub enum EventKind {
7673
execute_key: DatabaseKeyIndex,
7774

7875
/// Key for the query that is no longer output
79-
output_key: OutputDependencyIndex,
76+
output_key: DatabaseKeyIndex,
8077
},
8178

8279
/// Tracked structs or memoized data were discarded (freed).
@@ -91,6 +88,24 @@ pub enum EventKind {
9188
executor_key: DatabaseKeyIndex,
9289

9390
/// Accumulator that was accumulated into
94-
accumulator: InputDependencyIndex,
91+
accumulator: DatabaseKeyIndex,
92+
},
93+
94+
/// Indicates that a value was newly interned.
95+
DidInternValue {
96+
// The ID of the interned value.
97+
id: Id,
98+
99+
// The revision the value was interned in.
100+
revision: Revision,
101+
},
102+
103+
/// Indicates that a previously interned value was read in a new revision.
104+
DidReinternValue {
105+
// The ID of the interned value.
106+
id: Id,
107+
108+
// The revision the value was interned in.
109+
revision: Revision,
95110
},
96111
}

src/function.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,8 @@ where
160160
}
161161
}
162162

163-
pub fn database_key_index(&self, k: Id) -> DatabaseKeyIndex {
164-
DatabaseKeyIndex {
165-
ingredient_index: self.index,
166-
key_index: k,
167-
}
163+
pub fn database_key_index(&self, key: Id) -> DatabaseKeyIndex {
164+
DatabaseKeyIndex::new(self.index, key)
168165
}
169166

170167
pub fn set_capacity(&mut self, capacity: usize) {
@@ -302,7 +299,7 @@ where
302299
std::mem::take(&mut self.deleted_entries);
303300
}
304301

305-
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
302+
fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
306303
fmt_index(C::DEBUG_NAME, index, fmt)
307304
}
308305

src/function/accumulated.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ where
5454
continue;
5555
}
5656

57-
let ingredient = zalsa.lookup_ingredient(k.ingredient_index);
57+
let ingredient = zalsa.lookup_ingredient(k.ingredient_index());
5858
// Extend `output` with any values accumulated by `k`.
59-
let (accumulated_map, input) = ingredient.accumulated(db, k.key_index);
59+
let (accumulated_map, input) = ingredient.accumulated(db, k.key_index());
6060
if let Some(accumulated_map) = accumulated_map {
6161
accumulated_map.extend_with_accumulated(accumulator.index(), &mut output);
6262
}
@@ -71,7 +71,7 @@ where
7171
// output vector, we want to push in execution order, so reverse order to
7272
// ensure the first child that was executed will be the first child popped
7373
// from the stack.
74-
let Some(origin) = ingredient.origin(db, k.key_index) else {
74+
let Some(origin) = ingredient.origin(db, k.key_index()) else {
7575
continue;
7676
};
7777

src/function/diff_outputs.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{memo::Memo, Configuration, IngredientImpl};
22
use crate::{
3-
hash::FxHashSet, key::OutputDependencyIndex, zalsa::Zalsa, zalsa_local::QueryRevisions,
4-
AsDynDatabase as _, Database, DatabaseKeyIndex, Event, EventKind,
3+
hash::FxHashSet, zalsa::Zalsa, zalsa_local::QueryRevisions, AsDynDatabase as _, Database,
4+
DatabaseKeyIndex, Event, EventKind,
55
};
66

77
impl<C> IngredientImpl<C>
@@ -38,7 +38,7 @@ where
3838
// Remove the outputs that are no longer present in the current revision
3939
// to prevent that the next revision is seeded with a id mapping that no longer exists.
4040
revisions.tracked_struct_ids.retain(|&k, &mut value| {
41-
!old_outputs.contains(&OutputDependencyIndex::new(k.ingredient_index(), value))
41+
!old_outputs.contains(&DatabaseKeyIndex::new(k.ingredient_index(), value))
4242
});
4343
}
4444

@@ -51,7 +51,7 @@ where
5151
zalsa: &Zalsa,
5252
db: &C::DbView,
5353
key: DatabaseKeyIndex,
54-
output: OutputDependencyIndex,
54+
output: DatabaseKeyIndex,
5555
provisional: bool,
5656
) {
5757
db.salsa_event(&|| {

src/function/execute.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ where
2929
let (zalsa, zalsa_local) = db.zalsas();
3030
let revision_now = zalsa.current_revision();
3131
let database_key_index = active_query.database_key_index;
32-
let id = database_key_index.key_index;
32+
let id = database_key_index.key_index();
3333

3434
tracing::info!("{:?}: executing query", database_key_index);
3535

src/function/fetch.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ where
3030
self.lru.record_use(id);
3131

3232
zalsa_local.report_tracked_read(
33-
self.database_key_index(id).into(),
33+
self.database_key_index(id),
3434
durability,
3535
changed_at,
3636
match &memo.revisions.accumulated {
@@ -130,7 +130,7 @@ where
130130
}
131131
// no provisional value; create/insert/return initial provisional value
132132
return self
133-
.initial_value(db, database_key_index.key_index)
133+
.initial_value(db, database_key_index.key_index())
134134
.map(|initial_value| {
135135
tracing::debug!(
136136
"hit cycle at {database_key_index:#?}, \

src/function/maybe_changed_after.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ where
250250
);
251251
if (&memo.revisions.cycle_heads).into_iter().any(|cycle_head| {
252252
zalsa
253-
.lookup_ingredient(cycle_head.ingredient_index)
254-
.is_provisional_cycle_head(db.as_dyn_database(), cycle_head.key_index)
253+
.lookup_ingredient(cycle_head.ingredient_index())
254+
.is_provisional_cycle_head(db.as_dyn_database(), cycle_head.key_index())
255255
}) {
256256
return false;
257257
}

src/function/memo.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ impl<V> Memo<V> {
183183
.into_iter()
184184
.filter(|&head| head != database_key_index)
185185
.any(|head| {
186-
let ingredient = zalsa.lookup_ingredient(head.ingredient_index);
187-
if !ingredient.is_provisional_cycle_head(db, head.key_index) {
186+
let ingredient = zalsa.lookup_ingredient(head.ingredient_index());
187+
if !ingredient.is_provisional_cycle_head(db, head.key_index()) {
188188
// This cycle is already finalized, so we don't need to wait on it;
189189
// keep looping through cycle heads.
190190
retry = true;
191191
false
192-
} else if ingredient.wait_for(db, head.key_index) {
192+
} else if ingredient.wait_for(db, head.key_index()) {
193193
// There's a new memo available for the cycle head; fetch our own
194194
// updated memo and see if it's still provisional or if the cycle
195195
// has resolved.

src/function/specify.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ where
4141
//
4242
// Now, if We invoke Q3 first, We get one result for Q2, but if We invoke Q4 first, We get a different value. That's no good.
4343
let database_key_index = <C::Input<'db>>::database_key_index(db.as_dyn_database(), key);
44-
let dependency_index = database_key_index.into();
45-
if !zalsa_local.is_output_of_active_query(dependency_index) {
44+
if !zalsa_local.is_output_of_active_query(database_key_index) {
4645
panic!("can only use `specify` on salsa structs created during the current tracked fn");
4746
}
4847

@@ -105,7 +104,7 @@ where
105104

106105
// Record that the current query *specified* a value for this cell.
107106
let database_key_index = self.database_key_index(key);
108-
zalsa_local.add_output(database_key_index.into());
107+
zalsa_local.add_output(database_key_index);
109108
}
110109

111110
/// Invoked when the query `executor` has been validated as having green inputs

src/ingredient.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
141141
);
142142
}
143143

144-
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
144+
fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
145145
}
146146

147147
impl dyn Ingredient {
@@ -177,14 +177,6 @@ impl dyn Ingredient {
177177
}
178178

179179
/// A helper function to show human readable fmt.
180-
pub(crate) fn fmt_index(
181-
debug_name: &str,
182-
id: Option<Id>,
183-
fmt: &mut fmt::Formatter<'_>,
184-
) -> fmt::Result {
185-
if let Some(i) = id {
186-
write!(fmt, "{debug_name}({i:?})")
187-
} else {
188-
write!(fmt, "{debug_name}()")
189-
}
180+
pub(crate) fn fmt_index(debug_name: &str, id: Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
181+
write!(fmt, "{debug_name}({id:?})")
190182
}

src/input.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
id::{AsId, FromIdWithDb},
1616
ingredient::{fmt_index, Ingredient},
1717
input::singleton::{Singleton, SingletonChoice},
18-
key::{DatabaseKeyIndex, InputDependencyIndex},
18+
key::DatabaseKeyIndex,
1919
plumbing::{Jar, Stamp},
2020
table::{memo::MemoTable, sync::SyncTable, Slot, Table},
2121
zalsa::{IngredientIndex, Zalsa},
@@ -96,10 +96,7 @@ impl<C: Configuration> IngredientImpl<C> {
9696
}
9797

9898
pub fn database_key_index(&self, id: C::Struct) -> DatabaseKeyIndex {
99-
DatabaseKeyIndex {
100-
ingredient_index: self.ingredient_index,
101-
key_index: id.as_id(),
102-
}
99+
DatabaseKeyIndex::new(self.ingredient_index, id.as_id())
103100
}
104101

105102
pub fn new_input(&self, db: &dyn Database, fields: C::Fields, stamps: C::Stamps) -> C::Struct {
@@ -177,7 +174,7 @@ impl<C: Configuration> IngredientImpl<C> {
177174
let value = Self::data(zalsa, id);
178175
let stamp = &value.stamps[field_index];
179176
zalsa_local.report_tracked_read_simple(
180-
InputDependencyIndex::new(field_ingredient_index, id),
177+
DatabaseKeyIndex::new(field_ingredient_index, id),
181178
stamp.durability,
182179
stamp.changed_at,
183180
);
@@ -261,7 +258,7 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
261258
);
262259
}
263260

264-
fn fmt_index(&self, index: Option<Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
261+
fn fmt_index(&self, index: Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
265262
fmt_index(C::DEBUG_NAME, index, fmt)
266263
}
267264

src/input/input_field.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ where
8585
) {
8686
}
8787

88-
fn fmt_index(&self, index: Option<crate::Id>, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
88+
fn fmt_index(&self, index: crate::Id, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
8989
fmt_index(C::FIELD_DEBUG_NAMES[self.field_index], index, fmt)
9090
}
9191

0 commit comments

Comments
 (0)