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 rules_slice = rules.rules.as_slice();
218 let matching = partition_in_place(order, |&idx| {
219 let rule = &rules_slice[idx];
220 match self {
221 HasControlFlow::Match(binding_id) => rule.get_constraint(binding_id).is_some(),
222 HasControlFlow::Equal(x, y) => rule.equals.in_same_set(x, y),
223 HasControlFlow::Loop(binding_id) => rule.iterators.contains(&binding_id),
224 }
225 });
226 PartitionResults {
227 any_matched: matching > 0,
228 valid: respect_priority(rules, order, matching),
229 }
230 }
231}
232
233/// As we proceed through sorting a term's rules, the term's binding sites move
234/// through this sequence of states. This state machine helps us avoid doing
235/// the same thing with a binding site more than once in any subtree.
236#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
237enum BindingState {
238 /// Initially, all binding sites are unavailable for evaluation except for
239 /// top-level arguments, constants, and similar.
240 #[default]
241 Unavailable,
242 /// As more binding sites become available, it becomes possible to evaluate
243 /// bindings which depend on those sites.
244 Available,
245 /// Once we've decided a binding is needed in order to make progress in
246 /// matching, we emit a let-binding for it. We shouldn't evaluate it a
247 /// second time, if possible.
248 Emitted,
249 /// We can only match a constraint against a binding site if we can emit it
250 /// first. Afterward, we should not try to match a constraint against that
251 /// site again in the same subtree.
252 Matched,
253}
254
255/// A sort key used to order control-flow candidates in `best_control_flow`.
256#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
257struct Score {
258 // We prefer to match as many rules at once as possible.
259 count: usize,
260 // Break ties by preferring bindings we've already emitted.
261 state: BindingState,
262}
263
264impl Score {
265 /// Recompute this score. Returns whether this is a valid candidate; if
266 /// not, the score may not have been updated and the candidate should
267 /// be removed from further consideration. The `partition` callback is
268 /// evaluated lazily.
269 fn update(
270 &mut self,
271 state: BindingState,
272 partition: impl FnOnce() -> PartitionResults,
273 ) -> bool {
274 // Candidates which have already been matched in this partition must
275 // not be matched again. There's never anything to be gained from
276 // matching a binding site when you're in an evaluation path where you
277 // already know exactly what pattern that binding site matches. And
278 // without this check, we could go into an infinite loop: all rules in
279 // the current partition match the same pattern for this binding site,
280 // so matching on it doesn't reduce the number of rules to check and it
281 // doesn't make more binding sites available.
282 //
283 // Note that equality constraints never make a binding site `Matched`
284 // and are de-duplicated using more complicated equivalence-class
285 // checks instead.
286 if state == BindingState::Matched {
287 return false;
288 }
289 self.state = state;
290
291 // The score is not based solely on how many rules have this
292 // constraint, but on how many such rules can go into the same block
293 // without violating rule priority. This number can grow as higher-
294 // priority rules are removed from the partition, so we can't drop
295 // candidates just because this is zero. If some rule has this
296 // constraint, it will become viable in some later partition.
297 let partition = partition();
298 self.count = partition.valid;
299
300 // Only consider constraints that are present in some rule in the
301 // current partition. Note that as we partition the rule set into
302 // smaller groups, the number of rules which have a particular kind of
303 // constraint can never grow, so a candidate removed here doesn't need
304 // to be examined again in this partition.
305 partition.any_matched
306 }
307}
308
309/// A rule filter ([HasControlFlow]), plus temporary storage for the sort
310/// key used in `best_control_flow` to order these candidates. Keeping the
311/// temporary storage here lets us avoid repeated heap allocations.
312#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
313struct Candidate {
314 score: Score,
315 // Last resort tie-breaker: defer to HasControlFlow order, but prefer
316 // control-flow that sorts earlier.
317 kind: Reverse<HasControlFlow>,
318}
319
320impl Candidate {
321 /// Construct a candidate where the score is not set. The score will need
322 /// to be reset by [Score::update] before use.
323 fn new(kind: HasControlFlow) -> Self {
324 Candidate {
325 score: Score::default(),
326 kind: Reverse(kind),
327 }
328 }
329}
330
331/// A single binding site to check for participation in equality constraints,
332/// plus temporary storage for the score used in `best_control_flow` to order
333/// these candidates. Keeping the temporary storage here lets us avoid repeated
334/// heap allocations.
335#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
336struct EqualCandidate {
337 score: Score,
338 // Last resort tie-breaker: prefer earlier binding sites.
339 source: Reverse<BindingId>,
340}
341
342impl EqualCandidate {
343 /// Construct a candidate where the score is not set. The score will need
344 /// to be reset by [Score::update] before use.
345 fn new(source: BindingId) -> Self {
346 EqualCandidate {
347 score: Score::default(),
348 source: Reverse(source),
349 }
350 }
351}
352
353/// State for a [Decomposition] that needs to be cloned when entering a nested
354/// scope, so that changes in that scope don't affect this one.
355#[derive(Clone, Default)]
356struct ScopedState {
357 /// The state of all binding sites at this point in the tree, indexed by
358 /// [BindingId]. Bindings which become available in nested scopes don't
359 /// magically become available in outer scopes too.
360 ready: Vec<BindingState>,
361 /// The current set of candidates for control flow to add at this point in
362 /// the tree. We can't rely on any match results that might be computed in
363 /// a nested scope, so if we still care about a candidate in the fallback
364 /// case then we need to emit the correct control flow for it again.
365 candidates: Vec<Candidate>,
366 /// The current set of binding sites which participate in equality
367 /// constraints at this point in the tree. We can't rely on any match
368 /// results that might be computed in a nested scope, so if we still care
369 /// about a candidate in the fallback case then we need to emit the correct
370 /// control flow for it again.
371 equal_candidates: Vec<EqualCandidate>,
372 /// Equivalence classes that we've established on the current path from
373 /// the root.
374 equal: DisjointSets<BindingId>,
375}
376
377/// Builder for one [Block] in the tree.
378struct Decomposition<'a> {
379 /// The complete RuleSet, shared across the whole tree.
380 rules: &'a RuleSet,
381 /// Decomposition state that is scoped to the current subtree.
382 scope: ScopedState,
383 /// Accumulator for bindings that should be emitted before the next
384 /// control-flow construct.
385 bind_order: Vec<BindingId>,
386 /// Accumulator for the final Block that we'll return as this subtree.
387 block: Block,
388}
389
390impl<'a> Decomposition<'a> {
391 /// Create a builder for the root [Block].
392 fn new(rules: &'a RuleSet) -> Decomposition<'a> {
393 let mut scope = ScopedState::default();
394 scope.ready.resize(rules.bindings.len(), Default::default());
395 let mut result = Decomposition {
396 rules,
397 scope,
398 bind_order: Default::default(),
399 block: Default::default(),
400 };
401 result.add_bindings();
402 result
403 }
404
405 /// Create a builder for a nested [Block].
406 fn new_block(&mut self) -> Decomposition<'_> {
407 Decomposition {
408 rules: self.rules,
409 scope: self.scope.clone(),
410 bind_order: Default::default(),
411 block: Default::default(),
412 }
413 }
414
415 /// Ensure that every binding site's state reflects its dependencies'
416 /// states. This takes time linear in the number of bindings. Because
417 /// `trie_again` only hash-conses a binding after all its dependencies have
418 /// already been hash-consed, a single in-order pass visits a binding's
419 /// dependencies before visiting the binding itself.
420 fn add_bindings(&mut self) {
421 let mut idx: u16 = 0; // perf: u16 prevents casting usize from .enumerate() to u16
422 for binding in self.rules.bindings.iter() {
423 let binding_id = BindingId::from(idx);
424 idx += 1;
425
426 // We only add these bindings when matching a corresponding
427 // type of control flow, in `make_control_flow`.
428 if matches!(
429 binding,
430 Binding::Iterator { .. }
431 | Binding::MatchVariant { .. }
432 | Binding::ExtractStruct { .. }
433 | Binding::MatchSome { .. }
434 ) {
435 continue;
436 }
437
438 // TODO: proactively put some bindings in `Emitted` state
439 // That makes them visible to the best-binding heuristic, which
440 // prefers to match on already-emitted bindings first. This helps
441 // to sort cheap computations before expensive ones.
442
443 let ready = self.scope.ready.as_slice();
444
445 if ready[binding_id.index()] < BindingState::Available {
446 if binding
447 .sources()
448 .iter()
449 .all(|&source| ready[source.index()] >= BindingState::Available)
450 {
451 self.set_ready(binding_id, BindingState::Available);
452 }
453 }
454 }
455 }
456
457 /// Determines the final evaluation order for the given subset of rules, and
458 /// builds a [Block] representing that order.
459 fn sort(mut self, mut order: &mut [usize]) -> Block {
460 while let Some(best) = self.best_control_flow(order) {
461 // Peel off all rules that have this particular control flow, and
462 // save the rest for the next iteration of the loop.
463 let partition_point = best.partition(self.rules, order).valid;
464 debug_assert!(partition_point > 0);
465 let (this, rest) = order.split_at_mut(partition_point);
466 order = rest;
467
468 // Recursively build the control-flow tree for these rules.
469 let check = self.make_control_flow(best, this);
470 // Note that `make_control_flow` may have added more let-bindings.
471 let bind_order = std::mem::take(&mut self.bind_order);
472 self.block.steps.push(EvalStep { bind_order, check });
473 }
474
475 // At this point, `best_control_flow` says the remaining rules don't
476 // have any control flow left to emit. That could be because there are
477 // no unhandled rules left, or because every candidate for control flow
478 // for the remaining rules has already been matched by some ancestor in
479 // the tree.
480 debug_assert_eq!(self.scope.candidates.len(), 0);
481 // TODO: assert something about self.equal_candidates?
482
483 let rules = self.rules.rules.as_slice();
484 // If we're building a multi-constructor, then there could be multiple
485 // rules with the same left-hand side. We'll evaluate them all, but
486 // to keep the output consistent, first sort by descending priority
487 // and break ties with the order the rules were declared. In non-multi
488 // constructors, there should be at most one rule remaining here.
489 order.sort_unstable_by_key(|&idx| (Reverse(rules[idx].prio), idx));
490 for &idx in order.iter() {
491 let &Rule {
492 pos,
493 result,
494 ref impure,
495 ..
496 } = &rules[idx];
497
498 // Ensure that any impure constructors are called, even if their
499 // results aren't used.
500 for &impure in impure.iter() {
501 self.use_expr(impure);
502 }
503 self.use_expr(result);
504
505 let check = ControlFlow::Return { pos, result };
506 let bind_order = std::mem::take(&mut self.bind_order);
507 self.block.steps.push(EvalStep { bind_order, check });
508 }
509
510 self.block
511 }
512
513 /// Let-bind this binding site and all its dependencies, skipping any
514 /// which are already let-bound. Also skip let-bindings for certain trivial
515 /// expressions which are safe and cheap to evaluate multiple times,
516 /// because that reduces clutter in the generated code.
517 fn use_expr(&mut self, name: BindingId) {
518 if self.scope.ready[name.index()] < BindingState::Emitted {
519 self.set_ready(name, BindingState::Emitted);
520 let binding = &self.rules.bindings[name.index()];
521 for &source in binding.sources() {
522 self.use_expr(source);
523 }
524
525 let should_let_bind = match binding {
526 Binding::ConstInt { .. } => false,
527 Binding::ConstPrim { .. } => false,
528 Binding::Argument { .. } => false,
529 Binding::MatchTuple { .. } => false,
530
531 // Only let-bind variant constructors if they have some fields.
532 // Building a variant with no fields is cheap, but don't
533 // duplicate more complex expressions.
534 Binding::MakeVariant { fields, .. } | Binding::MakeStruct { fields, .. } => {
535 !fields.is_empty()
536 }
537
538 // By default, do let-bind: that's always safe.
539 _ => true,
540 };
541 if should_let_bind {
542 self.bind_order.push(name);
543 }
544 }
545 }
546
547 /// Build one control-flow construct and its subtree for the specified rules.
548 /// The rules in `order` must all have the kind of control-flow named in `best`.
549 fn make_control_flow(&mut self, best: HasControlFlow, order: &mut [usize]) -> ControlFlow {
550 match best {
551 HasControlFlow::Match(source) => {
552 self.use_expr(source);
553 self.add_bindings();
554 let mut arms = Vec::new();
555
556 let get_constraint =
557 |idx: usize| self.rules.rules[idx].get_constraint(source).unwrap();
558
559 // Ensure that identical constraints are grouped together, then
560 // loop over each group.
561 order.sort_unstable_by_key(|&idx| get_constraint(idx));
562 for g in group_by_mut(order, |&a, &b| get_constraint(a) == get_constraint(b)) {
563 // Applying a constraint moves the discriminant from
564 // Emitted to Matched, but only within the constraint's
565 // match arm; later fallthrough cases may need to match
566 // this discriminant again. Since `source` is in the
567 // `Emitted` state in the parent due to the above call
568 // to `use_expr`, calling `add_bindings` again after this
569 // wouldn't change anything.
570 let mut child = self.new_block();
571 child.set_ready(source, BindingState::Matched);
572
573 // Get the constraint for this group, and all of the
574 // binding sites that it introduces.
575 let constraint = get_constraint(g[0]);
576 let bindings = Vec::from_iter(
577 constraint
578 .bindings_for(source)
579 .into_iter()
580 .map(|b| child.rules.find_binding(&b)),
581 );
582
583 let mut changed = false;
584 for &binding in bindings.iter() {
585 if let Some(binding) = binding {
586 // Matching a pattern makes its bindings
587 // available, and also emits code to bind
588 // them.
589 child.set_ready(binding, BindingState::Emitted);
590 changed = true;
591 }
592 }
593
594 // As an optimization, only propagate availability
595 // if we changed any binding's readiness.
596 if changed {
597 child.add_bindings();
598 }
599
600 // Recursively construct a Block for this group of rules.
601 let body = child.sort(g);
602 arms.push(MatchArm {
603 constraint,
604 bindings,
605 body,
606 });
607 }
608
609 ControlFlow::Match { source, arms }
610 }
611
612 HasControlFlow::Equal(a, b) => {
613 // Both sides of the equality test must be evaluated before
614 // the condition can be tested. Go ahead and let-bind them
615 // so they're available without re-evaluation in fall-through
616 // cases.
617 self.use_expr(a);
618 self.use_expr(b);
619 self.add_bindings();
620
621 let mut child = self.new_block();
622 // Never mark binding sites used in equality constraints as
623 // "matched", because either might need to be used again in
624 // a later equality check. Instead record that they're in the
625 // same equivalence class on this path.
626 child.scope.equal.merge(a, b);
627 let body = child.sort(order);
628 ControlFlow::Equal { a, b, body }
629 }
630
631 HasControlFlow::Loop(source) => {
632 // Consuming a multi-term involves two binding sites:
633 // calling the multi-term to get an iterator (the `source`),
634 // and looping over the iterator to get a binding for each
635 // `result`.
636 let result = self
637 .rules
638 .find_binding(&Binding::Iterator { source })
639 .unwrap();
640
641 // We must not let-bind the iterator until we're ready to
642 // consume it, because it can only be consumed once. This also
643 // means that the let-binding for `source` is not actually
644 // reusable after this point, so even though we need to emit
645 // its let-binding here, we pretend we haven't.
646 let base_state = self.scope.ready[source.index()];
647 debug_assert_eq!(base_state, BindingState::Available);
648 self.use_expr(source);
649 self.scope.ready[source.index()] = base_state;
650 self.add_bindings();
651
652 let mut child = self.new_block();
653 child.set_ready(source, BindingState::Matched);
654 child.set_ready(result, BindingState::Emitted);
655 child.add_bindings();
656 let body = child.sort(order);
657 ControlFlow::Loop { result, body }
658 }
659 }
660 }
661
662 /// Advance the given binding to a new state. The new state usually should
663 /// be greater than the existing state; but at the least it must never
664 /// go backward.
665 fn set_ready(&mut self, source: BindingId, state: BindingState) {
666 let old = &mut self.scope.ready[source.index()];
667 debug_assert!(*old <= state);
668
669 // Add candidates for this binding, but only when it first becomes
670 // available.
671 if let BindingState::Unavailable = old {
672 // A binding site can't have all of these kinds of constraint,
673 // and many have none. But `best_control_flow` has to check all
674 // candidates anyway, so let it figure out which (if any) of these
675 // are applicable. It will only check false candidates once on any
676 // partition, removing them from this list immediately.
677 self.scope.candidates.extend([
678 Candidate::new(HasControlFlow::Match(source)),
679 Candidate::new(HasControlFlow::Loop(source)),
680 ]);
681 self.scope
682 .equal_candidates
683 .push(EqualCandidate::new(source));
684 }
685
686 *old = state;
687 }
688
689 /// For the specified set of rules, heuristically choose which control-flow
690 /// will minimize redundant work when the generated code is running.
691 fn best_control_flow(&mut self, order: &mut [usize]) -> Option<HasControlFlow> {
692 // If there are no rules left, none of the candidates will match
693 // anything in the `retain_mut` call below, so short-circuit it.
694 if order.is_empty() {
695 // This is only read in a debug-assert but it's fast so just do it
696 self.scope.candidates.clear();
697 return None;
698 }
699
700 // Remove false candidates, and recompute the candidate score for the
701 // current set of rules in `order`.
702 self.scope.candidates.retain_mut(|candidate| {
703 let kind = candidate.kind.0;
704 let source = match kind {
705 HasControlFlow::Match(source) => source,
706 HasControlFlow::Loop(source) => source,
707 HasControlFlow::Equal(..) => unreachable!(),
708 };
709 let state = self.scope.ready[source.index()];
710 candidate
711 .score
712 .update(state, || kind.partition(self.rules, order))
713 });
714
715 // Find the best normal candidate.
716 let mut best = self.scope.candidates.iter().max().cloned();
717
718 // Equality constraints are more complicated. We need to identify
719 // some pair of binding sites which are constrained to be equal in at
720 // least one rule in the current partition. We do this in two steps.
721 // First, find each single binding site which participates in any
722 // equality constraint in some rule. We compute the best-case `Score`
723 // we could get, if there were another binding site where all the rules
724 // constraining this binding site require it to be equal to that one.
725 self.scope.equal_candidates.retain_mut(|candidate| {
726 let source = candidate.source.0;
727 let state = self.scope.ready[source.index()];
728 candidate.score.update(state, || {
729 let rules = self.rules.rules.as_slice();
730 let matching =
731 partition_in_place(order, |&idx| rules[idx].equals.find(source).is_some());
732 PartitionResults {
733 any_matched: matching > 0,
734 valid: respect_priority(self.rules, order, matching),
735 }
736 })
737 });
738
739 // Now that we know which single binding sites participate in any
740 // equality constraints, we need to find the best pair of binding
741 // sites. Rules that require binding sites `x` and `y` to be equal are
742 // a subset of the intersection of rules constraining `x` and those
743 // constraining `y`. So the upper bound on the number of matching rules
744 // is whichever candidate is smaller.
745 //
746 // Do an O(n log n) sort to put the best single binding sites first.
747 // Then the O(n^2) all-pairs loop can do branch-and-bound style
748 // pruning, breaking out of a loop as soon as the remaining candidates
749 // must all produce worse results than our current best candidate.
750 //
751 // Note that `x` and `y` are reversed, to sort in descending order.
752 self.scope
753 .equal_candidates
754 .sort_unstable_by(|x, y| y.cmp(x));
755
756 let mut equals = self.scope.equal_candidates.iter();
757 while let Some(x) = equals.next() {
758 if Some(&x.score) < best.as_ref().map(|best| &best.score) {
759 break;
760 }
761 let x_id = x.source.0;
762 for y in equals.as_slice().iter() {
763 if Some(&y.score) < best.as_ref().map(|best| &best.score) {
764 break;
765 }
766 let y_id = y.source.0;
767 // If x and y are already in the same path-scoped equivalence
768 // class, then skip this pair because we already emitted this
769 // check or a combination of equivalent checks on this path.
770 if !self.scope.equal.in_same_set(x_id, y_id) {
771 // Sort arguments for consistency.
772 let kind = if x_id < y_id {
773 HasControlFlow::Equal(x_id, y_id)
774 } else {
775 HasControlFlow::Equal(y_id, x_id)
776 };
777 let pair = Candidate {
778 kind: Reverse(kind),
779 score: Score {
780 count: kind.partition(self.rules, order).valid,
781 // Only treat this as already-emitted if
782 // both bindings are.
783 state: x.score.state.min(y.score.state),
784 },
785 };
786 if best.as_ref() < Some(&pair) {
787 best = Some(pair);
788 }
789 }
790 }
791 }
792
793 best.filter(|candidate| candidate.score.count > 0)
794 .map(|candidate| candidate.kind.0)
795 }
796}
797
798/// Places all elements which satisfy the predicate at the beginning of the
799/// slice, and all elements which don't at the end. Returns the number of
800/// elements in the first partition.
801///
802/// This function runs in time linear in the number of elements, and calls
803/// the predicate exactly once per element. If either partition is empty, no
804/// writes will occur in the slice, so it's okay to call this frequently with
805/// predicates that we expect won't match anything.
806fn partition_in_place<T>(xs: &mut [T], mut pred: impl FnMut(&T) -> bool) -> usize {
807 let mut iter = xs.iter_mut();
808 let mut partition_point = 0;
809 while let Some(a) = iter.next() {
810 if pred(a) {
811 partition_point += 1;
812 } else {
813 // `a` belongs in the partition at the end. If there's some later
814 // element `b` that belongs in the partition at the beginning,
815 // swap them. Working backwards from the end establishes the loop
816 // invariant that both ends of the array are partitioned correctly,
817 // and only the middle needs to be checked.
818 while let Some(b) = iter.next_back() {
819 if pred(b) {
820 std::mem::swap(a, b);
821 partition_point += 1;
822 break;
823 }
824 }
825 }
826 }
827 partition_point
828}
829
830fn group_by_mut<T: Eq>(
831 mut xs: &mut [T],
832 mut pred: impl FnMut(&T, &T) -> bool,
833) -> impl Iterator<Item = &mut [T]> {
834 std::iter::from_fn(move || {
835 if xs.is_empty() {
836 None
837 } else {
838 let mid = xs
839 .array_windows()
840 .position(|[a, b]| !pred(a, b))
841 .map_or(xs.len(), |x| x + 1);
842 let slice = std::mem::take(&mut xs);
843 let (group, rest) = slice.split_at_mut(mid);
844 xs = rest;
845 Some(group)
846 }
847 })
848}
849
850#[cfg(test)]
851mod tests {
852 use super::*;
853
854 #[test]
855 fn test_group_mut() {
856 let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2];
857 let mut iter = group_by_mut(slice, |a, b| a == b);
858 assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
859 assert_eq!(iter.next(), Some(&mut [3, 3][..]));
860 assert_eq!(iter.next(), Some(&mut [2, 2, 2][..]));
861 assert_eq!(iter.next(), None);
862 }
863}