1use std::{
12 fmt::Debug,
13 hash::Hash,
14 iter, mem,
15 ops::{Index, IndexMut},
16 sync::{Arc, Mutex},
17};
18
19use crate::core_relations::{
20 BaseValue, BaseValueId, BaseValues, ColumnId, Constraint, ContainerValue, ContainerValues,
21 CounterId, Database, DisplacedTable, DisplacedTableWithProvenance, ExecutionState,
22 ExternalFunction, ExternalFunctionId, MergeVal, Offset, PlanStrategy, RuleSetReport,
23 SortedWritesTable, TableId, TaggedRowBuffer, Value, WrappedTable,
24};
25use crate::numeric_id::{DenseIdMap, DenseIdMapWithReuse, IdVec, NumericId, define_id};
26use egglog_core_relations as core_relations;
27use egglog_numeric_id as numeric_id;
28use hashbrown::HashMap;
29use indexmap::{IndexMap, IndexSet, map::Entry};
30use log::info;
31use once_cell::sync::Lazy;
32pub use proof_format::{EqProofId, ProofStore, TermProofId};
33use proof_spec::{ProofReason, ProofReconstructionState, ReasonSpecId};
34use smallvec::SmallVec;
35use web_time::{Duration, Instant};
36
37pub mod macros;
38pub mod proof_format;
39pub(crate) mod proof_spec;
40pub(crate) mod rule;
41pub(crate) mod syntax;
42#[cfg(test)]
43mod tests;
44
45pub use rule::{Function, QueryEntry, RuleBuilder};
46pub use syntax::{SourceExpr, SourceSyntax, TopLevelLhsExpr};
47use thiserror::Error;
48
49#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
50pub enum ColumnTy {
51 Id,
52 Base(BaseValueId),
53}
54
55define_id!(pub RuleId, u32, "An egglog-style rule");
56define_id!(pub FunctionId, u32, "An id representing an egglog function");
57define_id!(pub(crate) Timestamp, u32, "An abstract timestamp used to track execution of egglog rules");
58impl Timestamp {
59 fn to_value(self) -> Value {
60 Value::new(self.rep())
61 }
62}
63
64#[derive(Clone)]
66pub struct EGraph {
67 db: Database,
68 uf_table: TableId,
69 id_counter: CounterId,
70 reason_counter: CounterId,
71 timestamp_counter: CounterId,
72 rules: DenseIdMapWithReuse<RuleId, RuleInfo>,
73 funcs: DenseIdMap<FunctionId, FunctionInfo>,
74 panic_message: SideChannel<String>,
75 panic_funcs: HashMap<String, ExternalFunctionId>,
81 proof_specs: IdVec<ReasonSpecId, Arc<ProofReason>>,
82 cong_spec: ReasonSpecId,
83 reason_tables: IndexMap<usize , TableId>,
86 term_tables: IndexMap<usize , TableId>,
87 tracing: bool,
88}
89
90pub type Result<T> = std::result::Result<T, anyhow::Error>;
91
92impl Default for EGraph {
93 fn default() -> Self {
94 let mut db = Database::new();
95 let uf_table = db.add_table(DisplacedTable::default(), iter::empty(), iter::empty());
96 EGraph::create_internal(db, uf_table, false)
97 }
98}
99
100pub struct FunctionConfig {
102 pub schema: Vec<ColumnTy>,
104 pub default: DefaultVal,
106 pub merge: MergeFn,
108 pub name: String,
110 pub can_subsume: bool,
112}
113
114impl EGraph {
115 pub fn with_tracing() -> EGraph {
121 let mut db = Database::new();
122 let uf_table = db.add_table(
123 DisplacedTableWithProvenance::default(),
124 iter::empty(),
125 iter::empty(),
126 );
127 EGraph::create_internal(db, uf_table, true)
128 }
129
130 fn create_internal(mut db: Database, uf_table: TableId, tracing: bool) -> EGraph {
131 let id_counter = db.add_counter();
132 let trace_counter = db.add_counter();
133 let ts_counter = db.add_counter();
134 db.inc_counter(ts_counter);
136 let mut proof_specs = IdVec::default();
137 let cong_spec = proof_specs.push(Arc::new(ProofReason::CongRow));
138
139 Self {
140 db,
141 uf_table,
142 id_counter,
143 reason_counter: trace_counter,
144 timestamp_counter: ts_counter,
145 rules: Default::default(),
146 funcs: Default::default(),
147 panic_message: Default::default(),
148 panic_funcs: Default::default(),
149 proof_specs,
150 cong_spec,
151 reason_tables: Default::default(),
152 term_tables: Default::default(),
153 tracing,
154 }
155 }
156
157 fn next_ts(&self) -> Timestamp {
158 Timestamp::from_usize(self.db.read_counter(self.timestamp_counter))
159 }
160
161 fn inc_ts(&mut self) {
162 self.db.inc_counter(self.timestamp_counter);
163 }
164
165 pub fn base_values_mut(&mut self) -> &mut BaseValues {
168 self.db.base_values_mut()
169 }
170
171 pub fn container_values_mut(&mut self) -> &mut ContainerValues {
174 self.db.container_values_mut()
175 }
176
177 pub fn container_values(&self) -> &ContainerValues {
179 self.db.container_values()
180 }
181
182 pub fn get_container_value<C: ContainerValue>(&mut self, val: C) -> Value {
184 self.register_container_ty::<C>();
185 self.db
186 .with_execution_state(|state| state.clone().container_values().register_val(val, state))
187 }
188
189 pub fn register_container_ty<C: ContainerValue>(&mut self) {
194 let uf_table = self.uf_table;
195 let ts_counter = self.timestamp_counter;
196 self.db.container_values_mut().register_type::<C>(
197 self.id_counter,
198 move |state, old, new| {
199 if old != new {
200 let next_ts = Value::from_usize(state.read_counter(ts_counter));
201 state.stage_insert(uf_table, &[old, new, next_ts]);
202 std::cmp::min(old, new)
203 } else {
204 old
205 }
206 },
207 );
208 }
209
210 pub fn base_values(&self) -> &BaseValues {
212 self.db.base_values()
213 }
214
215 pub fn base_value_constant<T>(&self, x: T) -> QueryEntry
217 where
218 T: BaseValue,
219 {
220 QueryEntry::Const {
221 val: self.base_values().get(x),
222 ty: ColumnTy::Base(self.base_values().get_ty::<T>()),
223 }
224 }
225
226 pub fn register_external_func(
227 &mut self,
228 func: impl ExternalFunction + 'static,
229 ) -> ExternalFunctionId {
230 self.db.add_external_function(func)
231 }
232
233 pub fn free_external_func(&mut self, func: ExternalFunctionId) {
234 self.db.free_external_function(func)
235 }
236
237 pub fn fresh_id(&mut self) -> Value {
239 Value::from_usize(self.db.inc_counter(self.id_counter))
240 }
241
242 fn get_canon_in_uf(&self, val: Value) -> Value {
246 let table = self.db.get_table(self.uf_table);
247 let row = table.get_row(&[val]);
248 row.map(|row| row.vals[1]).unwrap_or(val)
249 }
250
251 pub fn get_canon_repr(&self, val: Value, ty: ColumnTy) -> Value {
256 match ty {
257 ColumnTy::Id => self.get_canon_in_uf(val),
258 ColumnTy::Base(_) => val,
259 }
260 }
261
262 fn term_table(&mut self, table: TableId) -> TableId {
263 let spec = self.db.get_table(table).spec();
264 match self.term_tables.entry(spec.n_keys) {
265 Entry::Occupied(o) => *o.get(),
266 Entry::Vacant(v) => {
267 let table = SortedWritesTable::new(
268 spec.n_keys + 1, spec.n_keys + 1 + 2, None,
271 vec![], Box::new(|_, _, _, _| false),
273 );
274 let table_id = self.db.add_table(table, iter::empty(), iter::empty());
275 *v.insert(table_id)
276 }
277 }
278 }
279
280 fn reason_table(&mut self, spec: &ProofReason) -> TableId {
281 let arity = spec.arity();
282 match self.reason_tables.entry(arity) {
283 Entry::Occupied(o) => *o.get(),
284 Entry::Vacant(v) => {
285 let table = SortedWritesTable::new(
286 arity,
287 arity + 1, None,
289 vec![], Box::new(|_, _, _, _| false),
291 );
292 let table_id = self.db.add_table(table, iter::empty(), iter::empty());
293 *v.insert(table_id)
294 }
295 }
296 }
297
298 pub fn add_values(&mut self, values: impl IntoIterator<Item = (FunctionId, Vec<Value>)>) {
307 self.add_values_with_desc("", values)
308 }
309
310 pub fn add_term(&mut self, func: FunctionId, inputs: &[Value], desc: &str) -> Value {
317 let info = &self.funcs[func];
318 let schema_math = SchemaMath {
319 tracing: self.tracing,
320 subsume: info.can_subsume,
321 func_cols: info.schema.len(),
322 };
323 let mut extended_row = Vec::new();
324 extended_row.extend_from_slice(inputs);
325 let term = self.tracing.then(|| {
326 let reason = self.get_fiat_reason(desc);
327 self.get_term(func, inputs, reason)
328 });
329 let res = term.unwrap_or_else(|| self.fresh_id());
330 schema_math.write_table_row(
331 &mut extended_row,
332 RowVals {
333 timestamp: self.next_ts().to_value(),
334 ret_val: Some(res),
335 proof: term,
336 subsume: schema_math.subsume.then_some(NOT_SUBSUMED),
337 },
338 );
339 extended_row[schema_math.ret_val_col()] = res;
340 let table_id = self.funcs[func].table;
341 self.db
342 .get_table(table_id)
343 .new_buffer()
344 .stage_insert(&extended_row);
345 self.flush_updates();
346 self.get_canon_in_uf(res)
347 }
348
349 fn get_term(&mut self, func: FunctionId, key: &[Value], reason: Value) -> Value {
354 let table_id = self.funcs[func].table;
355 let term_table_id = self.term_table(table_id);
356 let table = self.db.get_table(term_table_id);
357 let mut term_key = Vec::with_capacity(key.len() + 1);
358 term_key.push(Value::new(func.rep()));
359 term_key.extend(key);
360 if let Some(row) = table.get_row(&term_key) {
361 row.vals[row.vals.len() - 2]
362 } else {
363 let result = Value::from_usize(self.db.inc_counter(self.id_counter));
364 term_key.push(result);
365 term_key.push(reason);
366 self.db
367 .get_table(term_table_id)
368 .new_buffer()
369 .stage_insert(&term_key);
370 self.db.merge_table(term_table_id);
371 result
372 }
373 }
374
375 pub fn lookup_id(&self, func: FunctionId, key: &[Value]) -> Option<Value> {
378 let info = &self.funcs[func];
379 let schema_math = SchemaMath {
380 tracing: self.tracing,
381 subsume: info.can_subsume,
382 func_cols: info.schema.len(),
383 };
384 let table_id = info.table;
385 let table = self.db.get_table(table_id);
386 let row = table.get_row(key)?;
387 Some(row.vals[schema_math.ret_val_col()])
388 }
389
390 fn get_fiat_reason(&mut self, desc: &str) -> Value {
391 let reason = Arc::new(ProofReason::Fiat { desc: desc.into() });
392 let reason_table = self.reason_table(&reason);
393 let reason_spec_id = self.proof_specs.push(reason);
394 let reason_id = Value::from_usize(self.db.inc_counter(self.reason_counter));
395 self.db
396 .get_table(reason_table)
397 .new_buffer()
398 .stage_insert(&[Value::new(reason_spec_id.rep()), reason_id]);
399 self.db.merge_table(reason_table);
400 reason_id
401 }
402
403 pub fn add_values_with_desc(
413 &mut self,
414 desc: &str,
415 values: impl IntoIterator<Item = (FunctionId, Vec<Value>)>,
416 ) {
417 let mut extended_row = Vec::<Value>::new();
418 let reason_id = self.tracing.then(|| self.get_fiat_reason(desc));
419 let mut bufs = DenseIdMap::default();
420 for (func, row) in values.into_iter() {
421 let table_info = &self.funcs[func];
422 let schema_math = SchemaMath {
423 tracing: self.tracing,
424 subsume: table_info.can_subsume,
425 func_cols: table_info.schema.len(),
426 };
427 let table_id = table_info.table;
428 let term_id = reason_id.map(|reason| {
429 let term_id = self.get_term(func, &row[0..schema_math.num_keys()], reason);
431 let buf = bufs.get_or_insert(self.uf_table, || {
432 self.db.get_table(self.uf_table).new_buffer()
433 });
434 buf.stage_insert(&[
436 *row.last().unwrap(),
437 term_id,
438 self.next_ts().to_value(),
439 reason,
440 ]);
441 term_id
442 });
443 extended_row.extend_from_slice(&row);
444 schema_math.write_table_row(
445 &mut extended_row,
446 RowVals {
447 timestamp: self.next_ts().to_value(),
448 proof: term_id,
449 subsume: schema_math.subsume.then_some(NOT_SUBSUMED),
450 ret_val: None, },
452 );
453 let buf = bufs.get_or_insert(table_id, || self.db.get_table(table_id).new_buffer());
454 buf.stage_insert(&extended_row);
455 extended_row.clear();
456 }
457 mem::drop(bufs);
459 self.flush_updates();
460 }
461
462 pub fn approx_table_size(&self, table: FunctionId) -> usize {
463 self.db.estimate_size(self.funcs[table].table, None)
464 }
465
466 pub fn table_size(&self, table: FunctionId) -> usize {
467 self.db.get_table(self.funcs[table].table).len()
468 }
469
470 pub fn explain_term(&mut self, id: Value, store: &mut ProofStore) -> Result<TermProofId> {
479 if !self.tracing {
480 return Err(ProofReconstructionError::TracingNotEnabled.into());
481 }
482 let mut state = ProofReconstructionState::new(store);
483 Ok(self.explain_term_inner(id, &mut state))
484 }
485
486 pub fn explain_terms_equal(
493 &mut self,
494 id1: Value,
495 id2: Value,
496 store: &mut ProofStore,
497 ) -> Result<EqProofId> {
498 if !self.tracing {
499 return Err(ProofReconstructionError::TracingNotEnabled.into());
500 }
501 let mut state = ProofReconstructionState::new(store);
502 if self.get_canon_in_uf(id1) != self.get_canon_in_uf(id2) {
503 let mut buf = Vec::<u8>::new();
506 let term_id_1 = self.reconstruct_term(id1, ColumnTy::Id, &mut state);
507 let term_id_2 = self.reconstruct_term(id2, ColumnTy::Id, &mut state);
508 store.termdag.print_term(term_id_1, &mut buf).unwrap();
509 let term1 = String::from_utf8(buf).unwrap();
510 let mut buf = Vec::<u8>::new();
511 store.termdag.print_term(term_id_2, &mut buf).unwrap();
512 let term2 = String::from_utf8(buf).unwrap();
513 return Err(
514 ProofReconstructionError::EqualityExplanationOfUnequalTerms { term1, term2 }.into(),
515 );
516 }
517 Ok(self.explain_terms_equal_inner(id1, id2, &mut state))
518 }
519
520 pub fn for_each(&self, table: FunctionId, mut f: impl FnMut(FunctionRow<'_>)) {
524 self.for_each_while(table, |row| {
525 f(row);
526 true
527 });
528 }
529
530 pub fn for_each_while(&self, table: FunctionId, mut f: impl FnMut(FunctionRow<'_>) -> bool) {
533 let info = &self.funcs[table];
534 let table = self.funcs[table].table;
535 let schema_math = SchemaMath {
536 tracing: self.tracing,
537 subsume: info.can_subsume,
538 func_cols: info.schema.len(),
539 };
540 let imp = self.db.get_table(table);
541 let all = imp.all();
542 let mut cur = Offset::new(0);
543 let mut buf = TaggedRowBuffer::new(imp.spec().arity());
544 macro_rules! drain_buf {
549 ($buf:expr) => {
550 for (_, row) in $buf.non_stale() {
551 let subsumed =
552 schema_math.subsume && row[schema_math.subsume_col()] == SUBSUMED;
553 if !f(FunctionRow {
554 vals: &row[0..schema_math.func_cols],
555 subsumed,
556 }) {
557 return;
558 }
559 }
560 $buf.clear();
561 };
562 }
563 while let Some(next) = imp.scan_bounded(all.as_ref(), cur, 32, &mut buf) {
564 drain_buf!(buf);
565 cur = next;
566 }
567 drain_buf!(buf);
568 }
569
570 pub fn dump_debug_info(&self) {
574 info!("=== View Tables ===");
575 for (id, info) in self.funcs.iter() {
576 let table = self.db.get_table(info.table);
577 self.scan_table(table, |row| {
578 info!(
579 "View Table {name} / {id:?} / {table:?}: {row:?}",
580 name = info.name,
581 table = info.table
582 )
583 });
584 }
585
586 info!("=== Term Tables ===");
587 for (_, table_id) in &self.term_tables {
588 let table = self.db.get_table(*table_id);
589 self.scan_table(table, |row| {
590 let name = &self.funcs[FunctionId::new(row[0].rep())].name;
591 let row = &row[1..];
592 info!("Term Table {table_id:?}: {name}, {row:?}")
593 });
594 }
595
596 info!("=== Reason Tables ===");
597 for (_, table_id) in &self.reason_tables {
598 let table = self.db.get_table(*table_id);
599 self.scan_table(table, |row| {
600 let spec = self.proof_specs[ReasonSpecId::new(row[0].rep())].as_ref();
601 let row = &row[1..];
602 info!("Reason Table {table_id:?}: {spec:?}, {row:?}")
603 });
604 }
605 }
606
607 fn scan_table(&self, table: &WrappedTable, mut f: impl FnMut(&[Value])) {
609 const BATCH_SIZE: usize = 128;
610 let all = table.all();
611 let mut cur = Offset::new(0);
612 let mut out = TaggedRowBuffer::new(table.spec().arity());
613 while let Some(next) = table.scan_bounded(all.as_ref(), cur, BATCH_SIZE, &mut out) {
614 out.non_stale().for_each(|(_, row)| f(row));
615 out.clear();
616 cur = next;
617 }
618 out.non_stale().for_each(|(_, row)| f(row));
619 }
620
621 pub fn add_table(&mut self, config: FunctionConfig) -> FunctionId {
623 let FunctionConfig {
624 schema,
625 default,
626 merge,
627 name,
628 can_subsume,
629 } = config;
630 assert!(
631 !schema.is_empty(),
632 "must have at least one column in schema"
633 );
634 let to_rebuild: Vec<ColumnId> = schema
635 .iter()
636 .enumerate()
637 .filter(|(_, ty)| matches!(ty, ColumnTy::Id))
638 .map(|(i, _)| ColumnId::from_usize(i))
639 .collect();
640 let schema_math = SchemaMath {
641 tracing: self.tracing,
642 subsume: can_subsume,
643 func_cols: schema.len(),
644 };
645 let n_args = schema_math.num_keys();
646 let n_cols = schema_math.table_columns();
647 let next_func_id = self.funcs.next_id();
648 let mut read_deps = IndexSet::<TableId>::new();
649 let mut write_deps = IndexSet::<TableId>::new();
650 merge.fill_deps(self, &mut read_deps, &mut write_deps);
651 let merge_fn = merge.to_callback(schema_math, &name, self);
652 let table = SortedWritesTable::new(
653 n_args,
654 n_cols,
655 Some(ColumnId::from_usize(schema.len())),
656 to_rebuild,
657 merge_fn,
658 );
659 let table_id =
660 self.db
661 .add_table(table, read_deps.iter().copied(), write_deps.iter().copied());
662
663 let res = self.funcs.push(FunctionInfo {
664 table: table_id,
665 schema: schema.clone(),
666 incremental_rebuild_rules: Default::default(),
667 nonincremental_rebuild_rule: RuleId::new(!0),
668 default_val: default,
669 can_subsume,
670 name: name.into(),
671 });
672 debug_assert_eq!(res, next_func_id);
673 let incremental_rebuild_rules = self.incremental_rebuild_rules(res, &schema);
674 let nonincremental_rebuild_rule = self.nonincremental_rebuild(res, &schema);
675 let info = &mut self.funcs[res];
676 info.incremental_rebuild_rules = incremental_rebuild_rules;
677 info.nonincremental_rebuild_rule = nonincremental_rebuild_rule;
678 res
679 }
680
681 pub fn run_rules(&mut self, rules: &[RuleId]) -> Result<IterationReport> {
685 let ts = self.next_ts();
686
687 let rule_set_report = run_rules_impl(&mut self.db, &mut self.rules, rules, ts)?;
688 if let Some(message) = self.panic_message.lock().unwrap().take() {
689 return Err(PanicError(message).into());
690 }
691
692 let mut iteration_report = IterationReport {
693 changed: rule_set_report.changed,
694 rule_reports: rule_set_report.rule_reports.into_iter().collect(),
695 search_and_apply_time: rule_set_report.search_and_apply_time,
696 merge_time: rule_set_report.merge_time,
697 rebuild_time: Duration::ZERO,
698 };
699 if !iteration_report.changed {
700 return Ok(iteration_report);
701 }
702
703 let rebuild_timer = Instant::now();
704 self.rebuild()?;
705 iteration_report.rebuild_time = rebuild_timer.elapsed();
706
707 if let Some(message) = self.panic_message.lock().unwrap().take() {
708 return Err(PanicError(message).into());
709 }
710
711 Ok(iteration_report)
712 }
713
714 fn rebuild(&mut self) -> Result<()> {
715 fn do_parallel() -> bool {
716 #[cfg(test)]
717 {
718 use rand::Rng;
719 rand::rng().random_bool(0.5)
720 }
721 #[cfg(not(test))]
722 {
723 rayon::current_num_threads() > 1
724 }
725 }
726 if self.db.get_table(self.uf_table).rebuilder(&[]).is_some() {
727 let mut tables = Vec::with_capacity(self.funcs.next_id().index());
729 for (_, func) in self.funcs.iter() {
730 tables.push(func.table);
731 }
732 loop {
733 let container_rebuild = self.db.rebuild_containers(self.uf_table);
765 let table_rebuild =
766 self.db
767 .apply_rebuild(self.uf_table, &tables, self.next_ts().to_value());
768 self.inc_ts();
769 if !table_rebuild && !container_rebuild {
770 break;
771 }
772 }
773 return Ok(());
774 }
775 if do_parallel() {
776 return self.rebuild_parallel();
777 }
778 let start = Instant::now();
779
780 let mut changed = true;
782 while changed {
783 changed = false;
784 self.inc_ts();
787 let ts = self.next_ts();
788 for (_, info) in self.funcs.iter_mut() {
789 let last_rebuilt_at = self.rules[info.nonincremental_rebuild_rule].last_run_at;
790 let table_size = self.db.estimate_size(info.table, None);
791 let uf_size = self.db.estimate_size(
792 self.uf_table,
793 Some(Constraint::GeConst {
794 col: ColumnId::new(2),
795 val: last_rebuilt_at.to_value(),
796 }),
797 );
798 if incremental_rebuild(uf_size, table_size, false) {
799 marker_incremental_rebuild(|| -> Result<()> {
800 for rule in &info.incremental_rebuild_rules {
805 changed |= run_rules_impl(&mut self.db, &mut self.rules, &[*rule], ts)?
806 .changed;
807 }
808 self.rules[info.nonincremental_rebuild_rule].last_run_at = ts;
810 Ok(())
811 })?;
812 } else {
813 marker_nonincremental_rebuild(|| -> Result<()> {
814 changed |= run_rules_impl(
815 &mut self.db,
816 &mut self.rules,
817 &[info.nonincremental_rebuild_rule],
818 ts,
819 )?
820 .changed;
821 for rule in &info.incremental_rebuild_rules {
822 self.rules[*rule].last_run_at = ts;
823 }
824 Ok(())
825 })?;
826 }
827 }
828 }
829 log::info!("rebuild took {:?}", start.elapsed());
830 Ok(())
831 }
832
833 fn rebuild_parallel(&mut self) -> Result<()> {
838 let start = Instant::now();
839 #[derive(Default)]
840 struct RebuildState {
841 nonincremental: Vec<FunctionId>,
842 incremental: DenseIdMap<usize, SmallVec<[FunctionId; 2]>>,
843 }
844
845 impl RebuildState {
846 fn clear(&mut self) {
847 self.nonincremental.clear();
848 self.incremental.iter_mut().for_each(|(_, v)| v.clear());
849 }
850 }
851
852 let mut changed = true;
853 let mut state = RebuildState::default();
854 let mut scratch = Vec::new();
855 while changed {
856 changed = false;
857 state.clear();
858 self.inc_ts();
859 for (func, info) in self.funcs.iter_mut() {
862 let last_rebuilt_at = self.rules[info.nonincremental_rebuild_rule].last_run_at;
863 let table_size = self.db.estimate_size(info.table, None);
864 let uf_size = self.db.estimate_size(
865 self.uf_table,
866 Some(Constraint::GeConst {
867 col: ColumnId::new(2),
868 val: last_rebuilt_at.to_value(),
869 }),
870 );
871 if incremental_rebuild(uf_size, table_size, true) {
872 for (i, _) in info.incremental_rebuild_rules.iter().enumerate() {
873 state.incremental.get_or_default(i).push(func);
874 }
875 } else {
876 state.nonincremental.push(func);
877 }
878 }
879 let ts = self.next_ts();
880 for func in state.nonincremental.iter().copied() {
881 scratch.push(self.funcs[func].nonincremental_rebuild_rule);
882 for rule in &self.funcs[func].incremental_rebuild_rules {
883 self.rules[*rule].last_run_at = ts;
884 }
885 }
886 changed |= run_rules_impl(&mut self.db, &mut self.rules, &scratch, ts)?.changed;
887 scratch.clear();
888 let ts = self.next_ts();
889 for (i, funcs) in state.incremental.iter() {
890 for func in funcs.iter().copied() {
891 let info = &mut self.funcs[func];
892 scratch.push(info.incremental_rebuild_rules[i]);
893 self.rules[info.nonincremental_rebuild_rule].last_run_at = ts;
894 }
895 changed |= run_rules_impl(&mut self.db, &mut self.rules, &scratch, ts)?.changed;
896 scratch.clear();
897 }
898 }
899 log::info!("rebuild took {:?}", start.elapsed());
900 Ok(())
901 }
902
903 fn incremental_rebuild_rules(&mut self, table: FunctionId, schema: &[ColumnTy]) -> Vec<RuleId> {
904 schema
905 .iter()
906 .enumerate()
907 .filter_map(|(i, ty)| match ty {
908 ColumnTy::Id => {
909 Some(self.incremental_rebuild_rule(table, schema, ColumnId::from_usize(i)))
910 }
911 ColumnTy::Base(_) => None,
912 })
913 .collect()
914 }
915
916 fn incremental_rebuild_rule(
917 &mut self,
918 table: FunctionId,
919 schema: &[ColumnTy],
920 col: ColumnId,
921 ) -> RuleId {
922 let subsume = self.funcs[table].can_subsume;
923 let table_id = self.funcs[table].table;
924 let uf_table = self.uf_table;
925 let mut rb = self.new_rule(&format!("incremental rebuild {table:?}, {col:?}"), true);
927 rb.set_plan_strategy(PlanStrategy::MinCover);
928 let mut vars = Vec::<QueryEntry>::with_capacity(schema.len());
929 for ty in schema {
930 vars.push(rb.new_var(*ty).into());
931 }
932 let canon_val = rb.new_var(ColumnTy::Id);
933 let subsume_var = subsume.then(|| rb.new_var(ColumnTy::Id));
934 rb.add_atom_with_timestamp_and_func(
935 table_id,
936 Some(table),
937 subsume_var.map(QueryEntry::from),
938 &vars,
939 );
940 rb.add_atom_with_timestamp_and_func(
941 uf_table,
942 None,
943 None,
944 &[vars[col.index()].clone(), canon_val.into()],
945 );
946 rb.set_focus(1); let mut canon = Vec::<QueryEntry>::with_capacity(schema.len());
950 for (i, (var, ty)) in vars.iter().zip(schema.iter()).enumerate() {
951 canon.push(if i == col.index() {
952 canon_val.into()
953 } else if let ColumnTy::Id = ty {
954 rb.lookup_uf(var.clone()).unwrap().into()
955 } else {
956 var.clone()
957 })
958 }
959
960 rb.rebuild_row(table, &vars, &canon, subsume_var);
962 rb.build_internal(None)
963 }
964
965 fn nonincremental_rebuild(&mut self, table: FunctionId, schema: &[ColumnTy]) -> RuleId {
966 let can_subsume = self.funcs[table].can_subsume;
967 let table_id = self.funcs[table].table;
968 let mut rb = self.new_rule(&format!("nonincremental rebuild {table:?}"), false);
969 rb.set_plan_strategy(PlanStrategy::MinCover);
970 let mut vars = Vec::<QueryEntry>::with_capacity(schema.len());
971 for ty in schema {
972 vars.push(rb.new_var(*ty).into());
973 }
974 let subsume_var = can_subsume.then(|| rb.new_var(ColumnTy::Id));
975 rb.add_atom_with_timestamp_and_func(
976 table_id,
977 Some(table),
978 subsume_var.map(QueryEntry::from),
979 &vars,
980 );
981 let mut lhs = SmallVec::<[QueryEntry; 4]>::new();
982 let mut rhs = SmallVec::<[QueryEntry; 4]>::new();
983 let mut canon = Vec::<QueryEntry>::with_capacity(schema.len());
984 for (var, ty) in vars.iter().zip(schema.iter()) {
985 canon.push(if let ColumnTy::Id = ty {
986 lhs.push(var.clone());
987 let canon_var = QueryEntry::from(rb.lookup_uf(var.clone()).unwrap());
988 rhs.push(canon_var.clone());
989 canon_var
990 } else {
991 var.clone()
992 })
993 }
994 rb.check_for_update(&lhs, &rhs).unwrap();
995 rb.rebuild_row(table, &vars, &canon, subsume_var);
996 rb.build_internal(None) }
998
999 pub fn with_execution_state<R>(&self, f: impl FnOnce(&mut ExecutionState<'_>) -> R) -> R {
1005 self.db.with_execution_state(f)
1006 }
1007
1008 pub fn flush_updates(&mut self) -> bool {
1011 let updated = self.db.merge_all();
1012 self.inc_ts();
1013 self.rebuild().unwrap();
1014 updated
1015 }
1016}
1017
1018#[derive(Clone)]
1019struct RuleInfo {
1020 last_run_at: Timestamp,
1021 query: rule::Query,
1022 cached_plan: Option<CachedPlanInfo>,
1023 desc: Arc<str>,
1024}
1025
1026#[derive(Clone)]
1027struct CachedPlanInfo {
1028 plan: Arc<core_relations::CachedPlan>,
1029 atom_mapping: Vec<core_relations::AtomId>,
1032}
1033
1034#[derive(Clone)]
1035struct FunctionInfo {
1036 table: TableId,
1037 schema: Vec<ColumnTy>,
1038 incremental_rebuild_rules: Vec<RuleId>,
1039 nonincremental_rebuild_rule: RuleId,
1040 default_val: DefaultVal,
1041 can_subsume: bool,
1042 name: Arc<str>,
1043}
1044
1045impl FunctionInfo {
1046 fn ret_ty(&self) -> ColumnTy {
1047 self.schema.last().copied().unwrap()
1048 }
1049}
1050
1051#[derive(Copy, Clone)]
1053pub enum DefaultVal {
1054 FreshId,
1056 Fail,
1058 Const(Value),
1060}
1061
1062pub enum MergeFn {
1064 AssertEq,
1066 UnionId,
1068 Primitive(ExternalFunctionId, Vec<MergeFn>),
1071 Function(FunctionId, Vec<MergeFn>),
1074 Old,
1076 New,
1078 Const(Value),
1082}
1083
1084impl MergeFn {
1085 fn fill_deps(
1086 &self,
1087 egraph: &EGraph,
1088 read_deps: &mut IndexSet<TableId>,
1089 write_deps: &mut IndexSet<TableId>,
1090 ) {
1091 use MergeFn::*;
1092 match self {
1093 Primitive(_, args) => {
1094 args.iter()
1095 .for_each(|arg| arg.fill_deps(egraph, read_deps, write_deps));
1096 }
1097 Function(func, args) => {
1098 read_deps.insert(egraph.funcs[*func].table);
1099 write_deps.insert(egraph.funcs[*func].table);
1100 args.iter()
1101 .for_each(|arg| arg.fill_deps(egraph, read_deps, write_deps));
1102 }
1103 UnionId if !egraph.tracing => {
1104 write_deps.insert(egraph.uf_table);
1105 }
1106 UnionId | AssertEq | Old | New | Const(..) => {}
1107 }
1108 }
1109
1110 fn to_callback(
1111 &self,
1112 schema_math: SchemaMath,
1113 function_name: &str,
1114 egraph: &mut EGraph,
1115 ) -> Box<core_relations::MergeFn> {
1116 assert!(
1117 !egraph.tracing || matches!(self, MergeFn::UnionId),
1118 "proofs aren't supported for non-union merge functions"
1119 );
1120
1121 let resolved = self.resolve(function_name, egraph);
1122
1123 Box::new(move |state, cur, new, out| {
1124 let timestamp = new[schema_math.ts_col()];
1125
1126 let mut changed = false;
1127
1128 let ret_val = {
1129 let cur = cur[schema_math.ret_val_col()];
1130 let new = new[schema_math.ret_val_col()];
1131 let out = resolved.run(state, cur, new, timestamp);
1132 changed |= cur != out;
1133 out
1134 };
1135
1136 let subsume = schema_math.subsume.then(|| {
1137 let cur = cur[schema_math.subsume_col()];
1138 let new = new[schema_math.subsume_col()];
1139 let out = combine_subsumed(cur, new);
1140 changed |= cur != out;
1141 out
1142 });
1143
1144 if changed {
1145 out.extend_from_slice(new);
1146 schema_math.write_table_row(
1147 out,
1148 RowVals {
1149 timestamp,
1150 proof: None,
1151 subsume,
1152 ret_val: Some(ret_val),
1153 },
1154 );
1155 }
1156
1157 changed
1158 })
1159 }
1160
1161 fn resolve(&self, function_name: &str, egraph: &mut EGraph) -> ResolvedMergeFn {
1162 match self {
1163 MergeFn::Const(v) => ResolvedMergeFn::Const(*v),
1164 MergeFn::Old => ResolvedMergeFn::Old,
1165 MergeFn::New => ResolvedMergeFn::New,
1166 MergeFn::AssertEq => ResolvedMergeFn::AssertEq {
1167 panic: egraph.new_panic(format!(
1168 "Illegal merge attempted for function {function_name}"
1169 )),
1170 },
1171 MergeFn::UnionId => ResolvedMergeFn::UnionId {
1172 uf_table: egraph.uf_table,
1173 tracing: egraph.tracing,
1174 },
1175 MergeFn::Primitive(prim, args) => ResolvedMergeFn::Primitive {
1180 prim: *prim,
1181 args: args
1182 .iter()
1183 .map(|arg| arg.resolve(function_name, egraph))
1184 .collect::<Vec<_>>(),
1185 panic: egraph.new_panic(format!(
1186 "Merge function for {function_name} primitive call failed"
1187 )),
1188 },
1189 MergeFn::Function(func, args) => {
1190 let func_info = &egraph.funcs[*func];
1191 assert_eq!(
1192 func_info.schema.len(),
1193 args.len() + 1,
1194 "Merge function for {function_name} must match function arity for {}",
1195 func_info.name
1196 );
1197 ResolvedMergeFn::Function {
1198 func: TableAction::new(egraph, *func),
1199 panic: egraph.new_panic(format!(
1200 "Lookup on {} failed in the merge function for {function_name}",
1201 func_info.name
1202 )),
1203 args: args
1204 .iter()
1205 .map(|arg| arg.resolve(function_name, egraph))
1206 .collect::<Vec<_>>(),
1207 }
1208 }
1209 }
1210 }
1211}
1212
1213enum ResolvedMergeFn {
1218 Const(Value),
1219 Old,
1220 New,
1221 AssertEq {
1222 panic: ExternalFunctionId,
1223 },
1224 UnionId {
1225 uf_table: TableId,
1226 tracing: bool,
1227 },
1228 Primitive {
1229 prim: ExternalFunctionId,
1230 args: Vec<ResolvedMergeFn>,
1231 panic: ExternalFunctionId,
1232 },
1233 Function {
1234 func: TableAction,
1235 args: Vec<ResolvedMergeFn>,
1236 panic: ExternalFunctionId,
1237 },
1238}
1239
1240impl ResolvedMergeFn {
1241 fn run(&self, state: &mut ExecutionState, cur: Value, new: Value, ts: Value) -> Value {
1242 match self {
1243 ResolvedMergeFn::Const(v) => *v,
1244 ResolvedMergeFn::Old => cur,
1245 ResolvedMergeFn::New => new,
1246 ResolvedMergeFn::AssertEq { panic } => {
1247 if cur != new {
1248 let res = state.call_external_func(*panic, &[]);
1249 assert_eq!(res, None);
1250 }
1251 cur
1252 }
1253 ResolvedMergeFn::UnionId { uf_table, tracing } => {
1254 if cur != new && !tracing {
1255 state.stage_insert(*uf_table, &[cur, new, ts]);
1258 std::cmp::min(cur, new)
1261 } else {
1262 cur
1263 }
1264 }
1265 ResolvedMergeFn::Primitive { prim, args, panic } => {
1270 let args = args
1271 .iter()
1272 .map(|arg| arg.run(state, cur, new, ts))
1273 .collect::<Vec<_>>();
1274
1275 match state.call_external_func(*prim, &args) {
1276 Some(result) => result,
1277 None => {
1278 let res = state.call_external_func(*panic, &[]);
1279 assert_eq!(res, None);
1280 cur
1281 }
1282 }
1283 }
1284 ResolvedMergeFn::Function { func, args, panic } => {
1285 if cur == new {
1287 return cur;
1288 }
1289
1290 let args = args
1291 .iter()
1292 .map(|arg| arg.run(state, cur, new, ts))
1293 .collect::<Vec<_>>();
1294
1295 func.lookup(state, &args).unwrap_or_else(|| {
1296 let res = state.call_external_func(*panic, &[]);
1297 assert_eq!(res, None);
1298 cur
1299 })
1300 }
1301 }
1302 }
1303}
1304
1305#[derive(Debug, PartialEq, Eq, Hash)]
1309pub struct TableAction {
1310 table: TableId,
1311 table_math: SchemaMath,
1312 default: Option<MergeVal>,
1313 timestamp: CounterId,
1314 scratch: Vec<Value>,
1315}
1316
1317impl Clone for TableAction {
1318 fn clone(&self) -> Self {
1319 Self {
1320 table: self.table,
1321 table_math: self.table_math,
1322 default: self.default,
1323 timestamp: self.timestamp,
1324 scratch: Vec::new(),
1325 }
1326 }
1327}
1328
1329impl TableAction {
1330 pub fn new(egraph: &EGraph, func: FunctionId) -> TableAction {
1333 assert!(!egraph.tracing, "proofs not supported yet");
1334
1335 let func_info = &egraph.funcs[func];
1336 TableAction {
1337 table: func_info.table,
1338 table_math: SchemaMath {
1339 func_cols: func_info.schema.len(),
1340 subsume: func_info.can_subsume,
1341 tracing: egraph.tracing,
1342 },
1343 default: match &func_info.default_val {
1344 DefaultVal::FreshId => Some(MergeVal::Counter(egraph.id_counter)),
1345 DefaultVal::Fail => None,
1346 DefaultVal::Const(val) => Some(MergeVal::Constant(*val)),
1347 },
1348 timestamp: egraph.timestamp_counter,
1349 scratch: Vec::new(),
1350 }
1351 }
1352
1353 pub fn lookup(&self, state: &mut ExecutionState, key: &[Value]) -> Option<Value> {
1357 match self.default {
1358 Some(default) => {
1359 let timestamp =
1360 MergeVal::Constant(Value::from_usize(state.read_counter(self.timestamp)));
1361 let mut merge_vals = SmallVec::<[MergeVal; 3]>::new();
1362 SchemaMath {
1363 func_cols: 1,
1364 ..self.table_math
1365 }
1366 .write_table_row(
1367 &mut merge_vals,
1368 RowVals {
1369 timestamp,
1370 proof: None,
1371 subsume: self
1372 .table_math
1373 .subsume
1374 .then_some(MergeVal::Constant(NOT_SUBSUMED)),
1375 ret_val: Some(default),
1376 },
1377 );
1378 Some(
1379 state.predict_val(self.table, key, merge_vals.iter().copied())
1380 [self.table_math.ret_val_col()],
1381 )
1382 }
1383 None => state
1384 .get_table(self.table)
1385 .get_row(key)
1386 .map(|row| row.vals[self.table_math.ret_val_col()]),
1387 }
1388 }
1389
1390 pub fn insert(&mut self, state: &mut ExecutionState, row: impl Iterator<Item = Value>) {
1392 let ts = Value::from_usize(state.read_counter(self.timestamp));
1393 self.scratch.clear();
1394 self.scratch.extend(row);
1395 self.table_math.write_table_row(
1396 &mut self.scratch,
1397 RowVals {
1398 timestamp: ts,
1399 proof: None,
1400 subsume: self.table_math.subsume.then_some(NOT_SUBSUMED),
1401 ret_val: None,
1402 },
1403 );
1404 state.stage_insert(self.table, &self.scratch);
1405 }
1406
1407 pub fn remove(&self, state: &mut ExecutionState, key: &[Value]) {
1409 state.stage_remove(self.table, key);
1410 }
1411
1412 pub fn subsume(&mut self, state: &mut ExecutionState, key: impl Iterator<Item = Value>) {
1414 let ts = Value::from_usize(state.read_counter(self.timestamp));
1415 self.scratch.clear();
1416 self.scratch.extend(key);
1417
1418 let ret_val = self
1419 .lookup(state, &self.scratch)
1420 .expect("subsume lookup failed");
1421
1422 self.table_math.write_table_row(
1423 &mut self.scratch,
1424 RowVals {
1425 timestamp: ts,
1426 proof: None,
1427 subsume: Some(SUBSUMED),
1428 ret_val: Some(ret_val),
1429 },
1430 );
1431 state.stage_insert(self.table, &self.scratch);
1432 }
1433}
1434
1435#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1437pub struct UnionAction {
1438 table: TableId,
1439 timestamp: CounterId,
1440}
1441
1442impl UnionAction {
1443 pub fn new(egraph: &EGraph) -> UnionAction {
1446 assert!(!egraph.tracing, "proofs not supported yet");
1447 UnionAction {
1448 table: egraph.uf_table,
1449 timestamp: egraph.timestamp_counter,
1450 }
1451 }
1452
1453 pub fn union(&self, state: &mut ExecutionState, x: Value, y: Value) {
1455 let ts = Value::from_usize(state.read_counter(self.timestamp));
1456 state.stage_insert(self.table, &[x, y, ts]);
1457 }
1458}
1459
1460fn run_rules_impl(
1461 db: &mut Database,
1462 rule_info: &mut DenseIdMapWithReuse<RuleId, RuleInfo>,
1463 rules: &[RuleId],
1464 next_ts: Timestamp,
1465) -> Result<RuleSetReport> {
1466 for rule in rules {
1467 let info = &mut rule_info[*rule];
1468 if info.cached_plan.is_none() {
1469 info.cached_plan = Some(info.query.build_cached_plan(db, &info.desc)?);
1470 }
1471 }
1472 let mut rsb = db.new_rule_set();
1473 for rule in rules {
1474 let info = &mut rule_info[*rule];
1475 let cached_plan = info.cached_plan.as_ref().unwrap();
1476 info.query
1477 .add_rules_from_cached(&mut rsb, info.last_run_at, cached_plan)?;
1478 info.last_run_at = next_ts;
1479 }
1480 let ruleset = rsb.build();
1481 Ok(db.run_rule_set(&ruleset))
1482}
1483
1484#[inline(never)]
1488fn marker_incremental_rebuild<R>(f: impl FnOnce() -> R) -> R {
1489 f()
1490}
1491
1492#[inline(never)]
1493fn marker_nonincremental_rebuild<R>(f: impl FnOnce() -> R) -> R {
1494 f()
1495}
1496
1497pub type SideChannel<T> = Arc<Mutex<Option<T>>>;
1500
1501#[derive(Clone)]
1507struct GetFirstMatch(SideChannel<Vec<Value>>);
1508
1509impl ExternalFunction for GetFirstMatch {
1510 fn invoke(&self, _: &mut core_relations::ExecutionState, args: &[Value]) -> Option<Value> {
1511 let mut guard = self.0.lock().unwrap();
1512 if guard.is_some() {
1513 return None;
1514 }
1515 *guard = Some(args.to_vec());
1516 Some(Value::new(0))
1517 }
1518}
1519
1520struct LazyPanic<F>(Arc<Lazy<String, F>>, SideChannel<String>);
1531
1532impl<F: FnOnce() -> String + Send> ExternalFunction for LazyPanic<F> {
1533 fn invoke(&self, _: &mut core_relations::ExecutionState, args: &[Value]) -> Option<Value> {
1534 assert!(args.is_empty());
1535 let mut guard = self.1.lock().unwrap();
1536 if guard.is_none() {
1537 *guard = Some(Lazy::force(&self.0).clone());
1538 }
1539 None
1540 }
1541}
1542
1543impl<F> Clone for LazyPanic<F> {
1544 fn clone(&self) -> Self {
1545 LazyPanic(self.0.clone(), self.1.clone())
1546 }
1547}
1548
1549#[derive(Clone)]
1554struct Panic(String, SideChannel<String>);
1555
1556impl EGraph {
1557 pub fn new_panic(&mut self, message: String) -> ExternalFunctionId {
1559 *self
1560 .panic_funcs
1561 .entry(message.to_string())
1562 .or_insert_with(|| {
1563 let panic = Panic(message, self.panic_message.clone());
1564 self.db.add_external_function(panic)
1565 })
1566 }
1567
1568 pub fn new_panic_lazy(
1569 &mut self,
1570 message: impl FnOnce() -> String + Send + 'static,
1571 ) -> ExternalFunctionId {
1572 let lazy = Lazy::new(message);
1573 let panic = LazyPanic(Arc::new(lazy), self.panic_message.clone());
1574 self.db.add_external_function(panic)
1575 }
1576}
1577
1578impl ExternalFunction for Panic {
1579 fn invoke(&self, _: &mut core_relations::ExecutionState, args: &[Value]) -> Option<Value> {
1580 assert!(args.is_empty());
1582
1583 let mut guard = self.1.lock().unwrap();
1584 if guard.is_none() {
1585 *guard = Some(self.0.clone());
1586 }
1587 None
1588 }
1589}
1590
1591#[derive(Error, Debug)]
1592enum ProofReconstructionError {
1593 #[error(
1594 "attempting to explain a row without tracing enabled. Try constructing with `EGraph::with_tracing`"
1595 )]
1596 TracingNotEnabled,
1597 #[error("attempting to construct a proof that {term1} = {term2}, but they are not equal")]
1598 EqualityExplanationOfUnequalTerms { term1: String, term2: String },
1599}
1600
1601fn incremental_rebuild(uf_size: usize, table_size: usize, parallel: bool) -> bool {
1604 if parallel {
1605 uf_size <= (table_size / 16)
1606 } else {
1607 uf_size <= (table_size / 8)
1608 }
1609}
1610
1611pub(crate) const SUBSUMED: Value = Value::new_const(1);
1612pub(crate) const NOT_SUBSUMED: Value = Value::new_const(0);
1613fn combine_subsumed(v1: Value, v2: Value) -> Value {
1614 std::cmp::max(v1, v2)
1615}
1616
1617#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1628struct SchemaMath {
1629 tracing: bool,
1631 subsume: bool,
1633 func_cols: usize,
1635}
1636
1637struct RowVals<T> {
1642 timestamp: T,
1644 proof: Option<T>,
1646 subsume: Option<T>,
1648 ret_val: Option<T>,
1651}
1652
1653#[derive(Clone, Debug)]
1655pub struct FunctionRow<'a> {
1656 pub vals: &'a [Value],
1657 pub subsumed: bool,
1658}
1659
1660impl SchemaMath {
1661 fn write_table_row<T: Clone>(
1662 &self,
1663 row: &mut impl HasResizeWith<T>,
1664 RowVals {
1665 timestamp,
1666 proof,
1667 subsume,
1668 ret_val,
1669 }: RowVals<T>,
1670 ) {
1671 row.resize_with(self.table_columns(), || timestamp.clone());
1672 row[self.ts_col()] = timestamp;
1673 if let Some(ret_val) = ret_val {
1674 row[self.ret_val_col()] = ret_val;
1675 }
1676 if let Some(proof_id) = proof {
1677 row[self.proof_id_col()] = proof_id;
1678 } else {
1679 assert!(
1680 !self.tracing,
1681 "proof_id must be provided if tracing is enabled"
1682 );
1683 }
1684 if let Some(subsume) = subsume {
1685 row[self.subsume_col()] = subsume;
1686 } else {
1687 assert!(
1688 !self.subsume,
1689 "subsume flag must be provided if subsumption is enabled"
1690 );
1691 }
1692 }
1693
1694 fn num_keys(&self) -> usize {
1695 self.func_cols - 1
1696 }
1697
1698 fn table_columns(&self) -> usize {
1699 self.func_cols + 1 + if self.tracing { 1 } else { 0 } + if self.subsume { 1 } else { 0 }
1700 }
1701
1702 #[track_caller]
1703 fn proof_id_col(&self) -> usize {
1704 assert!(self.tracing);
1705 self.func_cols + 1
1706 }
1707
1708 fn ret_val_col(&self) -> usize {
1709 self.func_cols - 1
1710 }
1711
1712 fn ts_col(&self) -> usize {
1713 self.func_cols
1714 }
1715
1716 #[track_caller]
1717 fn subsume_col(&self) -> usize {
1718 assert!(self.subsume);
1719 if self.tracing {
1720 self.func_cols + 2
1721 } else {
1722 self.func_cols + 1
1723 }
1724 }
1725}
1726
1727#[derive(Error, Debug)]
1728#[error("Panic: {0}")]
1729struct PanicError(String);
1730
1731trait HasResizeWith<T>:
1734 AsMut<[T]> + AsRef<[T]> + Index<usize, Output = T> + IndexMut<usize, Output = T>
1735{
1736 fn resize_with<F>(&mut self, new_size: usize, f: F)
1737 where
1738 F: FnMut() -> T;
1739}
1740
1741impl<T> HasResizeWith<T> for Vec<T> {
1742 fn resize_with<F>(&mut self, new_size: usize, f: F)
1743 where
1744 F: FnMut() -> T,
1745 {
1746 self.resize_with(new_size, f);
1747 }
1748}
1749
1750impl<T, A: smallvec::Array<Item = T>> HasResizeWith<T> for SmallVec<A> {
1751 fn resize_with<F>(&mut self, new_size: usize, f: F)
1752 where
1753 F: FnMut() -> T,
1754 {
1755 self.resize_with(new_size, f);
1756 }
1757}
1758
1759#[derive(Debug, Default)]
1763pub struct IterationReport {
1764 pub changed: bool,
1765 pub rule_reports: HashMap<String, RuleReport>,
1766 pub search_and_apply_time: Duration,
1767 pub merge_time: Duration,
1768 pub rebuild_time: Duration,
1769}
1770
1771pub use crate::core_relations::RuleReport;