1use super::{
2 phase::{
3 FixpointPolicy, OptimizationReport, PhaseId, PhaseLabel, PhaseOutcome, RunCondition,
4 StopReason,
5 },
6 rule::{ErasedRule, Rule, Transformed},
7 stats::Stats,
8};
9use crate::Operand;
10use graphrecords_utils::aliases::{GrHashMap, GrHashSet};
11use std::{
12 any::{Any, TypeId, type_name},
13 collections::hash_map::DefaultHasher,
14 error::Error,
15 fmt::{self, Display, Formatter},
16 hash::Hasher,
17};
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
20pub enum Direction {
21 TopDown,
22 BottomUp,
23 Manual,
24}
25
26const DIRECTION_ORDER: [Direction; 3] =
27 [Direction::TopDown, Direction::BottomUp, Direction::Manual];
28
29#[derive(Debug)]
30#[non_exhaustive]
31pub struct OptimizerError {
32 misconfigurations: Vec<Misconfiguration>,
33}
34
35impl OptimizerError {
36 #[must_use]
37 pub const fn new(misconfigurations: Vec<Misconfiguration>) -> Self {
38 Self { misconfigurations }
39 }
40
41 #[must_use]
42 pub fn misconfigurations(&self) -> &[Misconfiguration] {
43 &self.misconfigurations
44 }
45}
46
47#[derive(Debug)]
48#[non_exhaustive]
49pub enum Misconfiguration {
50 DuplicatePhase(PhaseId),
51 UnknownPhase {
52 phase: PhaseId,
53 rule: &'static str,
54 },
55 UnknownPhaseReference {
56 phase: PhaseId,
57 reference: PhaseId,
58 },
59 UnknownRuleReference {
60 phase: PhaseId,
61 rule: &'static str,
62 reference: &'static str,
63 registered_elsewhere: Option<PhaseId>,
64 },
65 UnknownExclusion(&'static str),
66 NonExcludable(&'static str),
67 PhaseCycle(Vec<PhaseId>),
68 RuleCycle {
69 phase: PhaseId,
70 rules: Vec<&'static str>,
71 },
72}
73
74impl Display for Misconfiguration {
75 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::DuplicatePhase(phase) => {
78 write!(formatter, "phase {phase:?} is declared more than once")
79 }
80 Self::UnknownPhase { phase, rule } => write!(
81 formatter,
82 "rule `{rule}` targets phase {phase:?}, which is never declared"
83 ),
84 Self::UnknownPhaseReference { phase, reference } => write!(
85 formatter,
86 "phase {phase:?} orders against phase {reference:?}, which is never declared"
87 ),
88 Self::UnknownRuleReference {
89 phase,
90 rule,
91 reference,
92 registered_elsewhere,
93 } => {
94 write!(
95 formatter,
96 "rule `{rule}` in phase {phase:?} orders against `{reference}`, which is "
97 )?;
98
99 match registered_elsewhere {
100 Some(other) => write!(
101 formatter,
102 "not registered in this phase (it is registered in phase {other:?} instead)"
103 ),
104 None => formatter.write_str("never registered"),
105 }
106 }
107 Self::UnknownExclusion(name) => write!(
108 formatter,
109 "exclusion targets rule `{name}`, which is never registered"
110 ),
111 Self::NonExcludable(name) => write!(
112 formatter,
113 "rule `{name}` is marked non-excludable and cannot be excluded"
114 ),
115 Self::PhaseCycle(labels) => {
116 write!(formatter, "phase ordering has a cycle involving {labels:?}")
117 }
118 Self::RuleCycle { phase, rules } => write!(
119 formatter,
120 "rule ordering in phase {phase:?} has a cycle involving {rules:?}"
121 ),
122 }
123 }
124}
125
126impl Display for OptimizerError {
127 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
128 formatter.write_str("optimizer configuration is invalid:")?;
129
130 for misconfiguration in &self.misconfigurations {
131 write!(formatter, "\n- {misconfiguration}")?;
132 }
133
134 Ok(())
135 }
136}
137
138impl Error for OptimizerError {}
139
140struct RuleEntry {
141 identity: TypeId,
142 name: &'static str,
143 operand_type: TypeId,
144 direction: Option<Direction>,
145 before: Vec<RuleIdentity>,
146 after: Vec<RuleIdentity>,
147 excludable: bool,
148 run_if: Option<RunCondition>,
149 rule: Box<dyn Any + Send + Sync>,
150}
151
152struct PendingRule {
153 phase: PhaseId,
154 entry: RuleEntry,
155}
156
157struct RuleIdentity {
158 identity: TypeId,
159 name: &'static str,
160}
161
162struct Phase {
163 id: PhaseId,
164 direction: Direction,
165 policy: FixpointPolicy,
166 run_if: Option<RunCondition>,
167 before: Vec<PhaseId>,
168 after: Vec<PhaseId>,
169 rules: Vec<RuleEntry>,
170}
171
172struct CompiledPhase {
173 id: PhaseId,
174 policy: FixpointPolicy,
175 run_if: Option<RunCondition>,
176 passes: Vec<CompiledPass>,
177}
178
179struct CompiledPass {
180 direction: Direction,
181 buckets: GrHashMap<TypeId, Vec<CompiledRule>>,
182 has_run_conditions: bool,
183}
184
185struct CompiledRule {
186 run_if: Option<RunCondition>,
187 rule: Box<dyn Any + Send + Sync>,
188}
189
190pub struct OptimizerBuilder {
191 phases: Vec<Phase>,
192 rules: Vec<PendingRule>,
193 exclusions: Vec<RuleIdentity>,
194}
195
196impl Default for OptimizerBuilder {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl OptimizerBuilder {
203 #[must_use]
204 pub const fn new() -> Self {
205 Self {
206 phases: Vec::new(),
207 rules: Vec::new(),
208 exclusions: Vec::new(),
209 }
210 }
211
212 pub fn add_phase(&mut self, label: impl PhaseLabel) -> PhaseHandle<'_> {
213 self.phases.push(Phase {
214 id: PhaseId::new(label),
215 direction: Direction::BottomUp,
216 policy: FixpointPolicy::fixpoint(),
217 run_if: None,
218 before: Vec::new(),
219 after: Vec::new(),
220 rules: Vec::new(),
221 });
222
223 let index = self.phases.len() - 1;
224
225 PhaseHandle {
226 builder: self,
227 index,
228 }
229 }
230
231 pub fn add_rule<O: Operand + 'static, R: Rule<O>>(
232 &mut self,
233 phase: impl PhaseLabel,
234 rule: R,
235 ) -> RuleHandle<'_> {
236 let erased: ErasedRule<_> =
237 Box::new(move |operand, session| rule.apply(operand, session.stats()));
238
239 self.rules.push(PendingRule {
240 phase: PhaseId::new(phase),
241 entry: RuleEntry {
242 identity: TypeId::of::<R>(),
243 name: type_name::<R>(),
244 operand_type: TypeId::of::<O>(),
245 direction: None,
246 before: Vec::new(),
247 after: Vec::new(),
248 excludable: true,
249 run_if: None,
250 rule: Box::new(erased),
251 },
252 });
253
254 let index = self.rules.len() - 1;
255
256 RuleHandle {
257 builder: self,
258 index,
259 }
260 }
261
262 pub fn exclude<R: 'static>(&mut self) -> &mut Self {
263 self.exclusions.push(RuleIdentity {
264 identity: TypeId::of::<R>(),
265 name: type_name::<R>(),
266 });
267 self
268 }
269
270 pub fn build(self) -> Result<Optimizer, OptimizerError> {
271 let (phase_index, duplicates) = index_phases(&self.phases);
272 let (phases, unknown_phases) = join_rules(self.phases, self.rules, &phase_index);
273 let (order, ordering_findings) = phase_order(&phases, &phase_index);
274 let reference_findings = validate_rule_references(&phases);
275 let (excluded, exclusion_findings) = resolve_exclusions(self.exclusions, &phases);
276 let (compiled_phases, cycle_findings) = compile_phases(phases, order, &excluded);
277
278 let misconfigurations: Vec<_> = duplicates
279 .into_iter()
280 .chain(unknown_phases)
281 .chain(ordering_findings)
282 .chain(reference_findings)
283 .chain(exclusion_findings)
284 .chain(cycle_findings)
285 .collect();
286
287 if !misconfigurations.is_empty() {
288 return Err(OptimizerError::new(misconfigurations));
289 }
290
291 Ok(Optimizer {
292 phases: compiled_phases,
293 })
294 }
295}
296
297pub struct PhaseHandle<'a> {
298 builder: &'a mut OptimizerBuilder,
299 index: usize,
300}
301
302impl PhaseHandle<'_> {
303 fn phase(&mut self) -> &mut Phase {
304 &mut self.builder.phases[self.index]
305 }
306
307 pub fn direction(&mut self, direction: Direction) -> &mut Self {
308 self.phase().direction = direction;
309 self
310 }
311
312 pub fn policy(&mut self, policy: FixpointPolicy) -> &mut Self {
313 self.phase().policy = policy;
314 self
315 }
316
317 pub fn once(&mut self) -> &mut Self {
318 self.policy(FixpointPolicy::Once)
319 }
320
321 pub fn fixpoint(&mut self) -> &mut Self {
322 self.policy(FixpointPolicy::fixpoint())
323 }
324
325 pub fn before(&mut self, label: impl PhaseLabel) -> &mut Self {
326 self.phase().before.push(PhaseId::new(label));
327 self
328 }
329
330 pub fn after(&mut self, label: impl PhaseLabel) -> &mut Self {
331 self.phase().after.push(PhaseId::new(label));
332 self
333 }
334
335 pub fn run_if(
336 &mut self,
337 condition: impl Fn(&Stats<'_>) -> bool + Send + Sync + 'static,
338 ) -> &mut Self {
339 self.phase().run_if = Some(Box::new(condition));
340 self
341 }
342}
343
344pub struct RuleHandle<'a> {
345 builder: &'a mut OptimizerBuilder,
346 index: usize,
347}
348
349impl RuleHandle<'_> {
350 fn entry(&mut self) -> &mut RuleEntry {
351 &mut self.builder.rules[self.index].entry
352 }
353
354 pub fn direction(&mut self, direction: Direction) -> &mut Self {
355 self.entry().direction = Some(direction);
356 self
357 }
358
359 pub fn label<L: 'static>(&mut self) -> &mut Self {
360 let entry = self.entry();
361
362 entry.identity = TypeId::of::<L>();
363 entry.name = type_name::<L>();
364
365 self
366 }
367
368 pub fn before<R: 'static>(&mut self) -> &mut Self {
369 self.entry().before.push(RuleIdentity {
370 identity: TypeId::of::<R>(),
371 name: type_name::<R>(),
372 });
373 self
374 }
375
376 pub fn after<R: 'static>(&mut self) -> &mut Self {
377 self.entry().after.push(RuleIdentity {
378 identity: TypeId::of::<R>(),
379 name: type_name::<R>(),
380 });
381 self
382 }
383
384 pub fn non_excludable(&mut self) -> &mut Self {
385 self.entry().excludable = false;
386 self
387 }
388
389 pub fn run_if(
390 &mut self,
391 condition: impl Fn(&Stats<'_>) -> bool + Send + Sync + 'static,
392 ) -> &mut Self {
393 self.entry().run_if = Some(Box::new(condition));
394 self
395 }
396}
397
398fn index_phases(phases: &[Phase]) -> (GrHashMap<PhaseId, usize>, Vec<Misconfiguration>) {
399 let mut index = GrHashMap::default();
400 let mut duplicates = Vec::new();
401
402 for (position, phase) in phases.iter().enumerate() {
403 if index.contains_key(&phase.id) {
404 duplicates.push(Misconfiguration::DuplicatePhase(phase.id.clone()));
405 continue;
406 }
407
408 index.insert(phase.id.clone(), position);
409 }
410
411 (index, duplicates)
412}
413
414fn join_rules(
415 mut phases: Vec<Phase>,
416 rules: Vec<PendingRule>,
417 phase_index: &GrHashMap<PhaseId, usize>,
418) -> (Vec<Phase>, Vec<Misconfiguration>) {
419 let mut unknown_phases = Vec::new();
420
421 for pending in rules {
422 match phase_index.get(&pending.phase) {
423 Some(&position) => phases[position].rules.push(pending.entry),
424 None => unknown_phases.push(Misconfiguration::UnknownPhase {
425 phase: pending.phase,
426 rule: pending.entry.name,
427 }),
428 }
429 }
430
431 (phases, unknown_phases)
432}
433
434fn phase_order(
435 phases: &[Phase],
436 phase_index: &GrHashMap<PhaseId, usize>,
437) -> (Option<Vec<usize>>, Vec<Misconfiguration>) {
438 let mut edges = Vec::new();
439 let mut findings = Vec::new();
440
441 for (position, phase) in phases.iter().enumerate() {
442 for reference in &phase.before {
443 match phase_index.get(reference) {
444 Some(&target) => edges.push((position, target)),
445 None => findings.push(Misconfiguration::UnknownPhaseReference {
446 phase: phase.id.clone(),
447 reference: reference.clone(),
448 }),
449 }
450 }
451
452 for reference in &phase.after {
453 match phase_index.get(reference) {
454 Some(&target) => edges.push((target, position)),
455 None => findings.push(Misconfiguration::UnknownPhaseReference {
456 phase: phase.id.clone(),
457 reference: reference.clone(),
458 }),
459 }
460 }
461 }
462
463 match toposort(phases.len(), &edges) {
464 Ok(order) => (Some(order), findings),
465 Err(cycle) => {
466 findings.push(Misconfiguration::PhaseCycle(
467 cycle
468 .into_iter()
469 .map(|position| phases[position].id.clone())
470 .collect(),
471 ));
472
473 (None, findings)
474 }
475 }
476}
477
478fn validate_rule_references(phases: &[Phase]) -> Vec<Misconfiguration> {
479 let mut findings = Vec::new();
480
481 for phase in phases {
482 for (entry_index, entry) in phase.rules.iter().enumerate() {
483 for reference in entry.before.iter().chain(&entry.after) {
484 let registered_here = phase.rules.iter().enumerate().any(|(other_index, other)| {
485 other_index != entry_index && other.identity == reference.identity
486 });
487
488 if registered_here {
489 continue;
490 }
491
492 let registered_elsewhere = phases
493 .iter()
494 .find(|other| {
495 other.id != phase.id
496 && other
497 .rules
498 .iter()
499 .any(|candidate| candidate.identity == reference.identity)
500 })
501 .map(|other| other.id.clone());
502
503 findings.push(Misconfiguration::UnknownRuleReference {
504 phase: phase.id.clone(),
505 rule: entry.name,
506 reference: reference.name,
507 registered_elsewhere,
508 });
509 }
510 }
511 }
512
513 findings
514}
515
516fn resolve_exclusions(
517 exclusions: Vec<RuleIdentity>,
518 phases: &[Phase],
519) -> (GrHashSet<TypeId>, Vec<Misconfiguration>) {
520 let mut excluded = GrHashSet::default();
521 let mut findings = Vec::new();
522
523 for exclusion in exclusions {
524 if !excluded.insert(exclusion.identity) {
525 continue;
526 }
527
528 let matches: Vec<_> = phases
529 .iter()
530 .flat_map(|phase| &phase.rules)
531 .filter(|entry| entry.identity == exclusion.identity)
532 .collect();
533
534 if matches.is_empty() {
535 findings.push(Misconfiguration::UnknownExclusion(exclusion.name));
536 }
537
538 if matches.iter().any(|entry| !entry.excludable) {
539 findings.push(Misconfiguration::NonExcludable(exclusion.name));
540 }
541 }
542
543 (excluded, findings)
544}
545
546fn compile_phases(
547 phases: Vec<Phase>,
548 order: Option<Vec<usize>>,
549 excluded: &GrHashSet<TypeId>,
550) -> (Vec<CompiledPhase>, Vec<Misconfiguration>) {
551 let execution_order = order.unwrap_or_else(|| (0..phases.len()).collect());
552 let mut slots: Vec<_> = phases.into_iter().map(Some).collect();
553
554 let (compiled_phases, findings): (Vec<_>, Vec<_>) = execution_order
555 .into_iter()
556 .map(|index| {
557 let phase = slots[index]
558 .take()
559 .expect("Each phase must appear exactly once in the execution order");
560
561 compile_phase(phase, excluded)
562 })
563 .unzip();
564
565 (compiled_phases, findings.into_iter().flatten().collect())
566}
567
568fn compile_phase(
569 phase: Phase,
570 excluded: &GrHashSet<TypeId>,
571) -> (CompiledPhase, Vec<Misconfiguration>) {
572 let Phase {
573 id,
574 direction: phase_direction,
575 policy,
576 run_if,
577 rules,
578 ..
579 } = phase;
580
581 let mut by_direction: GrHashMap<_, GrHashMap<TypeId, Vec<_>>> = GrHashMap::default();
582
583 for entry in rules {
584 if entry.excludable && excluded.contains(&entry.identity) {
585 continue;
586 }
587
588 let direction = entry.direction.unwrap_or(phase_direction);
589 by_direction
590 .entry(direction)
591 .or_default()
592 .entry(entry.operand_type)
593 .or_default()
594 .push(entry);
595 }
596
597 let mut passes = Vec::new();
598 let mut findings = Vec::new();
599
600 for direction in DIRECTION_ORDER {
601 let Some(direction_buckets) = by_direction.remove(&direction) else {
602 continue;
603 };
604
605 let mut buckets = GrHashMap::default();
606
607 for (operand_type, entries) in direction_buckets {
608 match order_bucket(&id, entries) {
609 Ok(ordered) => {
610 buckets.insert(operand_type, ordered);
611 }
612 Err(cycle_finding) => findings.push(cycle_finding),
613 }
614 }
615
616 let has_run_conditions = buckets.values().flatten().any(|rule| rule.run_if.is_some());
617
618 passes.push(CompiledPass {
619 direction,
620 buckets,
621 has_run_conditions,
622 });
623 }
624
625 (
626 CompiledPhase {
627 id,
628 policy,
629 run_if,
630 passes,
631 },
632 findings,
633 )
634}
635
636fn order_bucket(
637 phase_id: &PhaseId,
638 entries: Vec<RuleEntry>,
639) -> Result<Vec<CompiledRule>, Misconfiguration> {
640 let references: Vec<_> = entries.iter().collect();
641
642 let order = match rule_order(&references) {
643 Ok(order) => order,
644 Err(cycle) => {
645 return Err(Misconfiguration::RuleCycle {
646 phase: phase_id.clone(),
647 rules: cycle.iter().map(|&index| references[index].name).collect(),
648 });
649 }
650 };
651
652 let mut slots: Vec<_> = entries.into_iter().map(Some).collect();
653
654 Ok(order
655 .into_iter()
656 .map(|index| {
657 let entry = slots[index]
658 .take()
659 .expect("Each rule must appear exactly once in the bucket order");
660
661 CompiledRule {
662 run_if: entry.run_if,
663 rule: entry.rule,
664 }
665 })
666 .collect())
667}
668
669pub struct Optimizer {
670 phases: Vec<CompiledPhase>,
671}
672
673impl Optimizer {
674 #[must_use]
675 pub const fn builder() -> OptimizerBuilder {
676 OptimizerBuilder::new()
677 }
678
679 #[must_use]
680 pub const fn none() -> Self {
681 Self { phases: Vec::new() }
682 }
683
684 #[must_use]
685 pub const fn is_empty(&self) -> bool {
686 self.phases.is_empty()
687 }
688
689 pub fn run<'a, O: Operand + Clone + 'static>(&'a self, stats: &'a Stats<'a>, root: &O) -> O {
690 self.run_reported(stats, root).0
691 }
692
693 pub fn run_reported<'a, O: Operand + Clone + 'static>(
694 &'a self,
695 stats: &'a Stats<'a>,
696 root: &O,
697 ) -> (O, OptimizationReport) {
698 let mut current = root.clone();
699 let mut phases = Vec::with_capacity(self.phases.len());
700
701 for phase in &self.phases {
702 let (next, stop) = Self::run_phase(phase, stats, current);
703
704 current = next;
705
706 phases.push(PhaseOutcome {
707 label: phase.id.clone(),
708 stop,
709 });
710 }
711
712 (current, OptimizationReport { phases })
713 }
714
715 fn run_phase<O: Operand + Clone + 'static>(
716 phase: &CompiledPhase,
717 stats: &Stats,
718 current: O,
719 ) -> (O, StopReason) {
720 if phase
721 .run_if
722 .as_ref()
723 .is_some_and(|condition| !condition(stats))
724 {
725 return (current, StopReason::Skipped);
726 }
727
728 if phase.passes.is_empty() {
729 return (current, StopReason::Empty);
730 }
731
732 let enabled = enabled_rules(&phase.passes, stats);
733
734 if let Some(passes_enabled) = &enabled {
735 let any_enabled = passes_enabled.iter().any(|pass_enabled| {
736 pass_enabled.as_ref().is_none_or(|buckets_enabled| {
737 buckets_enabled.values().flatten().any(|&enabled| enabled)
738 })
739 });
740
741 if !any_enabled {
742 return (current, StopReason::Empty);
743 }
744 }
745
746 match phase.policy {
747 FixpointPolicy::Once => {
748 let current = apply_passes(&phase.passes, enabled.as_deref(), current, stats).0;
749 (current, StopReason::CompletedOnce)
750 }
751 FixpointPolicy::Fixpoint { max_iterations } => {
752 let mut current = current;
753 let mut seen: GrHashMap<_, Vec<O>> = GrHashMap::default();
754
755 seen.entry(signature(¤t))
756 .or_default()
757 .push(current.clone());
758
759 for iteration in 1..=max_iterations {
760 let (next, changed) =
761 apply_passes(&phase.passes, enabled.as_deref(), current, stats);
762 current = next;
763
764 if !changed {
765 return (
766 current,
767 StopReason::Converged {
768 iterations: iteration,
769 },
770 );
771 }
772
773 let bucket = seen.entry(signature(¤t)).or_default();
774 if bucket
775 .iter()
776 .any(|plan| current.as_plan_node().dyn_eq(plan.as_plan_node()))
777 {
778 return (
779 current,
780 StopReason::Oscillation {
781 iterations: iteration,
782 },
783 );
784 }
785
786 bucket.push(current.clone());
787 }
788
789 (
790 current,
791 StopReason::IterationLimit {
792 iterations: max_iterations,
793 },
794 )
795 }
796 }
797 }
798}
799
800type EnabledBuckets = GrHashMap<TypeId, Vec<bool>>;
801type EnabledPasses = Vec<Option<EnabledBuckets>>;
802
803fn enabled_rules(passes: &[CompiledPass], stats: &Stats) -> Option<EnabledPasses> {
804 if passes.iter().all(|pass| !pass.has_run_conditions) {
805 return None;
806 }
807
808 Some(
809 passes
810 .iter()
811 .map(|pass| {
812 if !pass.has_run_conditions {
813 return None;
814 }
815
816 Some(
817 pass.buckets
818 .iter()
819 .map(|(&operand_type, rules)| {
820 (
821 operand_type,
822 rules
823 .iter()
824 .map(|rule| {
825 rule.run_if
826 .as_ref()
827 .is_none_or(|condition| condition(stats))
828 })
829 .collect(),
830 )
831 })
832 .collect(),
833 )
834 })
835 .collect(),
836 )
837}
838
839fn apply_passes<O: Operand + Clone + 'static>(
840 passes: &[CompiledPass],
841 enabled: Option<&[Option<EnabledBuckets>]>,
842 mut current: O,
843 stats: &Stats,
844) -> (O, bool) {
845 let mut changed = false;
846
847 for (pass_index, pass) in passes.iter().enumerate() {
848 let session = Session {
849 buckets: &pass.buckets,
850 enabled: enabled.and_then(|passes_enabled| passes_enabled[pass_index].as_ref()),
851 direction: pass.direction,
852 stats,
853 };
854 let transformed = session.optimize(¤t);
855 let (value, was_changed) = transformed.into_parts();
856
857 current = value;
858 changed |= was_changed;
859 }
860
861 (current, changed)
862}
863
864pub struct Session<'a> {
865 buckets: &'a GrHashMap<TypeId, Vec<CompiledRule>>,
866 enabled: Option<&'a EnabledBuckets>,
867 direction: Direction,
868 stats: &'a Stats<'a>,
869}
870
871impl Session<'_> {
872 #[must_use]
873 pub const fn stats(&self) -> &Stats<'_> {
874 self.stats
875 }
876
877 pub fn optimize<O: Operand + Clone + 'static>(&self, operand: &O) -> Transformed<O> {
878 match self.direction {
879 Direction::BottomUp => {
880 let (rebuilt, rebuilt_changed) =
881 operand.context().optimize(operand, self).into_parts();
882 let (applied, applied_changed) = self.apply_rules(rebuilt).into_parts();
883
884 if rebuilt_changed || applied_changed {
885 Transformed::changed(applied)
886 } else {
887 Transformed::unchanged(applied)
888 }
889 }
890 Direction::TopDown => {
891 let (rewritten, rewritten_changed) = self.apply_rules(operand.clone()).into_parts();
892 let (rebuilt, rebuilt_changed) =
893 rewritten.context().optimize(&rewritten, self).into_parts();
894
895 if rewritten_changed || rebuilt_changed {
896 Transformed::changed(rebuilt)
897 } else {
898 Transformed::unchanged(rebuilt)
899 }
900 }
901 Direction::Manual => self.apply_rules(operand.clone()),
902 }
903 }
904
905 fn apply_rules<O: Operand + 'static>(&self, mut operand: O) -> Transformed<O> {
906 let Some(rules) = self.buckets.get(&TypeId::of::<O>()) else {
907 return Transformed::unchanged(operand);
908 };
909
910 let bucket_enabled = self
911 .enabled
912 .and_then(|buckets_enabled| buckets_enabled.get(&TypeId::of::<O>()));
913
914 let mut changed = false;
915
916 for (index, compiled) in rules.iter().enumerate() {
917 if bucket_enabled.is_some_and(|enabled| !enabled[index]) {
918 continue;
919 }
920
921 let rule = compiled
922 .rule
923 .downcast_ref::<ErasedRule<O>>()
924 .expect("Compiled rule must hold an erased rule matching its operand bucket");
925 let (value, was_changed) = rule(operand, self).into_parts();
926
927 operand = value;
928 changed |= was_changed;
929 }
930
931 if changed {
932 Transformed::changed(operand)
933 } else {
934 Transformed::unchanged(operand)
935 }
936 }
937}
938
939fn signature<O: Operand>(operand: &O) -> u64 {
940 let mut hasher = DefaultHasher::new();
941 operand.as_plan_node().dyn_hash(&mut hasher);
942 hasher.finish()
943}
944
945fn rule_order(entries: &[&RuleEntry]) -> Result<Vec<usize>, Vec<usize>> {
946 let mut edges = Vec::new();
947
948 for (index, entry) in entries.iter().enumerate() {
949 for reference in &entry.before {
950 for (other, candidate) in entries.iter().enumerate() {
951 if other != index && candidate.identity == reference.identity {
952 edges.push((index, other));
953 }
954 }
955 }
956
957 for reference in &entry.after {
958 for (other, candidate) in entries.iter().enumerate() {
959 if other != index && candidate.identity == reference.identity {
960 edges.push((other, index));
961 }
962 }
963 }
964 }
965
966 toposort(entries.len(), &edges)
967}
968
969fn toposort(count: usize, edges: &[(usize, usize)]) -> Result<Vec<usize>, Vec<usize>> {
970 let mut indegree = vec![0usize; count];
971 let mut adjacency = vec![Vec::new(); count];
972
973 for &(before, after) in edges {
974 adjacency[before].push(after);
975 indegree[after] += 1;
976 }
977
978 let mut ready: Vec<_> = (0..count).filter(|&index| indegree[index] == 0).collect();
979 let mut order = Vec::with_capacity(count);
980
981 while !ready.is_empty() {
982 let mut position = 0;
983 for candidate in 1..ready.len() {
984 if ready[candidate] < ready[position] {
985 position = candidate;
986 }
987 }
988
989 let index = ready.swap_remove(position);
990 order.push(index);
991
992 for &next in &adjacency[index] {
993 indegree[next] -= 1;
994 if indegree[next] == 0 {
995 ready.push(next);
996 }
997 }
998 }
999
1000 if order.len() == count {
1001 Ok(order)
1002 } else {
1003 Err((0..count)
1004 .filter(|&index| !order.contains(&index) && cycles_back(index, &adjacency))
1005 .collect())
1006 }
1007}
1008
1009fn cycles_back(start: usize, adjacency: &[Vec<usize>]) -> bool {
1010 let mut visited = vec![false; adjacency.len()];
1011 let mut stack = adjacency[start].clone();
1012
1013 while let Some(node) = stack.pop() {
1014 if node == start {
1015 return true;
1016 }
1017
1018 if !visited[node] {
1019 visited[node] = true;
1020 stack.extend(adjacency[node].iter().copied());
1021 }
1022 }
1023
1024 false
1025}