Skip to content

Commit 34882a1

Browse files
authored
Flatten unserializable query dependencies (#975)
* flatten unserializable query dependencies * avoid allocating intermediary serialized dependency edges
1 parent 5aab823 commit 34882a1

File tree

15 files changed

+608
-120
lines changed

15 files changed

+608
-120
lines changed

components/salsa-macro-rules/src/setup_tracked_fn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ macro_rules! setup_tracked_fn {
172172
line: line!(),
173173
};
174174
const DEBUG_NAME: &'static str = concat!($(stringify!($self_ty), "::",)? stringify!($fn_name), "::interned_arguments");
175-
const PERSIST: bool = true;
175+
const PERSIST: bool = $persist;
176176

177177
type Fields<$db_lt> = ($($interned_input_ty),*);
178178

src/accumulator.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use std::panic::UnwindSafe;
88
use accumulated::{Accumulated, AnyAccumulated};
99

1010
use crate::function::{VerifyCycleHeads, VerifyResult};
11+
use crate::hash::FxIndexSet;
1112
use crate::ingredient::{Ingredient, Jar};
1213
use crate::plumbing::ZalsaLocal;
1314
use crate::sync::Arc;
1415
use crate::table::memo::MemoTableTypes;
1516
use crate::zalsa::{IngredientIndex, JarKind, Zalsa};
17+
use crate::zalsa_local::QueryEdge;
1618
use crate::{Database, Id, Revision};
1719

1820
mod accumulated;
@@ -110,6 +112,15 @@ impl<A: Accumulator> Ingredient for IngredientImpl<A> {
110112
panic!("nothing should ever depend on an accumulator directly")
111113
}
112114

115+
fn collect_minimum_serialized_edges(
116+
&self,
117+
_zalsa: &Zalsa,
118+
_edge: QueryEdge,
119+
_serialized_edges: &mut FxIndexSet<QueryEdge>,
120+
) {
121+
panic!("nothing should ever depend on an accumulator directly")
122+
}
123+
113124
fn debug_name(&self) -> &'static str {
114125
A::DEBUG_NAME
115126
}

src/function.rs

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::cycle::{
1313
use crate::database::RawDatabase;
1414
use crate::function::delete::DeletedEntries;
1515
use crate::function::sync::{ClaimResult, SyncTable};
16+
use crate::hash::FxIndexSet;
1617
use crate::ingredient::{Ingredient, WaitForResult};
1718
use crate::key::DatabaseKeyIndex;
1819
use crate::plumbing::{self, MemoIngredientMap};
@@ -22,7 +23,7 @@ use crate::table::memo::MemoTableTypes;
2223
use crate::table::Table;
2324
use crate::views::DatabaseDownCaster;
2425
use crate::zalsa::{IngredientIndex, JarKind, MemoIngredientIndex, Zalsa};
25-
use crate::zalsa_local::QueryOriginRef;
26+
use crate::zalsa_local::{QueryEdge, QueryOriginRef};
2627
use crate::{Id, Revision};
2728

2829
#[cfg(feature = "accumulator")]
@@ -277,7 +278,7 @@ where
277278

278279
unsafe fn maybe_changed_after(
279280
&self,
280-
_zalsa: &crate::zalsa::Zalsa,
281+
_zalsa: &Zalsa,
281282
db: RawDatabase<'_>,
282283
input: Id,
283284
revision: Revision,
@@ -288,6 +289,29 @@ where
288289
self.maybe_changed_after(db, input, revision, cycle_heads)
289290
}
290291

292+
fn collect_minimum_serialized_edges(
293+
&self,
294+
zalsa: &Zalsa,
295+
edge: QueryEdge,
296+
serialized_edges: &mut FxIndexSet<QueryEdge>,
297+
) {
298+
let input = edge.key().key_index();
299+
300+
let Some(memo) =
301+
self.get_memo_from_table_for(zalsa, input, self.memo_ingredient_index(zalsa, input))
302+
else {
303+
return;
304+
};
305+
306+
let origin = memo.revisions.origin.as_ref();
307+
308+
// Collect the minimum dependency tree.
309+
for edge in origin.edges() {
310+
let dependency = zalsa.lookup_ingredient(edge.key().ingredient_index());
311+
dependency.collect_minimum_serialized_edges(zalsa, *edge, serialized_edges)
312+
}
313+
}
314+
291315
/// Returns `final` only if the memo has the `verified_final` flag set and the cycle recovery strategy is not `FallbackImmediate`.
292316
///
293317
/// Otherwise, the value is still provisional. For both final and provisional, it also
@@ -470,8 +494,10 @@ where
470494
#[cfg(feature = "persistence")]
471495
mod persistence {
472496
use super::{Configuration, IngredientImpl, Memo};
473-
use crate::plumbing::{Ingredient, MemoIngredientMap, SalsaStructInDb};
497+
use crate::hash::FxIndexSet;
498+
use crate::plumbing::{MemoIngredientMap, SalsaStructInDb};
474499
use crate::zalsa::Zalsa;
500+
use crate::zalsa_local::{QueryEdge, QueryOrigin, QueryOriginRef};
475501
use crate::{Id, IngredientIndex};
476502

477503
use serde::de;
@@ -499,16 +525,6 @@ mod persistence {
499525

500526
let mut map = serializer.serialize_map(None)?;
501527

502-
for struct_index in
503-
<C::SalsaStruct<'_> as SalsaStructInDb>::lookup_ingredient_index(zalsa).iter()
504-
{
505-
let struct_ingredient = zalsa.lookup_ingredient(struct_index);
506-
assert!(
507-
struct_ingredient.is_persistable(),
508-
"the input of a serialized tracked function must be serialized"
509-
);
510-
}
511-
512528
for entry in <C::SalsaStruct<'_> as SalsaStructInDb>::entries(zalsa) {
513529
let memo_ingredient_index = ingredient
514530
.memo_ingredient_indices
@@ -521,18 +537,30 @@ mod persistence {
521537
);
522538

523539
if let Some(memo) = memo.filter(|memo| memo.should_serialize()) {
524-
for edge in memo.revisions.origin.as_ref().edges() {
525-
let dependency = zalsa.lookup_ingredient(edge.key().ingredient_index());
526-
527-
// TODO: This is not strictly necessary, we only need the transitive input
528-
// dependencies of this query to serialize a valid memo.
529-
assert!(
530-
dependency.is_persistable(),
531-
"attempted to serialize query `{}`, but dependency `{}` is not persistable",
532-
ingredient.debug_name(),
533-
dependency.debug_name()
534-
);
535-
}
540+
// Flatten the dependencies of this query down to the base inputs.
541+
let flattened_origin = match memo.revisions.origin.as_ref() {
542+
QueryOriginRef::Derived(edges) => {
543+
QueryOrigin::derived(flatten_edges(zalsa, edges))
544+
}
545+
QueryOriginRef::DerivedUntracked(edges) => {
546+
QueryOrigin::derived_untracked(flatten_edges(zalsa, edges))
547+
}
548+
QueryOriginRef::Assigned(key) => {
549+
let dependency = zalsa.lookup_ingredient(key.ingredient_index());
550+
assert!(
551+
dependency.is_persistable(),
552+
"specified query `{}` must be persistable",
553+
dependency.debug_name()
554+
);
555+
556+
QueryOrigin::assigned(key)
557+
}
558+
QueryOriginRef::FixpointInitial => unreachable!(
559+
"`should_serialize` returns `false` for provisional queries"
560+
),
561+
};
562+
563+
let memo = memo.with_origin(flattened_origin);
536564

537565
// TODO: Group structs by ingredient index into a nested map.
538566
let key = format!(
@@ -541,14 +569,34 @@ mod persistence {
541569
entry.key_index().as_bits()
542570
);
543571

544-
map.serialize_entry(&key, memo)?;
572+
map.serialize_entry(&key, &memo)?;
545573
}
546574
}
547575

548576
map.end()
549577
}
550578
}
551579

580+
// Flatten the dependency edges before serialization.
581+
fn flatten_edges(zalsa: &Zalsa, edges: &[QueryEdge]) -> FxIndexSet<QueryEdge> {
582+
let mut flattened_edges =
583+
FxIndexSet::with_capacity_and_hasher(edges.len(), Default::default());
584+
585+
for &edge in edges {
586+
let dependency = zalsa.lookup_ingredient(edge.key().ingredient_index());
587+
588+
if dependency.is_persistable() {
589+
// If the dependency will be serialized, we can serialize the edge directly.
590+
flattened_edges.insert(edge);
591+
} else {
592+
// Otherwise, serialize the minimum edges necessary to cover the dependency.
593+
dependency.collect_minimum_serialized_edges(zalsa, edge, &mut flattened_edges);
594+
}
595+
}
596+
597+
flattened_edges
598+
}
599+
552600
pub struct DeserializeIngredient<'db, C>
553601
where
554602
C: Configuration,

src/function/memo.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,36 @@ mod persistence {
359359
use crate::function::memo::Memo;
360360
use crate::function::Configuration;
361361
use crate::revision::AtomicRevision;
362-
use crate::zalsa_local::QueryRevisions;
362+
use crate::zalsa_local::persistence::MappedQueryRevisions;
363+
use crate::zalsa_local::{QueryOrigin, QueryRevisions};
363364

364365
use serde::ser::SerializeStruct;
365366
use serde::Deserialize;
366367

367-
impl<C> serde::Serialize for Memo<'_, C>
368+
/// A reference to the fields of a [`Memo`], with its [`QueryRevisions`] transformed.
369+
pub(crate) struct MappedMemo<'memo, 'db, C: Configuration> {
370+
value: Option<&'memo C::Output<'db>>,
371+
verified_at: AtomicRevision,
372+
revisions: MappedQueryRevisions<'memo>,
373+
}
374+
375+
impl<'db, C: Configuration> Memo<'db, C> {
376+
pub(crate) fn with_origin(&self, origin: QueryOrigin) -> MappedMemo<'_, 'db, C> {
377+
let Memo {
378+
ref verified_at,
379+
ref value,
380+
ref revisions,
381+
} = *self;
382+
383+
MappedMemo {
384+
value: value.as_ref(),
385+
verified_at: AtomicRevision::from(verified_at.load()),
386+
revisions: revisions.with_origin(origin),
387+
}
388+
}
389+
}
390+
391+
impl<C> serde::Serialize for MappedMemo<'_, '_, C>
368392
where
369393
C: Configuration,
370394
{
@@ -386,13 +410,15 @@ mod persistence {
386410
}
387411
}
388412

389-
let Memo {
413+
let MappedMemo {
390414
value,
391415
verified_at,
392416
revisions,
393417
} = self;
394418

395-
let value = value.as_ref().expect("attempted to serialize empty memo");
419+
let value = value.expect(
420+
"attempted to serialize memo where `Memo::should_serialize` returned `false`",
421+
);
396422

397423
let mut s = serializer.serialize_struct("Memo", 3)?;
398424
s.serialize_field("value", &SerializeValue::<C>(value))?;

src/id.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use crate::zalsa::Zalsa;
1818
/// As an end-user of `Salsa` you will generally not use `Id` directly,
1919
/// it is wrapped in new types.
2020
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
21-
#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
2221
pub struct Id {
2322
index: NonZeroU32,
2423
generation: u32,
@@ -133,6 +132,26 @@ impl Hash for Id {
133132
}
134133
}
135134

135+
#[cfg(feature = "persistence")]
136+
impl serde::Serialize for Id {
137+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138+
where
139+
S: serde::Serializer,
140+
{
141+
serde::Serialize::serialize(&self.as_bits(), serializer)
142+
}
143+
}
144+
145+
#[cfg(feature = "persistence")]
146+
impl<'de> serde::Deserialize<'de> for Id {
147+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148+
where
149+
D: serde::Deserializer<'de>,
150+
{
151+
serde::Deserialize::deserialize(deserializer).map(Self::from_bits)
152+
}
153+
}
154+
136155
impl Debug for Id {
137156
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138157
if self.generation() == 0 {

src/ingredient.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ use crate::cycle::{
66
};
77
use crate::database::RawDatabase;
88
use crate::function::{VerifyCycleHeads, VerifyResult};
9+
use crate::hash::FxIndexSet;
910
use crate::runtime::Running;
1011
use crate::sync::Arc;
1112
use crate::table::memo::MemoTableTypes;
1213
use crate::table::Table;
1314
use crate::zalsa::{transmute_data_mut_ptr, transmute_data_ptr, IngredientIndex, JarKind, Zalsa};
14-
use crate::zalsa_local::QueryOriginRef;
15+
use crate::zalsa_local::{QueryEdge, QueryOriginRef};
1516
use crate::{DatabaseKeyIndex, Id, Revision};
1617

1718
/// A "jar" is a group of ingredients that are added atomically.
@@ -55,6 +56,20 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
5556
cycle_heads: &mut VerifyCycleHeads,
5657
) -> VerifyResult;
5758

59+
/// Collects the minimum edges necessary to serialize a given dependency edge on this ingredient,
60+
/// without necessarily serializing the dependency edge itself.
61+
///
62+
/// This generally only returns any transitive input dependencies, i.e. the leaves of the dependency
63+
/// tree, as most other fine-grained dependencies are covered by the inputs.
64+
///
65+
/// Note that any ingredients returned by this function must be persistable.
66+
fn collect_minimum_serialized_edges(
67+
&self,
68+
zalsa: &Zalsa,
69+
edge: QueryEdge,
70+
serialized_edges: &mut FxIndexSet<QueryEdge>,
71+
);
72+
5873
/// Returns information about the current provisional status of `input`.
5974
///
6075
/// Is it a provisional value or has it been finalized and in which iteration.
@@ -217,7 +232,7 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
217232
_zalsa: &'db Zalsa,
218233
_f: &mut dyn FnMut(&dyn erased_serde::Serialize),
219234
) {
220-
unimplemented!("called `serialize` on ingredient where `is_persistable` returns `false`")
235+
unimplemented!("called `serialize` on ingredient where `should_serialize` returns `false`")
221236
}
222237

223238
/// Deserialize the ingredient.
@@ -227,7 +242,9 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
227242
_zalsa: &mut Zalsa,
228243
_deserializer: &mut dyn erased_serde::Deserializer,
229244
) -> Result<(), erased_serde::Error> {
230-
unimplemented!("called `deserialize` on ingredient where `is_persistable` returns `false`")
245+
unimplemented!(
246+
"called `deserialize` on ingredient where `should_serialize` returns `false`"
247+
)
231248
}
232249
}
233250

src/input.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod singleton;
99
use input_field::FieldIngredientImpl;
1010

1111
use crate::function::{VerifyCycleHeads, VerifyResult};
12+
use crate::hash::FxIndexSet;
1213
use crate::id::{AsId, FromId, FromIdWithDb};
1314
use crate::ingredient::Ingredient;
1415
use crate::input::singleton::{Singleton, SingletonChoice};
@@ -18,6 +19,7 @@ use crate::sync::Arc;
1819
use crate::table::memo::{MemoTable, MemoTableTypes};
1920
use crate::table::{Slot, Table};
2021
use crate::zalsa::{IngredientIndex, JarKind, Zalsa};
22+
use crate::zalsa_local::QueryEdge;
2123
use crate::{Durability, Id, Revision, Runtime};
2224

2325
pub trait Configuration: Any {
@@ -274,7 +276,16 @@ impl<C: Configuration> Ingredient for IngredientImpl<C> {
274276
) -> VerifyResult {
275277
// Input ingredients are just a counter, they store no data, they are immortal.
276278
// Their *fields* are stored in function ingredients elsewhere.
277-
VerifyResult::unchanged()
279+
panic!("nothing should ever depend on an input struct directly")
280+
}
281+
282+
fn collect_minimum_serialized_edges(
283+
&self,
284+
_zalsa: &Zalsa,
285+
_edge: QueryEdge,
286+
_serialized_edges: &mut FxIndexSet<QueryEdge>,
287+
) {
288+
panic!("nothing should ever depend on an input struct directly")
278289
}
279290

280291
fn debug_name(&self) -> &'static str {

0 commit comments

Comments
 (0)