Skip to content

Commit 42a4fb6

Browse files
Merge pull request #928 from saulshanabrook/codex/proof-selector-merge-main
Avoid redundant proof merge rows for eager current values
2 parents e22580d + 0a868b6 commit 42a4fb6

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

src/proofs/proof_encoding.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ pub(crate) struct EncodingState {
1010
pub uf_function: HashMap<String, String>,
1111
/// Maps sort name -> proof function name (set from :internal-proof-func annotation).
1212
pub proof_func_parent: HashMap<String, String>,
13+
/// Function name -> (hidden current-value function, input arity). The
14+
/// current function uses the original eager backend merge, so cleanup can
15+
/// discard stale proof-view candidates whenever the current value already
16+
/// has a proof witness.
17+
pub merge_current: HashMap<String, (String, usize)>,
1318
pub term_header_added: bool,
1419
// TODO this is very ugly- we should separate out a typechecking struct
1520
// since we didn't need an entire e-graph
@@ -30,6 +35,7 @@ impl EncodingState {
3035
uf_parent: HashMap::default(),
3136
uf_function: HashMap::default(),
3237
proof_func_parent: HashMap::default(),
38+
merge_current: HashMap::default(),
3339
term_header_added: false,
3440
original_typechecking: None,
3541
proofs_enabled: false,
@@ -255,13 +261,25 @@ impl<'a> ProofInstrumentor<'a> {
255261
.as_ref()
256262
.unwrap_or_else(|| panic!("Proofs don't support :no-merge"));
257263

264+
let current_name = self
265+
.egraph
266+
.parser
267+
.symbol_gen
268+
.fresh(&format!("{name}Current"));
269+
self.egraph
270+
.proof_state
271+
.merge_current
272+
.insert(name.clone(), (current_name.clone(), child_names.len()));
273+
258274
let fresh_name = self.egraph.parser.symbol_gen.fresh("merge_rule");
259275
let cleanup_name = self.egraph.parser.symbol_gen.fresh("merge_cleanup");
276+
let current_cleanup_name = self.egraph.parser.symbol_gen.fresh("merge_current_cleanup");
260277

261278
let p1_fresh = self.egraph.parser.symbol_gen.fresh("p1");
262279
let p2_fresh = self.egraph.parser.symbol_gen.fresh("p2");
263280
let view_name = self.view_name(&fdecl.name);
264281
let rebuilding_cleanup_ruleset = self.proof_names().rebuilding_cleanup_ruleset_name.clone();
282+
let input_sorts = ListDisplay(&fdecl.schema.input, " ");
265283
let proof_query = if self.egraph.proof_state.proofs_enabled {
266284
// View is a function with proof output; bind proof variables
267285
format!(
@@ -310,7 +328,11 @@ impl<'a> ProofInstrumentor<'a> {
310328
// The first runs the merge function adding a new row.
311329
// The second deletes rows with old values for the old variable, while the third deletes rows with new values for the new variable.
312330
format!(
313-
"(sort {fresh_sort})
331+
"(function {current_name} ({input_sorts}) {output_sort}
332+
:merge {merge_fn}
333+
:unextractable
334+
:internal-hidden)
335+
(sort {fresh_sort})
314336
(constructor {cleanup_constructor} ({output_sort} {output_sort}) {fresh_sort} :internal-hidden)
315337
(rule (({view_name} {child_names_str} old)
316338
({view_name} {child_names_str} new)
@@ -333,6 +355,13 @@ impl<'a> ProofInstrumentor<'a> {
333355
((delete ({view_name} {child_names_str} old)))
334356
:ruleset {rebuilding_cleanup_ruleset}
335357
:name \"{cleanup_name}\")
358+
(rule ((= selected ({current_name} {child_names_str}))
359+
({view_name} {child_names_str} selected)
360+
({view_name} {child_names_str} old)
361+
(!= selected old))
362+
((delete ({view_name} {child_names_str} old)))
363+
:ruleset {rebuilding_cleanup_ruleset}
364+
:name \"{current_cleanup_name}\")
336365
",
337366
)
338367
}
@@ -999,7 +1028,16 @@ impl<'a> ProofInstrumentor<'a> {
9991028
/// View is always a function (returning Proof or Unit).
10001029
fn update_view(&mut self, fname: &str, args: &[String], proof: &str) -> String {
10011030
let view_name = self.view_name(fname);
1002-
format!("(set ({view_name} {}) {proof})", ListDisplay(args, " "))
1031+
let view_update = format!("(set ({view_name} {}) {proof})", ListDisplay(args, " "));
1032+
if let Some((current_name, input_arity)) =
1033+
self.egraph.proof_state.merge_current.get(fname).cloned()
1034+
&& args.len() == input_arity + 1
1035+
{
1036+
let inputs = ListDisplay(&args[..input_arity], " ");
1037+
let output = &args[input_arity];
1038+
return format!("{view_update}\n(set ({current_name} {inputs}) {output})");
1039+
}
1040+
view_update
10031041
}
10041042

10051043
/// Return some code adding to the view and term tables.

src/proofs/snapshots/egglog__proofs__proof_tests__tests__doc_example_add_function2.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ expression: snapshot
1313
(function __addView (i64 i64 i64) Unit :merge old :unextractable :internal-term-constructor add)
1414
(constructor __to_delete_add (i64 i64) __view :internal-hidden)
1515
(constructor __to_subsume_add (i64 i64) __view :internal-hidden)
16+
(function __addCurrent (i64 i64) i64 :merge old :unextractable :internal-hidden)
1617
(sort __mergecleanupsort)
1718
(constructor __mergecleanup (i64 i64) __mergecleanupsort :internal-hidden)
1819
(rule ((__addView c0_ c1_ old)
1920
(__addView c0_ c1_ new)
2021
(!= old new)
2122
(= (ordering-max old new) new))
2223
((set (__addView c0_ c1_ old) ())
24+
(set (__addCurrent c0_ c1_) old)
2325
(__mergecleanup old old)
2426
(__mergecleanup old new))
2527
:ruleset __rebuilding :name "__merge_rule")
@@ -29,6 +31,12 @@ expression: snapshot
2931
(!= merged old))
3032
((delete (__addView c0_ c1_ old)))
3133
:ruleset __rebuilding_cleanup :name "__merge_cleanup")
34+
(rule ((= selected (__addCurrent c0_ c1_))
35+
(__addView c0_ c1_ selected)
36+
(__addView c0_ c1_ old)
37+
(!= selected old))
38+
((delete (__addView c0_ c1_ old)))
39+
:ruleset __rebuilding_cleanup :name "__merge_current_cleanup")
3240
(rule ((__to_delete_add c0_ c1_)
3341
(__addView c0_ c1_ out))
3442
((delete (__addView c0_ c1_ out))

tests/proof_selector_merge.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use egglog::EGraph;
2+
3+
#[test]
4+
fn selector_merge_cleanup_uses_existing_current_proof() {
5+
let mut candidates = String::new();
6+
for i in 0..200 {
7+
candidates.push_str(&format!("(Candidate (K) {i})\n"));
8+
}
9+
10+
let program = format!(
11+
r#"
12+
(sort Key)
13+
(constructor K () Key)
14+
(function Best (Key) i64 :merge old)
15+
(relation Candidate (Key i64))
16+
(rule ((Candidate k v))
17+
((set (Best k) v)))
18+
{candidates}
19+
(run 1)
20+
(check (= (Best (K)) 0))
21+
"#
22+
);
23+
24+
let mut egraph = EGraph::new_with_proofs().with_proof_testing();
25+
egraph.parse_and_run_program(None, &program).unwrap();
26+
27+
let merge_rule_matches: usize = egraph
28+
.get_overall_run_report()
29+
.num_matches_per_rule
30+
.iter()
31+
.filter(|(rule, _)| rule.as_ref().contains("merge_rule"))
32+
.map(|(_, matches)| *matches)
33+
.sum();
34+
assert_eq!(merge_rule_matches, 0);
35+
}

0 commit comments

Comments
 (0)