cranelift_isle/
serialize.rs

1//! Put "sea of nodes" representation of a `RuleSet` into a sequential order.
2//!
3//! We're trying to satisfy two key constraints on generated code:
4//!
5//! First, we must produce the same result as if we tested the left-hand side
6//! of every rule in descending priority order and picked the first match.
7//! But that would mean a lot of duplicated work since many rules have similar
8//! patterns. We want to evaluate in an order that gets the same answer but
9//! does as little work as possible.
10//!
11//! Second, some ISLE patterns can only be implemented in Rust using a `match`
12//! expression (or various choices of syntactic sugar). Others can only
13//! be implemented as expressions, which can't be evaluated while matching
14//! patterns in Rust. So we need to alternate between pattern matching and
15//! expression evaluation.
16//!
17//! To meet both requirements, we repeatedly partition the set of rules for a
18//! term and build a tree of Rust control-flow constructs corresponding to each
19//! partition. The root of such a tree is a [Block], and [serialize] constructs
20//! it.
21
22use crate::disjointsets::DisjointSets;
23use crate::lexer::Pos;
24use crate::trie_again::{Binding, BindingId, Constraint, Rule, RuleSet};
25use std::cmp::Reverse;
26
27/// Decomposes the rule-set into a tree of [Block]s.
28pub fn serialize(rules: &RuleSet) -> Block {
29    // While building the tree, we need temporary storage to keep track of
30    // different subsets of the rules as we partition them into ever smaller
31    // sets. As long as we're allowed to re-order the rules, we can ensure
32    // that every partition is contiguous; but since we plan to re-order them,
33    // we actually just store indexes into the `RuleSet` to minimize data
34    // movement. The algorithm in this module never duplicates or discards
35    // rules, so the total size of all partitions is exactly the number of
36    // rules. For all the above reasons, we can pre-allocate all the space
37    // we'll need to hold those partitions up front and share it throughout the
38    // tree.
39    //
40    // As an interesting side effect, when the algorithm finishes, this vector
41    // records the order in which rule bodies will be emitted in the generated
42    // Rust. We don't care because we could get the same information from the
43    // built tree, but it may be helpful to think about the intermediate steps
44    // as recursively sorting the rules. It may not be possible to produce the
45    // same order using a comparison sort, and the asymptotic complexity is
46    // probably worse than the O(n log n) of a comparison sort, but it's still
47    // doing sorting of some kind.
48    let mut order = Vec::from_iter(0..rules.rules.len());
49    Decomposition::new(rules).sort(&mut order)
50}
51
52/// A sequence of steps to evaluate in order. Any step may return early, so
53/// steps ordered later can assume the negation of the conditions evaluated in
54/// earlier steps.
55#[derive(Default)]
56pub struct Block {
57    /// Steps to evaluate.
58    pub steps: Vec<EvalStep>,
59}
60
61/// A step to evaluate involves possibly let-binding some expressions, then
62/// executing some control flow construct.
63pub struct EvalStep {
64    /// Before evaluating this case, emit let-bindings in this order.
65    pub bind_order: Vec<BindingId>,
66    /// The control-flow construct to execute at this point.
67    pub check: ControlFlow,
68}
69
70/// What kind of control-flow structure do we need to emit here?
71pub enum ControlFlow {
72    /// Test a binding site against one or more mutually-exclusive patterns and
73    /// branch to the appropriate block if a pattern matches.
74    Match {
75        /// Which binding site are we examining at this point?
76        source: BindingId,
77        /// What patterns do we care about?
78        arms: Vec<MatchArm>,
79    },
80    /// Test whether two binding sites have values which are equal when
81    /// evaluated on the current input.
82    Equal {
83        /// One binding site.
84        a: BindingId,
85        /// The other binding site. To ensure we always generate the same code
86        /// given the same set of ISLE rules, `b` should be strictly greater
87        /// than `a`.
88        b: BindingId,
89        /// If the test succeeds, evaluate this block.
90        body: Block,
91    },
92    /// Evaluate a block once with each value of the given binding site.
93    Loop {
94        /// A binding site of type [Binding::Iterator]. Its source binding site
95        /// must be a multi-extractor or multi-constructor call.
96        result: BindingId,
97        /// What to evaluate with each binding.
98        body: Block,
99    },
100    /// Return a result from the right-hand side of a rule. If we're building a
101    /// multi-constructor then this doesn't actually return, but adds to a list
102    /// of results instead. Otherwise this return stops evaluation before any
103    /// later steps.
104    Return {
105        /// Where was the rule defined that had this right-hand side?
106        pos: Pos,
107        /// What is the result expression which should be returned if this
108        /// rule matched?
109        result: BindingId,
110    },
111}
112
113/// One concrete pattern and the block to evaluate if the pattern matches.
114pub struct MatchArm {
115    /// The pattern to match.
116    pub constraint: Constraint,
117    /// If this pattern matches, it brings these bindings into scope. If a
118    /// binding is unused in this block, then the corresponding position in the
119    /// pattern's bindings may be `None`.
120    pub bindings: Vec<Option<BindingId>>,
121    /// Steps to evaluate if the pattern matched.
122    pub body: Block,
123}
124
125/// Given a set of rules that's been partitioned into two groups, move rules
126/// from the first partition to the second if there are higher-priority rules
127/// in the second group. In the final generated code, we'll check the rules
128/// in the first ("selected") group before any in the second ("deferred")
129/// group. But we need the result to be _as if_ we checked the rules in strict
130/// descending priority order.
131///
132/// When evaluating the relationship between one rule in the selected set and
133/// one rule in the deferred set, there are two cases where we can keep a rule
134/// in the selected set:
135/// 1. The deferred rule is lower priority than the selected rule; or
136/// 2. The two rules don't overlap, so they can't match on the same inputs.
137///
138/// In either case, if the selected rule matches then we know the deferred rule
139/// would not have been the one we wanted anyway; and if it doesn't match then
140/// the fall-through semantics of the code we generate will let us go on to
141/// check the deferred rule.
142///
143/// So a rule can stay in the selected set as long as it's in one of the above
144/// relationships with every rule in the deferred set.
145///
146/// Due to the overlap checking pass which occurs before codegen, we know that
147/// if two rules have the same priority, they do not overlap. So case 1 above
148/// can be expanded to when the deferred rule is lower _or equal_ priority
149/// to the selected rule. This much overlap checking is absolutely necessary:
150/// There are terms where codegen is impossible if we use only the unmodified
151/// case 1 and don't also check case 2.
152///
153/// Aside from the equal-priority case, though, case 2 does not seem to matter
154/// in practice. On the current backends, doing a full overlap check here does
155/// not change the generated code at all. So we don't bother.
156///
157/// Since this function never moves rules from the deferred set to the selected
158/// set, the returned partition-point is always less than or equal to the
159/// initial partition-point.
160fn respect_priority(rules: &RuleSet, order: &mut [usize], partition_point: usize) -> usize {
161    let (selected, deferred) = order.split_at_mut(partition_point);
162
163    if let Some(max_deferred_prio) = deferred.iter().map(|&idx| rules.rules[idx].prio).max() {
164        partition_in_place(selected, |&idx| rules.rules[idx].prio >= max_deferred_prio)
165    } else {
166        // If the deferred set is empty, all selected rules are fine where
167        // they are.
168        partition_point
169    }
170}
171
172/// A query which can be tested against a [Rule] to see if that rule requires
173/// the given kind of control flow around the given binding sites. These
174/// choices correspond to the identically-named variants of [ControlFlow].
175///
176/// The order of these variants is significant, because it's used as a tie-
177/// breaker in the heuristic that picks which control flow to generate next.
178///
179/// - Loops should always be chosen last. If a rule needs to run once for each
180///   value from an iterator, but only if some other condition is true, we
181///   should check the other condition first.
182///
183/// - Sorting concrete [HasControlFlow::Match] constraints first has the effect
184///   of clustering such constraints together, which is not important but means
185///   codegen could theoretically merge the cluster of matches into a single
186///   Rust `match` statement.
187#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
188enum HasControlFlow {
189    /// Find rules which have a concrete pattern constraint on the given
190    /// binding site.
191    Match(BindingId),
192
193    /// Find rules which require both given binding sites to be in the same
194    /// equivalence class.
195    Equal(BindingId, BindingId),
196
197    /// Find rules which must loop over the multiple values of the given
198    /// binding site.
199    Loop(BindingId),
200}
201
202struct PartitionResults {
203    any_matched: bool,
204    valid: usize,
205}
206
207impl HasControlFlow {
208    /// Identify which rules both satisfy this query, and are safe to evaluate
209    /// before all rules that don't satisfy the query, considering rules'
210    /// relative priorities like [respect_priority]. Partition matching rules
211    /// first in `order`. Return the number of rules which are valid with
212    /// respect to priority, as well as whether any rules matched the query at
213    /// all. No ordering is guaranteed within either partition, which allows
214    /// this function to run in linear time. That's fine because later we'll
215    /// recursively sort both partitions.
216    fn partition(self, rules: &RuleSet, order: &mut [usize]) -> PartitionResults {
217        let matching = partition_in_place(order, |&idx| {
218            let rule = &rules.rules[idx];
219            match self {
220                HasControlFlow::Match(binding_id) => rule.get_constraint(binding_id).is_some(),
221                HasControlFlow::Equal(x, y) => rule.equals.in_same_set(x, y),
222                HasControlFlow::Loop(binding_id) => rule.iterators.contains(&binding_id),
223            }
224        });
225        PartitionResults {
226            any_matched: matching > 0,
227            valid: respect_priority(rules, order, matching),
228        }
229    }
230}
231
232/// As we proceed through sorting a term's rules, the term's binding sites move
233/// through this sequence of states. This state machine helps us avoid doing
234/// the same thing with a binding site more than once in any subtree.
235#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
236enum BindingState {
237    /// Initially, all binding sites are unavailable for evaluation except for
238    /// top-level arguments, constants, and similar.
239    #[default]
240    Unavailable,
241    /// As more binding sites become available, it becomes possible to evaluate
242    /// bindings which depend on those sites.
243    Available,
244    /// Once we've decided a binding is needed in order to make progress in
245    /// matching, we emit a let-binding for it. We shouldn't evaluate it a
246    /// second time, if possible.
247    Emitted,
248    /// We can only match a constraint against a binding site if we can emit it
249    /// first. Afterward, we should not try to match a constraint against that
250    /// site again in the same subtree.
251    Matched,
252}
253
254/// A sort key used to order control-flow candidates in `best_control_flow`.
255#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
256struct Score {
257    // We prefer to match as many rules at once as possible.
258    count: usize,
259    // Break ties by preferring bindings we've already emitted.
260    state: BindingState,
261}
262
263impl Score {
264    /// Recompute this score. Returns whether this is a valid candidate; if
265    /// not, the score may not have been updated and the candidate should
266    /// be removed from further consideration. The `partition` callback is
267    /// evaluated lazily.
268    fn update(
269        &mut self,
270        state: BindingState,
271        partition: impl FnOnce() -> PartitionResults,
272    ) -> bool {
273        // Candidates which have already been matched in this partition must
274        // not be matched again. There's never anything to be gained from
275        // matching a binding site when you're in an evaluation path where you
276        // already know exactly what pattern that binding site matches. And
277        // without this check, we could go into an infinite loop: all rules in
278        // the current partition match the same pattern for this binding site,
279        // so matching on it doesn't reduce the number of rules to check and it
280        // doesn't make more binding sites available.
281        //
282        // Note that equality constraints never make a binding site `Matched`
283        // and are de-duplicated using more complicated equivalence-class
284        // checks instead.
285        if state == BindingState::Matched {
286            return false;
287        }
288        self.state = state;
289
290        // The score is not based solely on how many rules have this
291        // constraint, but on how many such rules can go into the same block
292        // without violating rule priority. This number can grow as higher-
293        // priority rules are removed from the partition, so we can't drop
294        // candidates just because this is zero. If some rule has this
295        // constraint, it will become viable in some later partition.
296        let partition = partition();
297        self.count = partition.valid;
298
299        // Only consider constraints that are present in some rule in the
300        // current partition. Note that as we partition the rule set into
301        // smaller groups, the number of rules which have a particular kind of
302        // constraint can never grow, so a candidate removed here doesn't need
303        // to be examined again in this partition.
304        partition.any_matched
305    }
306}
307
308/// A rule filter ([HasControlFlow]), plus temporary storage for the sort
309/// key used in `best_control_flow` to order these candidates. Keeping the
310/// temporary storage here lets us avoid repeated heap allocations.
311#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
312struct Candidate {
313    score: Score,
314    // Last resort tie-breaker: defer to HasControlFlow order, but prefer
315    // control-flow that sorts earlier.
316    kind: Reverse<HasControlFlow>,
317}
318
319impl Candidate {
320    /// Construct a candidate where the score is not set. The score will need
321    /// to be reset by [Score::update] before use.
322    fn new(kind: HasControlFlow) -> Self {
323        Candidate {
324            score: Score::default(),
325            kind: Reverse(kind),
326        }
327    }
328}
329
330/// A single binding site to check for participation in equality constraints,
331/// plus temporary storage for the score used in `best_control_flow` to order
332/// these candidates. Keeping the temporary storage here lets us avoid repeated
333/// heap allocations.
334#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
335struct EqualCandidate {
336    score: Score,
337    // Last resort tie-breaker: prefer earlier binding sites.
338    source: Reverse<BindingId>,
339}
340
341impl EqualCandidate {
342    /// Construct a candidate where the score is not set. The score will need
343    /// to be reset by [Score::update] before use.
344    fn new(source: BindingId) -> Self {
345        EqualCandidate {
346            score: Score::default(),
347            source: Reverse(source),
348        }
349    }
350}
351
352/// State for a [Decomposition] that needs to be cloned when entering a nested
353/// scope, so that changes in that scope don't affect this one.
354#[derive(Clone, Default)]
355struct ScopedState {
356    /// The state of all binding sites at this point in the tree, indexed by
357    /// [BindingId]. Bindings which become available in nested scopes don't
358    /// magically become available in outer scopes too.
359    ready: Vec<BindingState>,
360    /// The current set of candidates for control flow to add at this point in
361    /// the tree. We can't rely on any match results that might be computed in
362    /// a nested scope, so if we still care about a candidate in the fallback
363    /// case then we need to emit the correct control flow for it again.
364    candidates: Vec<Candidate>,
365    /// The current set of binding sites which participate in equality
366    /// constraints at this point in the tree. We can't rely on any match
367    /// results that might be computed in a nested scope, so if we still care
368    /// about a candidate in the fallback case then we need to emit the correct
369    /// control flow for it again.
370    equal_candidates: Vec<EqualCandidate>,
371    /// Equivalence classes that we've established on the current path from
372    /// the root.
373    equal: DisjointSets<BindingId>,
374}
375
376/// Builder for one [Block] in the tree.
377struct Decomposition<'a> {
378    /// The complete RuleSet, shared across the whole tree.
379    rules: &'a RuleSet,
380    /// Decomposition state that is scoped to the current subtree.
381    scope: ScopedState,
382    /// Accumulator for bindings that should be emitted before the next
383    /// control-flow construct.
384    bind_order: Vec<BindingId>,
385    /// Accumulator for the final Block that we'll return as this subtree.
386    block: Block,
387}
388
389impl<'a> Decomposition<'a> {
390    /// Create a builder for the root [Block].
391    fn new(rules: &'a RuleSet) -> Decomposition<'a> {
392        let mut scope = ScopedState::default();
393        scope.ready.resize(rules.bindings.len(), Default::default());
394        let mut result = Decomposition {
395            rules,
396            scope,
397            bind_order: Default::default(),
398            block: Default::default(),
399        };
400        result.add_bindings();
401        result
402    }
403
404    /// Create a builder for a nested [Block].
405    fn new_block(&mut self) -> Decomposition {
406        Decomposition {
407            rules: self.rules,
408            scope: self.scope.clone(),
409            bind_order: Default::default(),
410            block: Default::default(),
411        }
412    }
413
414    /// Ensure that every binding site's state reflects its dependencies'
415    /// states. This takes time linear in the number of bindings. Because
416    /// `trie_again` only hash-conses a binding after all its dependencies have
417    /// already been hash-consed, a single in-order pass visits a binding's
418    /// dependencies before visiting the binding itself.
419    fn add_bindings(&mut self) {
420        for (idx, binding) in self.rules.bindings.iter().enumerate() {
421            // We only add these bindings when matching a corresponding
422            // type of control flow, in `make_control_flow`.
423            if matches!(
424                binding,
425                Binding::Iterator { .. } | Binding::MatchVariant { .. } | Binding::MatchSome { .. }
426            ) {
427                continue;
428            }
429
430            // TODO: proactively put some bindings in `Emitted` state
431            // That makes them visible to the best-binding heuristic, which
432            // prefers to match on already-emitted bindings first. This helps
433            // to sort cheap computations before expensive ones.
434
435            let idx: BindingId = idx.try_into().unwrap();
436            if self.scope.ready[idx.index()] < BindingState::Available {
437                if binding
438                    .sources()
439                    .iter()
440                    .all(|&source| self.scope.ready[source.index()] >= BindingState::Available)
441                {
442                    self.set_ready(idx, BindingState::Available);
443                }
444            }
445        }
446    }
447
448    /// Determines the final evaluation order for the given subset of rules, and
449    /// builds a [Block] representing that order.
450    fn sort(mut self, mut order: &mut [usize]) -> Block {
451        while let Some(best) = self.best_control_flow(order) {
452            // Peel off all rules that have this particular control flow, and
453            // save the rest for the next iteration of the loop.
454            let partition_point = best.partition(self.rules, order).valid;
455            debug_assert!(partition_point > 0);
456            let (this, rest) = order.split_at_mut(partition_point);
457            order = rest;
458
459            // Recursively build the control-flow tree for these rules.
460            let check = self.make_control_flow(best, this);
461            // Note that `make_control_flow` may have added more let-bindings.
462            let bind_order = std::mem::take(&mut self.bind_order);
463            self.block.steps.push(EvalStep { bind_order, check });
464        }
465
466        // At this point, `best_control_flow` says the remaining rules don't
467        // have any control flow left to emit. That could be because there are
468        // no unhandled rules left, or because every candidate for control flow
469        // for the remaining rules has already been matched by some ancestor in
470        // the tree.
471        debug_assert_eq!(self.scope.candidates.len(), 0);
472        // TODO: assert something about self.equal_candidates?
473
474        // If we're building a multi-constructor, then there could be multiple
475        // rules with the same left-hand side. We'll evaluate them all, but
476        // to keep the output consistent, first sort by descending priority
477        // and break ties with the order the rules were declared. In non-multi
478        // constructors, there should be at most one rule remaining here.
479        order.sort_unstable_by_key(|&idx| (Reverse(self.rules.rules[idx].prio), idx));
480        for &idx in order.iter() {
481            let &Rule {
482                pos,
483                result,
484                ref impure,
485                ..
486            } = &self.rules.rules[idx];
487
488            // Ensure that any impure constructors are called, even if their
489            // results aren't used.
490            for &impure in impure.iter() {
491                self.use_expr(impure);
492            }
493            self.use_expr(result);
494
495            let check = ControlFlow::Return { pos, result };
496            let bind_order = std::mem::take(&mut self.bind_order);
497            self.block.steps.push(EvalStep { bind_order, check });
498        }
499
500        self.block
501    }
502
503    /// Let-bind this binding site and all its dependencies, skipping any
504    /// which are already let-bound. Also skip let-bindings for certain trivial
505    /// expressions which are safe and cheap to evaluate multiple times,
506    /// because that reduces clutter in the generated code.
507    fn use_expr(&mut self, name: BindingId) {
508        if self.scope.ready[name.index()] < BindingState::Emitted {
509            self.set_ready(name, BindingState::Emitted);
510            let binding = &self.rules.bindings[name.index()];
511            for &source in binding.sources() {
512                self.use_expr(source);
513            }
514
515            let should_let_bind = match binding {
516                Binding::ConstInt { .. } => false,
517                Binding::ConstPrim { .. } => false,
518                Binding::Argument { .. } => false,
519                Binding::MatchTuple { .. } => false,
520
521                // Only let-bind variant constructors if they have some fields.
522                // Building a variant with no fields is cheap, but don't
523                // duplicate more complex expressions.
524                Binding::MakeVariant { fields, .. } => !fields.is_empty(),
525
526                // By default, do let-bind: that's always safe.
527                _ => true,
528            };
529            if should_let_bind {
530                self.bind_order.push(name);
531            }
532        }
533    }
534
535    /// Build one control-flow construct and its subtree for the specified rules.
536    /// The rules in `order` must all have the kind of control-flow named in `best`.
537    fn make_control_flow(&mut self, best: HasControlFlow, order: &mut [usize]) -> ControlFlow {
538        match best {
539            HasControlFlow::Match(source) => {
540                self.use_expr(source);
541                self.add_bindings();
542                let mut arms = Vec::new();
543
544                let get_constraint =
545                    |idx: usize| self.rules.rules[idx].get_constraint(source).unwrap();
546
547                // Ensure that identical constraints are grouped together, then
548                // loop over each group.
549                order.sort_unstable_by_key(|&idx| get_constraint(idx));
550                for g in group_by_mut(order, |&a, &b| get_constraint(a) == get_constraint(b)) {
551                    // Applying a constraint moves the discriminant from
552                    // Emitted to Matched, but only within the constraint's
553                    // match arm; later fallthrough cases may need to match
554                    // this discriminant again. Since `source` is in the
555                    // `Emitted` state in the parent due to the above call
556                    // to `use_expr`, calling `add_bindings` again after this
557                    // wouldn't change anything.
558                    let mut child = self.new_block();
559                    child.set_ready(source, BindingState::Matched);
560
561                    // Get the constraint for this group, and all of the
562                    // binding sites that it introduces.
563                    let constraint = get_constraint(g[0]);
564                    let bindings = Vec::from_iter(
565                        constraint
566                            .bindings_for(source)
567                            .into_iter()
568                            .map(|b| child.rules.find_binding(&b)),
569                    );
570
571                    let mut changed = false;
572                    for &binding in bindings.iter() {
573                        if let Some(binding) = binding {
574                            // Matching a pattern makes its bindings
575                            // available, and also emits code to bind
576                            // them.
577                            child.set_ready(binding, BindingState::Emitted);
578                            changed = true;
579                        }
580                    }
581
582                    // As an optimization, only propagate availability
583                    // if we changed any binding's readiness.
584                    if changed {
585                        child.add_bindings();
586                    }
587
588                    // Recursively construct a Block for this group of rules.
589                    let body = child.sort(g);
590                    arms.push(MatchArm {
591                        constraint,
592                        bindings,
593                        body,
594                    });
595                }
596
597                ControlFlow::Match { source, arms }
598            }
599
600            HasControlFlow::Equal(a, b) => {
601                // Both sides of the equality test must be evaluated before
602                // the condition can be tested. Go ahead and let-bind them
603                // so they're available without re-evaluation in fall-through
604                // cases.
605                self.use_expr(a);
606                self.use_expr(b);
607                self.add_bindings();
608
609                let mut child = self.new_block();
610                // Never mark binding sites used in equality constraints as
611                // "matched", because either might need to be used again in
612                // a later equality check. Instead record that they're in the
613                // same equivalence class on this path.
614                child.scope.equal.merge(a, b);
615                let body = child.sort(order);
616                ControlFlow::Equal { a, b, body }
617            }
618
619            HasControlFlow::Loop(source) => {
620                // Consuming a multi-term involves two binding sites:
621                // calling the multi-term to get an iterator (the `source`),
622                // and looping over the iterator to get a binding for each
623                // `result`.
624                let result = self
625                    .rules
626                    .find_binding(&Binding::Iterator { source })
627                    .unwrap();
628
629                // We must not let-bind the iterator until we're ready to
630                // consume it, because it can only be consumed once. This also
631                // means that the let-binding for `source` is not actually
632                // reusable after this point, so even though we need to emit
633                // its let-binding here, we pretend we haven't.
634                let base_state = self.scope.ready[source.index()];
635                debug_assert_eq!(base_state, BindingState::Available);
636                self.use_expr(source);
637                self.scope.ready[source.index()] = base_state;
638                self.add_bindings();
639
640                let mut child = self.new_block();
641                child.set_ready(source, BindingState::Matched);
642                child.set_ready(result, BindingState::Emitted);
643                child.add_bindings();
644                let body = child.sort(order);
645                ControlFlow::Loop { result, body }
646            }
647        }
648    }
649
650    /// Advance the given binding to a new state. The new state usually should
651    /// be greater than the existing state; but at the least it must never
652    /// go backward.
653    fn set_ready(&mut self, source: BindingId, state: BindingState) {
654        let old = &mut self.scope.ready[source.index()];
655        debug_assert!(*old <= state);
656
657        // Add candidates for this binding, but only when it first becomes
658        // available.
659        if let BindingState::Unavailable = old {
660            // A binding site can't have all of these kinds of constraint,
661            // and many have none. But `best_control_flow` has to check all
662            // candidates anyway, so let it figure out which (if any) of these
663            // are applicable. It will only check false candidates once on any
664            // partition, removing them from this list immediately.
665            self.scope.candidates.extend([
666                Candidate::new(HasControlFlow::Match(source)),
667                Candidate::new(HasControlFlow::Loop(source)),
668            ]);
669            self.scope
670                .equal_candidates
671                .push(EqualCandidate::new(source));
672        }
673
674        *old = state;
675    }
676
677    /// For the specified set of rules, heuristically choose which control-flow
678    /// will minimize redundant work when the generated code is running.
679    fn best_control_flow(&mut self, order: &mut [usize]) -> Option<HasControlFlow> {
680        // If there are no rules left, none of the candidates will match
681        // anything in the `retain_mut` call below, so short-circuit it.
682        if order.is_empty() {
683            // This is only read in a debug-assert but it's fast so just do it
684            self.scope.candidates.clear();
685            return None;
686        }
687
688        // Remove false candidates, and recompute the candidate score for the
689        // current set of rules in `order`.
690        self.scope.candidates.retain_mut(|candidate| {
691            let kind = candidate.kind.0;
692            let source = match kind {
693                HasControlFlow::Match(source) => source,
694                HasControlFlow::Loop(source) => source,
695                HasControlFlow::Equal(..) => unreachable!(),
696            };
697            let state = self.scope.ready[source.index()];
698            candidate
699                .score
700                .update(state, || kind.partition(self.rules, order))
701        });
702
703        // Find the best normal candidate.
704        let mut best = self.scope.candidates.iter().max().cloned();
705
706        // Equality constraints are more complicated. We need to identify
707        // some pair of binding sites which are constrained to be equal in at
708        // least one rule in the current partition. We do this in two steps.
709        // First, find each single binding site which participates in any
710        // equality constraint in some rule. We compute the best-case `Score`
711        // we could get, if there were another binding site where all the rules
712        // constraining this binding site require it to be equal to that one.
713        self.scope.equal_candidates.retain_mut(|candidate| {
714            let source = candidate.source.0;
715            let state = self.scope.ready[source.index()];
716            candidate.score.update(state, || {
717                let matching = partition_in_place(order, |&idx| {
718                    self.rules.rules[idx].equals.find(source).is_some()
719                });
720                PartitionResults {
721                    any_matched: matching > 0,
722                    valid: respect_priority(self.rules, order, matching),
723                }
724            })
725        });
726
727        // Now that we know which single binding sites participate in any
728        // equality constraints, we need to find the best pair of binding
729        // sites. Rules that require binding sites `x` and `y` to be equal are
730        // a subset of the intersection of rules constraining `x` and those
731        // constraining `y`. So the upper bound on the number of matching rules
732        // is whichever candidate is smaller.
733        //
734        // Do an O(n log n) sort to put the best single binding sites first.
735        // Then the O(n^2) all-pairs loop can do branch-and-bound style
736        // pruning, breaking out of a loop as soon as the remaining candidates
737        // must all produce worse results than our current best candidate.
738        //
739        // Note that `x` and `y` are reversed, to sort in descending order.
740        self.scope
741            .equal_candidates
742            .sort_unstable_by(|x, y| y.cmp(x));
743
744        let mut equals = self.scope.equal_candidates.iter();
745        while let Some(x) = equals.next() {
746            if Some(&x.score) < best.as_ref().map(|best| &best.score) {
747                break;
748            }
749            let x_id = x.source.0;
750            for y in equals.as_slice().iter() {
751                if Some(&y.score) < best.as_ref().map(|best| &best.score) {
752                    break;
753                }
754                let y_id = y.source.0;
755                // If x and y are already in the same path-scoped equivalence
756                // class, then skip this pair because we already emitted this
757                // check or a combination of equivalent checks on this path.
758                if !self.scope.equal.in_same_set(x_id, y_id) {
759                    // Sort arguments for consistency.
760                    let kind = if x_id < y_id {
761                        HasControlFlow::Equal(x_id, y_id)
762                    } else {
763                        HasControlFlow::Equal(y_id, x_id)
764                    };
765                    let pair = Candidate {
766                        kind: Reverse(kind),
767                        score: Score {
768                            count: kind.partition(self.rules, order).valid,
769                            // Only treat this as already-emitted if
770                            // both bindings are.
771                            state: x.score.state.min(y.score.state),
772                        },
773                    };
774                    if best.as_ref() < Some(&pair) {
775                        best = Some(pair);
776                    }
777                }
778            }
779        }
780
781        best.filter(|candidate| candidate.score.count > 0)
782            .map(|candidate| candidate.kind.0)
783    }
784}
785
786/// Places all elements which satisfy the predicate at the beginning of the
787/// slice, and all elements which don't at the end. Returns the number of
788/// elements in the first partition.
789///
790/// This function runs in time linear in the number of elements, and calls
791/// the predicate exactly once per element. If either partition is empty, no
792/// writes will occur in the slice, so it's okay to call this frequently with
793/// predicates that we expect won't match anything.
794fn partition_in_place<T>(xs: &mut [T], mut pred: impl FnMut(&T) -> bool) -> usize {
795    let mut iter = xs.iter_mut();
796    let mut partition_point = 0;
797    while let Some(a) = iter.next() {
798        if pred(a) {
799            partition_point += 1;
800        } else {
801            // `a` belongs in the partition at the end. If there's some later
802            // element `b` that belongs in the partition at the beginning,
803            // swap them. Working backwards from the end establishes the loop
804            // invariant that both ends of the array are partitioned correctly,
805            // and only the middle needs to be checked.
806            while let Some(b) = iter.next_back() {
807                if pred(b) {
808                    std::mem::swap(a, b);
809                    partition_point += 1;
810                    break;
811                }
812            }
813        }
814    }
815    partition_point
816}
817
818fn group_by_mut<T: Eq>(
819    mut xs: &mut [T],
820    mut pred: impl FnMut(&T, &T) -> bool,
821) -> impl Iterator<Item = &mut [T]> {
822    std::iter::from_fn(move || {
823        if xs.is_empty() {
824            None
825        } else {
826            let mid = xs
827                .windows(2)
828                .position(|w| !pred(&w[0], &w[1]))
829                .map_or(xs.len(), |x| x + 1);
830            let slice = std::mem::take(&mut xs);
831            let (group, rest) = slice.split_at_mut(mid);
832            xs = rest;
833            Some(group)
834        }
835    })
836}
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841
842    #[test]
843    fn test_group_mut() {
844        let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2];
845        let mut iter = group_by_mut(slice, |a, b| a == b);
846        assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
847        assert_eq!(iter.next(), Some(&mut [3, 3][..]));
848        assert_eq!(iter.next(), Some(&mut [2, 2, 2][..]));
849        assert_eq!(iter.next(), None);
850    }
851}