@@ -13,6 +13,7 @@ use crate::cycle::{
1313use crate :: database:: RawDatabase ;
1414use crate :: function:: delete:: DeletedEntries ;
1515use crate :: function:: sync:: { ClaimResult , SyncTable } ;
16+ use crate :: hash:: FxIndexSet ;
1617use crate :: ingredient:: { Ingredient , WaitForResult } ;
1718use crate :: key:: DatabaseKeyIndex ;
1819use crate :: plumbing:: { self , MemoIngredientMap } ;
@@ -22,7 +23,7 @@ use crate::table::memo::MemoTableTypes;
2223use crate :: table:: Table ;
2324use crate :: views:: DatabaseDownCaster ;
2425use crate :: zalsa:: { IngredientIndex , JarKind , MemoIngredientIndex , Zalsa } ;
25- use crate :: zalsa_local:: QueryOriginRef ;
26+ use crate :: zalsa_local:: { QueryEdge , QueryOriginRef } ;
2627use 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" ) ]
471495mod 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 ,
0 commit comments