blob: b2e13efb6eb175c4c37a74c85cde873ef86cf020 [file] [log] [blame] [edit]
//! Put "sea of nodes" representation of a `RuleSet` into a sequential order.
//!
//! We're trying to satisfy two key constraints on generated code:
//!
//! First, we must produce the same result as if we tested the left-hand side
//! of every rule in descending priority order and picked the first match.
//! But that would mean a lot of duplicated work since many rules have similar
//! patterns. We want to evaluate in an order that gets the same answer but
//! does as little work as possible.
//!
//! Second, some ISLE patterns can only be implemented in Rust using a `match`
//! expression (or various choices of syntactic sugar). Others can only
//! be implemented as expressions, which can't be evaluated while matching
//! patterns in Rust. So we need to alternate between pattern matching and
//! expression evaluation.
//!
//! To meet both requirements, we repeatedly partition the set of rules for a
//! term and build a tree of Rust control-flow constructs corresponding to each
//! partition. The root of such a tree is a [Block], and [serialize] constructs
//! it.
use std::cmp::Reverse;
use crate::disjointsets::DisjointSets;
use crate::lexer::Pos;
use crate::trie_again::{Binding, BindingId, Constraint, Rule, RuleSet};
/// Decomposes the rule-set into a tree of [Block]s.
pub fn serialize(rules: &RuleSet) -> Block {
// While building the tree, we need temporary storage to keep track of
// different subsets of the rules as we partition them into ever smaller
// sets. As long as we're allowed to re-order the rules, we can ensure
// that every partition is contiguous; but since we plan to re-order them,
// we actually just store indexes into the `RuleSet` to minimize data
// movement. The algorithm in this module never duplicates or discards
// rules, so the total size of all partitions is exactly the number of
// rules. For all the above reasons, we can pre-allocate all the space
// we'll need to hold those partitions up front and share it throughout the
// tree.
//
// As an interesting side effect, when the algorithm finishes, this vector
// records the order in which rule bodies will be emitted in the generated
// Rust. We don't care because we could get the same information from the
// built tree, but it may be helpful to think about the intermediate steps
// as recursively sorting the rules. It may not be possible to produce the
// same order using a comparison sort, and the asymptotic complexity is
// probably worse than the O(n log n) of a comparison sort, but it's still
// doing sorting of some kind.
let mut order = Vec::from_iter(0..rules.rules.len());
Decomposition::new(rules).sort(&mut order)
}
/// A sequence of steps to evaluate in order. Any step may return early, so
/// steps ordered later can assume the negation of the conditions evaluated in
/// earlier steps.
#[derive(Default)]
pub struct Block {
/// Steps to evaluate.
pub steps: Vec<EvalStep>,
}
/// A step to evaluate involves possibly let-binding some expressions, then
/// executing some control flow construct.
pub struct EvalStep {
/// Before evaluating this case, emit let-bindings in this order.
pub bind_order: Vec<BindingId>,
/// The control-flow construct to execute at this point.
pub check: ControlFlow,
}
/// What kind of control-flow structure do we need to emit here?
pub enum ControlFlow {
/// Test a binding site against one or more mutually-exclusive patterns and
/// branch to the appropriate block if a pattern matches.
Match {
/// Which binding site are we examining at this point?
source: BindingId,
/// What patterns do we care about?
arms: Vec<MatchArm>,
},
/// Test whether two binding sites have values which are equal when
/// evaluated on the current input.
Equal {
/// One binding site.
a: BindingId,
/// The other binding site. To ensure we always generate the same code
/// given the same set of ISLE rules, `b` should be strictly greater
/// than `a`.
b: BindingId,
/// If the test succeeds, evaluate this block.
body: Block,
},
/// Evaluate a block once with each value of the given binding site.
Loop {
/// A binding site of type [Binding::Iterator]. Its source binding site
/// must be a multi-extractor or multi-constructor call.
result: BindingId,
/// What to evaluate with each binding.
body: Block,
},
/// Return a result from the right-hand side of a rule. If we're building a
/// multi-constructor then this doesn't actually return, but adds to a list
/// of results instead. Otherwise this return stops evaluation before any
/// later steps.
Return {
/// Where was the rule defined that had this right-hand side?
pos: Pos,
/// What is the result expression which should be returned if this
/// rule matched?
result: BindingId,
},
}
/// One concrete pattern and the block to evaluate if the pattern matches.
pub struct MatchArm {
/// The pattern to match.
pub constraint: Constraint,
/// If this pattern matches, it brings these bindings into scope. If a
/// binding is unused in this block, then the corresponding position in the
/// pattern's bindings may be `None`.
pub bindings: Vec<Option<BindingId>>,
/// Steps to evaluate if the pattern matched.
pub body: Block,
}
/// Given a set of rules that's been partitioned into two groups, move rules
/// from the first partition to the second if there are higher-priority rules
/// in the second group. In the final generated code, we'll check the rules
/// in the first ("selected") group before any in the second ("deferred")
/// group. But we need the result to be _as if_ we checked the rules in strict
/// descending priority order.
///
/// When evaluating the relationship between one rule in the selected set and
/// one rule in the deferred set, there are two cases where we can keep a rule
/// in the selected set:
/// 1. The deferred rule is lower priority than the selected rule; or
/// 2. The two rules don't overlap, so they can't match on the same inputs.
///
/// In either case, if the selected rule matches then we know the deferred rule
/// would not have been the one we wanted anyway; and if it doesn't match then
/// the fall-through semantics of the code we generate will let us go on to
/// check the deferred rule.
///
/// So a rule can stay in the selected set as long as it's in one of the above
/// relationships with every rule in the deferred set.
///
/// Due to the overlap checking pass which occurs before codegen, we know that
/// if two rules have the same priority, they do not overlap. So case 1 above
/// can be expanded to when the deferred rule is lower _or equal_ priority
/// to the selected rule. This much overlap checking is absolutely necessary:
/// There are terms where codegen is impossible if we use only the unmodified
/// case 1 and don't also check case 2.
///
/// Aside from the equal-priority case, though, case 2 does not seem to matter
/// in practice. On the current backends, doing a full overlap check here does
/// not change the generated code at all. So we don't bother.
///
/// Since this function never moves rules from the deferred set to the selected
/// set, the returned partition-point is always less than or equal to the
/// initial partition-point.
fn respect_priority(rules: &RuleSet, order: &mut [usize], partition_point: usize) -> usize {
let (selected, deferred) = order.split_at_mut(partition_point);
if let Some(max_deferred_prio) = deferred.iter().map(|&idx| rules.rules[idx].prio).max() {
partition_in_place(selected, |&idx| rules.rules[idx].prio >= max_deferred_prio)
} else {
// If the deferred set is empty, all selected rules are fine where
// they are.
partition_point
}
}
/// A query which can be tested against a [Rule] to see if that rule requires
/// the given kind of control flow around the given binding sites. These
/// choices correspond to the identically-named variants of [ControlFlow].
///
/// The order of these variants is significant, because it's used as a tie-
/// breaker in the heuristic that picks which control flow to generate next.
///
/// - Loops should always be chosen last. If a rule needs to run once for each
/// value from an iterator, but only if some other condition is true, we
/// should check the other condition first.
///
/// - Sorting concrete [HasControlFlow::Match] constraints first has the effect
/// of clustering such constraints together, which is not important but means
/// codegen could theoretically merge the cluster of matches into a single
/// Rust `match` statement.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
enum HasControlFlow {
/// Find rules which have a concrete pattern constraint on the given
/// binding site.
Match(BindingId),
/// Find rules which require both given binding sites to be in the same
/// equivalence class.
Equal(BindingId, BindingId),
/// Find rules which must loop over the multiple values of the given
/// binding site.
Loop(BindingId),
}
struct PartitionResults {
any_matched: bool,
valid: usize,
}
impl HasControlFlow {
/// Identify which rules both satisfy this query, and are safe to evaluate
/// before all rules that don't satisfy the query, considering rules'
/// relative priorities like [respect_priority]. Partition matching rules
/// first in `order`. Return the number of rules which are valid with
/// respect to priority, as well as whether any rules matched the query at
/// all. No ordering is guaranteed within either partition, which allows
/// this function to run in linear time. That's fine because later we'll
/// recursively sort both partitions.
fn partition(self, rules: &RuleSet, order: &mut [usize]) -> PartitionResults {
let matching = partition_in_place(order, |&idx| {
let rule = &rules.rules[idx];
match self {
HasControlFlow::Match(binding_id) => rule.get_constraint(binding_id).is_some(),
HasControlFlow::Equal(x, y) => rule.equals.in_same_set(x, y),
HasControlFlow::Loop(binding_id) => rule.iterators.contains(&binding_id),
}
});
PartitionResults {
any_matched: matching > 0,
valid: respect_priority(rules, order, matching),
}
}
}
/// As we proceed through sorting a term's rules, the term's binding sites move
/// through this sequence of states. This state machine helps us avoid doing
/// the same thing with a binding site more than once in any subtree.
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
enum BindingState {
/// Initially, all binding sites are unavailable for evaluation except for
/// top-level arguments, constants, and similar.
#[default]
Unavailable,
/// As more binding sites become available, it becomes possible to evaluate
/// bindings which depend on those sites.
Available,
/// Once we've decided a binding is needed in order to make progress in
/// matching, we emit a let-binding for it. We shouldn't evaluate it a
/// second time, if possible.
Emitted,
/// We can only match a constraint against a binding site if we can emit it
/// first. Afterward, we should not try to match a constraint against that
/// site again in the same subtree.
Matched,
}
/// A sort key used to order control-flow candidates in `best_control_flow`.
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
struct Score {
// We prefer to match as many rules at once as possible.
count: usize,
// Break ties by preferring bindings we've already emitted.
state: BindingState,
}
impl Score {
/// Recompute this score. Returns whether this is a valid candidate; if
/// not, the score may not have been updated and the candidate should
/// be removed from further consideration. The `partition` callback is
/// evaluated lazily.
fn update(
&mut self,
state: BindingState,
partition: impl FnOnce() -> PartitionResults,
) -> bool {
// Candidates which have already been matched in this partition must
// not be matched again. There's never anything to be gained from
// matching a binding site when you're in an evaluation path where you
// already know exactly what pattern that binding site matches. And
// without this check, we could go into an infinite loop: all rules in
// the current partition match the same pattern for this binding site,
// so matching on it doesn't reduce the number of rules to check and it
// doesn't make more binding sites available.
//
// Note that equality constraints never make a binding site `Matched`
// and are de-duplicated using more complicated equivalence-class
// checks instead.
if state == BindingState::Matched {
return false;
}
self.state = state;
// The score is not based solely on how many rules have this
// constraint, but on how many such rules can go into the same block
// without violating rule priority. This number can grow as higher-
// priority rules are removed from the partition, so we can't drop
// candidates just because this is zero. If some rule has this
// constraint, it will become viable in some later partition.
let partition = partition();
self.count = partition.valid;
// Only consider constraints that are present in some rule in the
// current partition. Note that as we partition the rule set into
// smaller groups, the number of rules which have a particular kind of
// constraint can never grow, so a candidate removed here doesn't need
// to be examined again in this partition.
partition.any_matched
}
}
/// A rule filter ([HasControlFlow]), plus temporary storage for the sort
/// key used in `best_control_flow` to order these candidates. Keeping the
/// temporary storage here lets us avoid repeated heap allocations.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Candidate {
score: Score,
// Last resort tie-breaker: defer to HasControlFlow order, but prefer
// control-flow that sorts earlier.
kind: Reverse<HasControlFlow>,
}
impl Candidate {
/// Construct a candidate where the score is not set. The score will need
/// to be reset by [Score::update] before use.
fn new(kind: HasControlFlow) -> Self {
Candidate {
score: Score::default(),
kind: Reverse(kind),
}
}
}
/// A single binding site to check for participation in equality constraints,
/// plus temporary storage for the score used in `best_control_flow` to order
/// these candidates. Keeping the temporary storage here lets us avoid repeated
/// heap allocations.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct EqualCandidate {
score: Score,
// Last resort tie-breaker: prefer earlier binding sites.
source: Reverse<BindingId>,
}
impl EqualCandidate {
/// Construct a candidate where the score is not set. The score will need
/// to be reset by [Score::update] before use.
fn new(source: BindingId) -> Self {
EqualCandidate {
score: Score::default(),
source: Reverse(source),
}
}
}
/// State for a [Decomposition] that needs to be cloned when entering a nested
/// scope, so that changes in that scope don't affect this one.
#[derive(Clone, Default)]
struct ScopedState {
/// The state of all binding sites at this point in the tree, indexed by
/// [BindingId]. Bindings which become available in nested scopes don't
/// magically become available in outer scopes too.
ready: Vec<BindingState>,
/// The current set of candidates for control flow to add at this point in
/// the tree. We can't rely on any match results that might be computed in
/// a nested scope, so if we still care about a candidate in the fallback
/// case then we need to emit the correct control flow for it again.
candidates: Vec<Candidate>,
/// The current set of binding sites which participate in equality
/// constraints at this point in the tree. We can't rely on any match
/// results that might be computed in a nested scope, so if we still care
/// about a candidate in the fallback case then we need to emit the correct
/// control flow for it again.
equal_candidates: Vec<EqualCandidate>,
/// Equivalence classes that we've established on the current path from
/// the root.
equal: DisjointSets<BindingId>,
}
/// Builder for one [Block] in the tree.
struct Decomposition<'a> {
/// The complete RuleSet, shared across the whole tree.
rules: &'a RuleSet,
/// Decomposition state that is scoped to the current subtree.
scope: ScopedState,
/// Accumulator for bindings that should be emitted before the next
/// control-flow construct.
bind_order: Vec<BindingId>,
/// Accumulator for the final Block that we'll return as this subtree.
block: Block,
}
impl<'a> Decomposition<'a> {
/// Create a builder for the root [Block].
fn new(rules: &'a RuleSet) -> Decomposition<'a> {
let mut scope = ScopedState::default();
scope.ready.resize(rules.bindings.len(), Default::default());
let mut result = Decomposition {
rules,
scope,
bind_order: Default::default(),
block: Default::default(),
};
result.add_bindings();
result
}
/// Create a builder for a nested [Block].
fn new_block(&mut self) -> Decomposition {
Decomposition {
rules: self.rules,
scope: self.scope.clone(),
bind_order: Default::default(),
block: Default::default(),
}
}
/// Ensure that every binding site's state reflects its dependencies'
/// states. This takes time linear in the number of bindings. Because
/// `trie_again` only hash-conses a binding after all its dependencies have
/// already been hash-consed, a single in-order pass visits a binding's
/// dependencies before visiting the binding itself.
fn add_bindings(&mut self) {
for (idx, binding) in self.rules.bindings.iter().enumerate() {
// We only add these bindings when matching a corresponding
// type of control flow, in `make_control_flow`.
if matches!(
binding,
Binding::Iterator { .. } | Binding::MatchVariant { .. } | Binding::MatchSome { .. }
) {
continue;
}
// TODO: proactively put some bindings in `Emitted` state
// That makes them visible to the best-binding heuristic, which
// prefers to match on already-emitted bindings first. This helps
// to sort cheap computations before expensive ones.
let idx: BindingId = idx.try_into().unwrap();
if self.scope.ready[idx.index()] < BindingState::Available {
if binding
.sources()
.iter()
.all(|&source| self.scope.ready[source.index()] >= BindingState::Available)
{
self.set_ready(idx, BindingState::Available);
}
}
}
}
/// Determines the final evaluation order for the given subset of rules, and
/// builds a [Block] representing that order.
fn sort(mut self, mut order: &mut [usize]) -> Block {
while let Some(best) = self.best_control_flow(order) {
// Peel off all rules that have this particular control flow, and
// save the rest for the next iteration of the loop.
let partition_point = best.partition(self.rules, order).valid;
debug_assert!(partition_point > 0);
let (this, rest) = order.split_at_mut(partition_point);
order = rest;
// Recursively build the control-flow tree for these rules.
let check = self.make_control_flow(best, this);
// Note that `make_control_flow` may have added more let-bindings.
let bind_order = std::mem::take(&mut self.bind_order);
self.block.steps.push(EvalStep { bind_order, check });
}
// At this point, `best_control_flow` says the remaining rules don't
// have any control flow left to emit. That could be because there are
// no unhandled rules left, or because every candidate for control flow
// for the remaining rules has already been matched by some ancestor in
// the tree.
debug_assert_eq!(self.scope.candidates.len(), 0);
// TODO: assert something about self.equal_candidates?
// If we're building a multi-constructor, then there could be multiple
// rules with the same left-hand side. We'll evaluate them all, but
// to keep the output consistent, first sort by descending priority
// and break ties with the order the rules were declared. In non-multi
// constructors, there should be at most one rule remaining here.
order.sort_unstable_by_key(|&idx| (Reverse(self.rules.rules[idx].prio), idx));
for &idx in order.iter() {
let &Rule {
pos,
result,
ref impure,
..
} = &self.rules.rules[idx];
// Ensure that any impure constructors are called, even if their
// results aren't used.
for &impure in impure.iter() {
self.use_expr(impure);
}
self.use_expr(result);
let check = ControlFlow::Return { pos, result };
let bind_order = std::mem::take(&mut self.bind_order);
self.block.steps.push(EvalStep { bind_order, check });
}
self.block
}
/// Let-bind this binding site and all its dependencies, skipping any
/// which are already let-bound. Also skip let-bindings for certain trivial
/// expressions which are safe and cheap to evaluate multiple times,
/// because that reduces clutter in the generated code.
fn use_expr(&mut self, name: BindingId) {
if self.scope.ready[name.index()] < BindingState::Emitted {
self.set_ready(name, BindingState::Emitted);
let binding = &self.rules.bindings[name.index()];
for &source in binding.sources() {
self.use_expr(source);
}
let should_let_bind = match binding {
Binding::ConstInt { .. } => false,
Binding::ConstPrim { .. } => false,
Binding::Argument { .. } => false,
Binding::MatchTuple { .. } => false,
// Only let-bind variant constructors if they have some fields.
// Building a variant with no fields is cheap, but don't
// duplicate more complex expressions.
Binding::MakeVariant { fields, .. } => !fields.is_empty(),
// By default, do let-bind: that's always safe.
_ => true,
};
if should_let_bind {
self.bind_order.push(name);
}
}
}
/// Build one control-flow construct and its subtree for the specified rules.
/// The rules in `order` must all have the kind of control-flow named in `best`.
fn make_control_flow(&mut self, best: HasControlFlow, order: &mut [usize]) -> ControlFlow {
match best {
HasControlFlow::Match(source) => {
self.use_expr(source);
self.add_bindings();
let mut arms = Vec::new();
let get_constraint =
|idx: usize| self.rules.rules[idx].get_constraint(source).unwrap();
// Ensure that identical constraints are grouped together, then
// loop over each group.
order.sort_unstable_by_key(|&idx| get_constraint(idx));
for g in group_by_mut(order, |&a, &b| get_constraint(a) == get_constraint(b)) {
// Applying a constraint moves the discriminant from
// Emitted to Matched, but only within the constraint's
// match arm; later fallthrough cases may need to match
// this discriminant again. Since `source` is in the
// `Emitted` state in the parent due to the above call
// to `use_expr`, calling `add_bindings` again after this
// wouldn't change anything.
let mut child = self.new_block();
child.set_ready(source, BindingState::Matched);
// Get the constraint for this group, and all of the
// binding sites that it introduces.
let constraint = get_constraint(g[0]);
let bindings = Vec::from_iter(
constraint
.bindings_for(source)
.into_iter()
.map(|b| child.rules.find_binding(&b)),
);
let mut changed = false;
for &binding in bindings.iter() {
if let Some(binding) = binding {
// Matching a pattern makes its bindings
// available, and also emits code to bind
// them.
child.set_ready(binding, BindingState::Emitted);
changed = true;
}
}
// As an optimization, only propagate availability
// if we changed any binding's readiness.
if changed {
child.add_bindings();
}
// Recursively construct a Block for this group of rules.
let body = child.sort(g);
arms.push(MatchArm {
constraint,
bindings,
body,
});
}
ControlFlow::Match { source, arms }
}
HasControlFlow::Equal(a, b) => {
// Both sides of the equality test must be evaluated before
// the condition can be tested. Go ahead and let-bind them
// so they're available without re-evaluation in fall-through
// cases.
self.use_expr(a);
self.use_expr(b);
self.add_bindings();
let mut child = self.new_block();
// Never mark binding sites used in equality constraints as
// "matched", because either might need to be used again in
// a later equality check. Instead record that they're in the
// same equivalence class on this path.
child.scope.equal.merge(a, b);
let body = child.sort(order);
ControlFlow::Equal { a, b, body }
}
HasControlFlow::Loop(source) => {
// Consuming a multi-term involves two binding sites:
// calling the multi-term to get an iterator (the `source`),
// and looping over the iterator to get a binding for each
// `result`.
let result = self
.rules
.find_binding(&Binding::Iterator { source })
.unwrap();
// We must not let-bind the iterator until we're ready to
// consume it, because it can only be consumed once. This also
// means that the let-binding for `source` is not actually
// reusable after this point, so even though we need to emit
// its let-binding here, we pretend we haven't.
let base_state = self.scope.ready[source.index()];
debug_assert_eq!(base_state, BindingState::Available);
self.use_expr(source);
self.scope.ready[source.index()] = base_state;
self.add_bindings();
let mut child = self.new_block();
child.set_ready(source, BindingState::Matched);
child.set_ready(result, BindingState::Emitted);
child.add_bindings();
let body = child.sort(order);
ControlFlow::Loop { result, body }
}
}
}
/// Advance the given binding to a new state. The new state usually should
/// be greater than the existing state; but at the least it must never
/// go backward.
fn set_ready(&mut self, source: BindingId, state: BindingState) {
let old = &mut self.scope.ready[source.index()];
debug_assert!(*old <= state);
// Add candidates for this binding, but only when it first becomes
// available.
if let BindingState::Unavailable = old {
// A binding site can't have all of these kinds of constraint,
// and many have none. But `best_control_flow` has to check all
// candidates anyway, so let it figure out which (if any) of these
// are applicable. It will only check false candidates once on any
// partition, removing them from this list immediately.
self.scope.candidates.extend([
Candidate::new(HasControlFlow::Match(source)),
Candidate::new(HasControlFlow::Loop(source)),
]);
self.scope
.equal_candidates
.push(EqualCandidate::new(source));
}
*old = state;
}
/// For the specified set of rules, heuristically choose which control-flow
/// will minimize redundant work when the generated code is running.
fn best_control_flow(&mut self, order: &mut [usize]) -> Option<HasControlFlow> {
// If there are no rules left, none of the candidates will match
// anything in the `retain_mut` call below, so short-circuit it.
if order.is_empty() {
// This is only read in a debug-assert but it's fast so just do it
self.scope.candidates.clear();
return None;
}
// Remove false candidates, and recompute the candidate score for the
// current set of rules in `order`.
self.scope.candidates.retain_mut(|candidate| {
let kind = candidate.kind.0;
let source = match kind {
HasControlFlow::Match(source) => source,
HasControlFlow::Loop(source) => source,
HasControlFlow::Equal(..) => unreachable!(),
};
let state = self.scope.ready[source.index()];
candidate
.score
.update(state, || kind.partition(self.rules, order))
});
// Find the best normal candidate.
let mut best = self.scope.candidates.iter().max().cloned();
// Equality constraints are more complicated. We need to identify
// some pair of binding sites which are constrained to be equal in at
// least one rule in the current partition. We do this in two steps.
// First, find each single binding site which participates in any
// equality constraint in some rule. We compute the best-case `Score`
// we could get, if there were another binding site where all the rules
// constraining this binding site require it to be equal to that one.
self.scope.equal_candidates.retain_mut(|candidate| {
let source = candidate.source.0;
let state = self.scope.ready[source.index()];
candidate.score.update(state, || {
let matching = partition_in_place(order, |&idx| {
self.rules.rules[idx].equals.find(source).is_some()
});
PartitionResults {
any_matched: matching > 0,
valid: respect_priority(self.rules, order, matching),
}
})
});
// Now that we know which single binding sites participate in any
// equality constraints, we need to find the best pair of binding
// sites. Rules that require binding sites `x` and `y` to be equal are
// a subset of the intersection of rules constraining `x` and those
// constraining `y`. So the upper bound on the number of matching rules
// is whichever candidate is smaller.
//
// Do an O(n log n) sort to put the best single binding sites first.
// Then the O(n^2) all-pairs loop can do branch-and-bound style
// pruning, breaking out of a loop as soon as the remaining candidates
// must all produce worse results than our current best candidate.
//
// Note that `x` and `y` are reversed, to sort in descending order.
self.scope
.equal_candidates
.sort_unstable_by(|x, y| y.cmp(x));
let mut equals = self.scope.equal_candidates.iter();
while let Some(x) = equals.next() {
if Some(&x.score) < best.as_ref().map(|best| &best.score) {
break;
}
let x_id = x.source.0;
for y in equals.as_slice().iter() {
if Some(&y.score) < best.as_ref().map(|best| &best.score) {
break;
}
let y_id = y.source.0;
// If x and y are already in the same path-scoped equivalence
// class, then skip this pair because we already emitted this
// check or a combination of equivalent checks on this path.
if !self.scope.equal.in_same_set(x_id, y_id) {
// Sort arguments for consistency.
let kind = if x_id < y_id {
HasControlFlow::Equal(x_id, y_id)
} else {
HasControlFlow::Equal(y_id, x_id)
};
let pair = Candidate {
kind: Reverse(kind),
score: Score {
count: kind.partition(self.rules, order).valid,
// Only treat this as already-emitted if
// both bindings are.
state: x.score.state.min(y.score.state),
},
};
if best.as_ref() < Some(&pair) {
best = Some(pair);
}
}
}
}
best.filter(|candidate| candidate.score.count > 0)
.map(|candidate| candidate.kind.0)
}
}
/// Places all elements which satisfy the predicate at the beginning of the
/// slice, and all elements which don't at the end. Returns the number of
/// elements in the first partition.
///
/// This function runs in time linear in the number of elements, and calls
/// the predicate exactly once per element. If either partition is empty, no
/// writes will occur in the slice, so it's okay to call this frequently with
/// predicates that we expect won't match anything.
fn partition_in_place<T>(xs: &mut [T], mut pred: impl FnMut(&T) -> bool) -> usize {
let mut iter = xs.iter_mut();
let mut partition_point = 0;
while let Some(a) = iter.next() {
if pred(a) {
partition_point += 1;
} else {
// `a` belongs in the partition at the end. If there's some later
// element `b` that belongs in the partition at the beginning,
// swap them. Working backwards from the end establishes the loop
// invariant that both ends of the array are partitioned correctly,
// and only the middle needs to be checked.
while let Some(b) = iter.next_back() {
if pred(b) {
std::mem::swap(a, b);
partition_point += 1;
break;
}
}
}
}
partition_point
}
fn group_by_mut<T: Eq>(
mut xs: &mut [T],
mut pred: impl FnMut(&T, &T) -> bool,
) -> impl Iterator<Item = &mut [T]> {
std::iter::from_fn(move || {
if xs.is_empty() {
None
} else {
let mid = xs
.windows(2)
.position(|w| !pred(&w[0], &w[1]))
.map_or(xs.len(), |x| x + 1);
let slice = std::mem::take(&mut xs);
let (group, rest) = slice.split_at_mut(mid);
xs = rest;
Some(group)
}
})
}
#[test]
fn test_group_mut() {
let slice = &mut [1, 1, 1, 3, 3, 2, 2, 2];
let mut iter = group_by_mut(slice, |a, b| a == b);
assert_eq!(iter.next(), Some(&mut [1, 1, 1][..]));
assert_eq!(iter.next(), Some(&mut [3, 3][..]));
assert_eq!(iter.next(), Some(&mut [2, 2, 2][..]));
assert_eq!(iter.next(), None);
}