Skip to main content

rucc_base/
rules.rs

1//! Matching a set of rules against a term.
2//!
3//! Design: `spec/10-backend.md` section 10.2 and `spec/optimizer/13-rewrite-rules.md`. The rules
4//! themselves are rule files, one per rule set, and the automaton they compile into is generated
5//! by `rucc-rules` when the crate that owns the file is built. What is here is the walk over
6//! that automaton, which is the same walk for every rule set and is written once.
7//!
8//! # Why this is at the bottom of the stack
9//!
10//! Two crates match with a generated table and neither can see the other. `rucc-codegen` lowers
11//! IR to machine terms and `rucc-opt` rewrites IR to IR, and a lowering and a rewrite are the
12//! same claim about two terms, so they are the same trie and the same walk. Putting the walk
13//! here rather than in either of them is what keeps that true rather than merely intended, and
14//! it costs nothing: none of this knows what an instruction is, what a value is, or what C is.
15//!
16//! # What a subject is
17//!
18//! A rule matches a term, and the compiler does not have terms: it has a function full of
19//! instructions, and what a pattern is about is one of them and whatever it was computed from.
20//! So the walk is written against [`Subject`], which is the three questions the automaton asks
21//! of whatever it is matching, and a caller answers them out of the IR without building a term
22//! to be thrown away. A test can answer them out of anything at all, which is what the tests at
23//! the bottom of this file do.
24//!
25//! # What a match gives back
26//!
27//! The rule that fired and what its pattern bound, in the order the pattern binds it. The
28//! bindings are positions rather than names because that is what the walk has, and the rule
29//! carries the names for anything that has to say what it did. Building the replacement out of
30//! [`Piece`] belongs to the caller rather than to this file, because what a replacement becomes
31//! is a machine instruction in one crate and an IR instruction in the other, and this module is
32//! about matching.
33//!
34//! # A name written twice
35//!
36//! A pattern may write one name in two places, which is how `x & x` is said. The second place
37//! becomes a branch in [`Node::same`] rather than a hole, and it asks the subject whether the two
38//! are the same thing rather than comparing nodes, because a node is a place and two places can
39//! hold one value. It is a concrete test, so it is tried before the wildcard for the same reason
40//! every other test is: a rule about one value in both operands is more specific than a rule
41//! about any two.
42//!
43//! # Order
44//!
45//! At every node the concrete tests are tried before the branch that takes anything, so a rule
46//! naming an operand is tried before a rule taking whatever is there. That is the maximal munch
47//! `spec/10-backend.md` asks for, and it falls out of the shape of the trie rather than being
48//! sorted for. Among rules that are equally specific the first one written wins.
49//!
50//! The concrete tests are three kinds of question and they are asked in this order: the head of
51//! the term, then its value as a constant, then whether it is what an earlier binding took.
52//! `spec/optimizer/36-lowering-and-isel.md` section 36.5 asks that the order be stated rather
53//! than left to be read out of what the matcher does, so it is stated here, next to the walk that
54//! applies it. It decides nothing in any rule set shipped today, because deciding something would
55//! need one node to ask two kinds of question about one place and none does, which is a number
56//! `rucc-rules` prints in the header of every table it generates.
57//!
58//! # Finding a branch
59//!
60//! A term has one head and a constant has one value, so at most one head branch and at most one
61//! value branch can match, and the two lists are sorted by the thing they are asked about. That
62//! makes finding the branch a binary search rather than a walk over the node, which is the
63//! difference section 36.5 is about: the widest node of the x86-64 rule set has a hundred and
64//! sixty seven heads on it, and the selector reaches that node once for every instruction in the
65//! program. A repeat of an earlier binding is not searchable, because two of them can hold the
66//! same value, so those stay in the order the rules were written and there are never many.
67//!
68//! A guard is part of deciding whether a rule fires, so a rule whose guard is false is a rule
69//! that did not match, and the walk carries on looking rather than giving up. What that costs is
70//! the search from where the guard failed, which is the price of a guard being allowed to be
71//! about the values rather than only about the shape.
72//!
73//! Two rules can end at the same node when the earlier one has a guard, which is how one pattern
74//! gets a different answer for different constants. They are tried in the order the rule file
75//! writes them and the first whose guard holds fires.
76
77/// The bits of a term the automaton asks about.
78///
79/// A node is whatever the thing doing the matching calls one of its terms: an IR value, an index
80/// into an arena, a pointer. It has to be cheap to copy because the walk keeps a stack of them.
81pub trait Subject {
82    /// What this subject calls one of its terms.
83    type Node: Copy;
84
85    /// The head of a term and how many arguments it has, or nothing if the term is not an
86    /// application. An IR instruction answers with its opcode and its width, spelled the way the
87    /// rule file spells it.
88    fn head(&self, node: Self::Node) -> Option<(&str, usize)>;
89
90    /// One argument of a term, counted from zero. Only ever asked for an argument the answer to
91    /// [`Subject::head`] said was there.
92    fn arg(&self, node: Self::Node, index: usize) -> Self::Node;
93
94    /// The value of a term that is a constant, or nothing if it is not one. This is what a
95    /// pattern matching a literal is asking, and what a guard reads.
96    fn int(&self, node: Self::Node) -> Option<i128>;
97
98    /// Whether two terms are the same thing, which is what a pattern that writes one name in two
99    /// places is asking.
100    ///
101    /// This is a question for the subject rather than something the walk can answer by comparing
102    /// nodes, because a node is a place and two places can hold one value. In
103    /// `(and.i32 (value.i32 x) (value.i32 x))` the two operands are operand zero and operand
104    /// one, which are different places, and what the rule wants to know is whether the same
105    /// value is in both. A subject that cannot tell may answer `false`, which costs the rule a
106    /// match it could have had and never gives it one it should not.
107    fn same(&self, a: Self::Node, b: Self::Node) -> bool;
108}
109
110/// One node of the trie over the patterns.
111///
112/// The branches are held by the kind of question they ask rather than in one list, which is what
113/// lets the two that can be searched be searched.
114#[derive(Debug, Clone, Copy)]
115pub struct Node {
116    /// The branches taken on the head of the subterm, as the [`Node::key`] of the name, the name,
117    /// how many arguments it takes, and where to go. Sorted by the first three, which is what
118    /// [`Node::branch`] needs and is the same order as sorting by the name and the count.
119    pub heads: &'static [(u128, &'static str, usize, u32)],
120    /// The branches taken on the value of a subterm that is a constant, sorted by the value.
121    pub ints: &'static [(i128, u32)],
122    /// The branches taken when the subterm is the same thing as a binding this pattern already
123    /// made, named by which binding it is. A pattern writes one where it writes a name for the
124    /// second time, so this is how `x & x` is told apart from `x & y`. In the order the rules
125    /// were written, because two of them can match one subterm.
126    pub same: &'static [(usize, u32)],
127    /// The branch that takes anything, and the name the first rule to reach it gave that hole.
128    pub wildcard: Option<(&'static str, u32)>,
129    /// The rules that end here, in the order the rule file writes them. The first whose guard
130    /// holds is the one that fires, so every one of them but the last has a guard, which the rule
131    /// compiler checks.
132    pub accept: &'static [u32],
133}
134
135impl Node {
136    /// The branch for a term with this head and this many arguments, if the node has one.
137    ///
138    /// A binary search, which is the whole point of the list being sorted. At most one branch can
139    /// answer, so nothing about which rule fires depends on the list being in this order rather
140    /// than in the order the rules were written.
141    ///
142    /// Each step compares the keys first and only compares the names when the keys agree, which
143    /// for names no longer than sixteen bytes is only on the branch being looked for. Comparing
144    /// the names at every step called `memcmp` at every step, and that was most of what finding a
145    /// branch cost.
146    #[must_use]
147    pub fn branch(&self, head: &str, arity: usize) -> Option<u32> {
148        // Most nodes have no branch on a head or only the one, two thirds and a fifth of the nodes
149        // of the simplifier's table, and the walk asks every node it passes. For those, making the
150        // key copied the name only to compare it against nothing, or against one name that
151        // comparing the two directly answers as well. tamnd/rucc#3052.
152        match self.heads {
153            [] => return None,
154            [(_, name, count, next)] => return (*count == arity && *name == head).then_some(*next),
155            _ => {}
156        }
157        // The same number as `Node::key`, made with one copy rather than a byte at a time.
158        let mut bytes = [0; 16];
159        let take = head.len().min(16);
160        bytes[..take].copy_from_slice(&head.as_bytes()[..take]);
161        let key = u128::from_be_bytes(bytes);
162        let found = self
163            .heads
164            .binary_search_by(|(first, have, count, _)| {
165                first.cmp(&key).then_with(|| have.cmp(&head)).then(count.cmp(&arity))
166            })
167            .ok()?;
168        Some(self.heads[found].3)
169    }
170
171    /// The first sixteen bytes of a name as one number, padded with zeros, which orders the way
172    /// the names do.
173    ///
174    /// Reading the bytes most significant first makes comparing two of these the same as
175    /// comparing the bytes one at a time. A name never holds a zero byte, so the padding sorts
176    /// below every byte a name does hold and a name that runs out first is the smaller one, as it
177    /// should be. Two names with one key are only known to be equal when neither is longer than
178    /// sixteen bytes, which is why [`Node::branch`] compares the names as well. A table computes
179    /// the key of each of its names when it is compiled.
180    #[must_use]
181    pub const fn key(name: &str) -> u128 {
182        let bytes = name.as_bytes();
183        let mut key = 0;
184        let mut at = 0;
185        while at < 16 {
186            key <<= 8;
187            if at < bytes.len() {
188                key |= bytes[at] as u128;
189            }
190            at += 1;
191        }
192        key
193    }
194
195    /// The branch for a constant of this value, if the node has one.
196    #[must_use]
197    pub fn literal(&self, value: i128) -> Option<u32> {
198        let found = self.ints.binary_search_by(|(have, _)| have.cmp(&value)).ok()?;
199        Some(self.ints[found].1)
200    }
201}
202
203/// One piece of a replacement, in the pre-order that builds it.
204#[derive(Debug)]
205pub enum Piece {
206    /// Whatever the pattern bound at this position.
207    Var {
208        /// The name the rule gave it, for anything that has to say what it did.
209        name: &'static str,
210        /// Which binding of the match it is.
211        index: usize,
212    },
213    /// A constant written in the rule.
214    Int(i128),
215    /// A constant the rule works out from the ones the pattern matched.
216    ///
217    /// This is what lets a rule be written once per width rather than once per constant. A shift
218    /// that stands in for a multiplication by a power of two shifts by the log of that power, and
219    /// the log is a number no rule can write down until it has seen which power it matched.
220    Computed {
221        /// The computation as the rule file writes it, for anything that has to say what it did.
222        text: &'static str,
223        /// What it works out.
224        work: Computation,
225    },
226    /// A term the rule writes, which is an instruction once the caller has built it.
227    App {
228        /// The name in head position.
229        head: &'static str,
230        /// How many arguments it takes.
231        arity: usize,
232    },
233}
234
235/// A condition on the constants a pattern matched.
236///
237/// It is handed one entry per binding, holding the value of that binding when it has one. A
238/// guard about a binding that is not a constant is false, which is how a rule about a number
239/// declines an operand that is a register.
240pub type Guard = fn(&[Option<i128>]) -> bool;
241
242/// A number worked out from the constants a pattern matched.
243///
244/// Handed one entry per binding, the same as a [`Guard`] is, and for the same reason: the
245/// computation is written in the names the pattern bound and those are positions by the time it
246/// runs. It gives nothing back when a binding it reads is not a constant, which is the answer a
247/// guard gives as false, and the rule does not fire.
248pub type Computation = fn(&[Option<i128>]) -> Option<i128>;
249
250/// One rule, as much of it as matching needs.
251#[derive(Debug)]
252pub struct Rule {
253    /// The pattern as it is written in the rule file, for diagnostics and for tests.
254    pub pattern: &'static str,
255    /// What to put in the matched term's place, flattened into pre-order.
256    pub replacement: &'static [Piece],
257    /// The condition on the match, if the rule has one.
258    pub guard: Option<Guard>,
259    /// The line of the rule file this rule starts on.
260    pub line: u32,
261}
262
263impl Rule {
264    /// The head of the replacement, which is what this rule writes.
265    #[must_use]
266    pub fn head(&self) -> Option<&'static str> {
267        match self.replacement.first() {
268            Some(Piece::App { head, .. }) => Some(head),
269            _ => None,
270        }
271    }
272}
273
274/// A set of rules, as an automaton over their patterns.
275#[derive(Debug)]
276pub struct Table {
277    /// The rule file this was built from, so that anything said about a rule can name a file
278    /// somebody can open.
279    pub source: &'static str,
280    /// The trie. Node zero is the root.
281    pub nodes: &'static [Node],
282    /// The rules, in the order the file writes them.
283    pub rules: &'static [Rule],
284}
285
286/// What [`Table::opening`] found at the root of the trie for one head.
287#[derive(Debug, Clone, Copy)]
288pub struct Opening<'h> {
289    /// The head, which is what the walk pushes the arguments of the term by.
290    head: Option<(&'h str, usize)>,
291    /// The root's branch on it, if it has one.
292    branch: Option<u32>,
293}
294
295/// What a successful match found.
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct Match<N> {
298    /// Which rule of the table fired.
299    pub rule: usize,
300    /// What the pattern bound, in the order it binds it.
301    pub bindings: Vec<N>,
302}
303
304impl Table {
305    /// The rule that fires on this term, and what it bound.
306    ///
307    /// The term is matched as a whole. Finding the terms in a function worth matching is the
308    /// caller's job and not this one's.
309    #[must_use]
310    pub fn find<S: Subject>(&self, subject: &S, term: S::Node) -> Option<Match<S::Node>> {
311        // Both start with room for a pattern of the usual size. Starting them empty grew them a
312        // few times on every instruction selected, and that was most of what the walk allocated.
313        let mut left = Vec::with_capacity(16);
314        let mut bindings = Vec::with_capacity(8);
315        let rule = self.find_in(subject, term, &mut left, &mut bindings)?;
316        Some(Match { rule, bindings })
317    }
318
319    /// [`Table::find`] with the two stacks the walk needs handed in, for a caller that asks more
320    /// than once and would rather not pay for them every time.
321    ///
322    /// Both are emptied before the walk. What `bindings` holds afterwards is what the pattern
323    /// bound when a rule fired, and nothing anybody reads when none did.
324    pub fn find_in<S: Subject>(
325        &self,
326        subject: &S,
327        term: S::Node,
328        left: &mut Vec<S::Node>,
329        bindings: &mut Vec<S::Node>,
330    ) -> Option<usize> {
331        left.clear();
332        left.push(term);
333        bindings.clear();
334        self.run(subject, 0, left, bindings)
335    }
336
337    /// Whether a term with this head could match any rule at all, which is a question about the
338    /// root of the trie alone.
339    ///
340    /// A walk whose first node has no branch for the head, no constant to compare against, no
341    /// repeat to look for and no wildcard gives up at that node, so a false here is the answer
342    /// [`Table::find`] would have given without the walk. A caller that matches one term under
343    /// several ways of showing its operands asks this once, since the head of the term itself
344    /// does not depend on how its operands are shown.
345    #[must_use]
346    pub fn opens(&self, head: Option<(&str, usize)>) -> bool {
347        self.opening(head).is_some()
348    }
349
350    /// What the root of the trie says about a term with this head, or nothing when that is that
351    /// no rule matches it, which is [`Table::opens`] with the branch it found kept.
352    ///
353    /// A caller that matches one term under several ways of showing its operands hands this to
354    /// [`Table::find_opened`] for each of them. The head of the term is the same under every one,
355    /// so the root's branch on it is too, and finding it again for each was a search of the
356    /// largest node in the trie and a question to the subject for every way tried.
357    /// tamnd/rucc#3052.
358    #[must_use]
359    pub fn opening<'h>(&self, head: Option<(&'h str, usize)>) -> Option<Opening<'h>> {
360        let root = &self.nodes[0];
361        let branch = head.and_then(|(name, arity)| root.branch(name, arity));
362        let open = branch.is_some()
363            || !root.ints.is_empty()
364            || !root.same.is_empty()
365            || root.wildcard.is_some();
366        open.then_some(Opening { head, branch })
367    }
368
369    /// [`Table::find_in`] for a term whose root [`Table::opening`] already looked at.
370    ///
371    /// The opening has to be of this table and of the head this subject gives the term, which is
372    /// what makes the walk the one [`Table::find_in`] would have made.
373    pub fn find_opened<S: Subject>(
374        &self,
375        subject: &S,
376        term: S::Node,
377        opening: Opening<'_>,
378        left: &mut Vec<S::Node>,
379        bindings: &mut Vec<S::Node>,
380    ) -> Option<usize> {
381        left.clear();
382        bindings.clear();
383        self.ask(subject, 0, (term, opening.head), opening.branch, left, bindings)
384    }
385
386    /// The rule a match found, which is the one thing every caller wants out of it.
387    #[must_use]
388    pub fn rule<N>(&self, found: &Match<N>) -> &Rule {
389        &self.rules[found.rule]
390    }
391
392    /// Walk the trie and the subject together.
393    ///
394    /// `left` is the subterms still to be matched, innermost last, so that popping gives the
395    /// pre-order the patterns were flattened in. It is one stack for the whole walk rather than a
396    /// copy per branch, so a walk that finds nothing puts back what it took: the term it popped,
397    /// and through [`Table::take`] the arguments it pushed. What is on it after a match is not
398    /// anything anybody reads.
399    fn run<S: Subject>(
400        &self,
401        subject: &S,
402        at: usize,
403        left: &mut Vec<S::Node>,
404        bindings: &mut Vec<S::Node>,
405    ) -> Option<usize> {
406        let Some(term) = left.pop() else {
407            return self.accept(subject, at, bindings);
408        };
409        let head = subject.head(term);
410        let branch = head.and_then(|(name, arity)| self.nodes[at].branch(name, arity));
411        self.ask(subject, at, (term, head), branch, left, bindings)
412    }
413
414    /// The questions a node asks of one term, with the branch on its head already found, in the
415    /// order that makes the most specific rule the one that fires.
416    fn ask<S: Subject>(
417        &self,
418        subject: &S,
419        at: usize,
420        (term, head): (S::Node, Option<(&str, usize)>),
421        branch: Option<u32>,
422        left: &mut Vec<S::Node>,
423        bindings: &mut Vec<S::Node>,
424    ) -> Option<usize> {
425        let node = &self.nodes[at];
426
427        // The head of the term, which is the question nearly every branch of nearly every node
428        // is about and the one that has to be found rather than looked for.
429        if let Some(next) = branch {
430            if let Some(rule) = self.take(subject, next, (term, head), left, bindings) {
431                return Some(rule);
432            }
433        }
434
435        // Its value, if it is a constant and if this node asks about one. The emptiness is
436        // checked first because asking the subject for a value costs something and most nodes
437        // have nothing to compare it against.
438        if !node.ints.is_empty() {
439            if let Some(next) = subject.int(term).and_then(|value| node.literal(value)) {
440                if let Some(rule) = self.take(subject, next, (term, head), left, bindings) {
441                    return Some(rule);
442                }
443            }
444        }
445
446        // A repeat of an earlier binding. The binding is always there, because a pattern only
447        // writes a name for the second time after it has written it once and the trie keeps that
448        // order.
449        for &(index, next) in node.same {
450            if bindings.get(index).is_some_and(|&bound| subject.same(bound, term)) {
451                if let Some(rule) = self.take(subject, next, (term, head), left, bindings) {
452                    return Some(rule);
453                }
454            }
455        }
456
457        // The wildcard is last, which is the whole of what specificity order means here.
458        if let Some((_, next)) = node.wildcard {
459            let depth = bindings.len();
460            bindings.push(term);
461            if let Some(rule) = self.run(subject, next as usize, left, bindings) {
462                return Some(rule);
463            }
464            bindings.truncate(depth);
465        }
466        left.push(term);
467        None
468    }
469
470    /// Follow one branch, and give the stack and the bindings back as they were if it led nowhere.
471    ///
472    /// What goes on the stack is the arguments of the term, innermost last, whenever the term has
473    /// any. That is the same for every kind of branch, because what a branch decided is that this
474    /// subterm is matched and the walk carries on into what is under it.
475    fn take<S: Subject>(
476        &self,
477        subject: &S,
478        next: u32,
479        term: (S::Node, Option<(&str, usize)>),
480        left: &mut Vec<S::Node>,
481        bindings: &mut Vec<S::Node>,
482    ) -> Option<usize> {
483        let (term, head) = term;
484        let height = left.len();
485        if let Some((_, arity)) = head {
486            for index in (0..arity).rev() {
487                left.push(subject.arg(term, index));
488            }
489        }
490        let depth = bindings.len();
491        if let Some(rule) = self.run(subject, next as usize, left, bindings) {
492            return Some(rule);
493        }
494        left.truncate(height);
495        bindings.truncate(depth);
496        None
497    }
498
499    /// The first rule that ends at this node whose guard holds, if there is one.
500    fn accept<S: Subject>(&self, subject: &S, at: usize, bindings: &[S::Node]) -> Option<usize> {
501        // The values are collected once and only when a guard asks, because most rules have no
502        // guard and would pay for it every time.
503        let mut values: Option<Vec<Option<i128>>> = None;
504        for &rule in self.nodes[at].accept {
505            let rule = rule as usize;
506            let Some(guard) = self.rules[rule].guard else { return Some(rule) };
507            let values = values
508                .get_or_insert_with(|| bindings.iter().map(|&node| subject.int(node)).collect());
509            if guard(values) {
510                return Some(rule);
511            }
512        }
513        None
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::{Match, Node, Piece, Rule, Subject, Table};
520
521    /// A term, in the only shape a test needs: a flat arena, because that is the shape the IR
522    /// has and answering the questions out of one is what the callers will be doing.
523    #[derive(Debug)]
524    enum Held {
525        Int(i128),
526        App(String, Vec<usize>),
527    }
528
529    #[derive(Debug, Default)]
530    struct Terms {
531        nodes: Vec<Held>,
532    }
533
534    impl Terms {
535        fn constant(&mut self, value: i128) -> usize {
536            self.nodes.push(Held::Int(value));
537            self.nodes.len() - 1
538        }
539
540        fn app(&mut self, head: &str, args: &[usize]) -> usize {
541            self.nodes.push(Held::App(head.to_owned(), args.to_vec()));
542            self.nodes.len() - 1
543        }
544    }
545
546    impl Subject for Terms {
547        type Node = usize;
548
549        fn head(&self, node: usize) -> Option<(&str, usize)> {
550            match &self.nodes[node] {
551                Held::App(head, args) => Some((head.as_str(), args.len())),
552                Held::Int(_) => None,
553            }
554        }
555
556        fn arg(&self, node: usize, index: usize) -> usize {
557            match &self.nodes[node] {
558                Held::App(_, args) => args[index],
559                Held::Int(_) => unreachable!("a constant has no arguments"),
560            }
561        }
562
563        fn int(&self, node: usize) -> Option<i128> {
564            match self.nodes[node] {
565                Held::Int(value) => Some(value),
566                Held::App(..) => None,
567            }
568        }
569
570        // An index into the arena is the identity of a term here, so two places are the same
571        // thing when they point at the same entry. A subject over the IR answers this out of the
572        // value each place holds instead, which is the same question asked of a different shape.
573        fn same(&self, a: usize, b: usize) -> bool {
574            a == b
575        }
576    }
577
578    /// A table written by hand, in the shape `rucc-rules` emits.
579    ///
580    /// Three rules over `(add x k)`: the first wants the constant to be zero, the second takes
581    /// any constant that is not negative, and the third, on the same node as the second, takes
582    /// one below minus ten. That is enough to exercise everything the walk does, which is a
583    /// concrete test before a wildcard, a guard that can refuse, the next rule on the node being
584    /// asked when it does, and the search carrying on after all of them have. A fourth rule,
585    /// `(and x x)`, is the one that writes a name twice.
586    /// A node with nothing on it, so that the ones below say only what they are about.
587    const NOTHING: Node = Node { heads: &[], ints: &[], same: &[], wildcard: None, accept: &[] };
588
589    /// A branch on a head, with the key a generated table would give it.
590    const fn head(name: &'static str, arity: usize, next: u32) -> (u128, &'static str, usize, u32) {
591        (Node::key(name), name, arity, next)
592    }
593
594    static NODES: &[Node] = &[
595        // 0, the root.
596        Node { heads: &[head("add", 2, 1), head("and", 2, 5)], ..NOTHING },
597        // 1, the first operand.
598        Node { wildcard: Some(("x", 2)), ..NOTHING },
599        // 2, the second operand.
600        Node { ints: &[(0, 3)], wildcard: Some(("k", 4)), ..NOTHING },
601        // 3, an addition of zero.
602        Node { accept: &[0], ..NOTHING },
603        // 4, an addition of anything, if one of the two guards holds.
604        Node { accept: &[1, 3], ..NOTHING },
605        // 5, the first operand of the conjunction, which is the one that binds.
606        Node { wildcard: Some(("x", 6)), ..NOTHING },
607        // 6, the second operand, which has to be what the first one bound.
608        Node { same: &[(0, 7)], ..NOTHING },
609        // 7, a conjunction of one thing with itself.
610        Node { accept: &[2], ..NOTHING },
611    ];
612
613    fn not_negative(bound: &[Option<i128>]) -> bool {
614        let Some(Some(k)) = bound.get(1).copied() else { return false };
615        k >= 0
616    }
617
618    fn far_below(bound: &[Option<i128>]) -> bool {
619        let Some(Some(k)) = bound.get(1).copied() else { return false };
620        k < -10
621    }
622
623    static RULES: &[Rule] = &[
624        Rule {
625            pattern: "(add x 0)",
626            replacement: &[Piece::Var { name: "x", index: 0 }],
627            guard: None,
628            line: 1,
629        },
630        Rule {
631            pattern: "(add x k)",
632            replacement: &[
633                Piece::App { head: "add_immediate", arity: 2 },
634                Piece::Var { name: "x", index: 0 },
635                Piece::Var { name: "k", index: 1 },
636            ],
637            guard: Some(not_negative),
638            line: 2,
639        },
640        Rule {
641            pattern: "(and x x)",
642            replacement: &[Piece::Var { name: "x", index: 0 }],
643            guard: None,
644            line: 3,
645        },
646        Rule {
647            pattern: "(add x k)",
648            replacement: &[
649                Piece::App { head: "add_far", arity: 2 },
650                Piece::Var { name: "x", index: 0 },
651                Piece::Var { name: "k", index: 1 },
652            ],
653            guard: Some(far_below),
654            line: 4,
655        },
656    ];
657
658    static TABLE: Table = Table { source: "rules/test.rules", nodes: NODES, rules: RULES };
659
660    fn add(terms: &mut Terms, second: usize) -> usize {
661        let first = terms.app("v0", &[]);
662        terms.app("add", &[first, second])
663    }
664
665    /// The concrete test is tried before the wildcard, so the rule about zero wins over the rule
666    /// about any constant even though both of them match. That is the whole of what specificity
667    /// order means here, and it falls out of the shape of the trie.
668    #[test]
669    fn the_rule_that_names_the_operand_beats_the_rule_that_takes_anything() {
670        let mut terms = Terms::default();
671        let zero = terms.constant(0);
672        let term = add(&mut terms, zero);
673        let found = TABLE.find(&terms, term).expect("a rule fires");
674        assert_eq!(TABLE.rule(&found).pattern, "(add x 0)");
675    }
676
677    /// The bindings come back in the order the pattern binds them, which is the pre-order the
678    /// replacement was flattened in, so a `Piece::Var` can be read as an index into them.
679    #[test]
680    fn a_match_gives_back_what_the_pattern_bound_in_the_order_it_bound_it() {
681        let mut terms = Terms::default();
682        let seven = terms.constant(7);
683        let term = add(&mut terms, seven);
684        let found = TABLE.find(&terms, term).expect("a rule fires");
685        let rule = TABLE.rule(&found);
686        assert_eq!(rule.pattern, "(add x k)");
687        assert_eq!(rule.head(), Some("add_immediate"));
688        assert_eq!(found.bindings.len(), 2);
689        assert_eq!(found.bindings[1], seven);
690        assert_eq!(terms.int(found.bindings[1]), Some(7));
691    }
692
693    /// A guard that does not hold is a rule that did not match, and there is nothing else to
694    /// try, so the answer is nothing rather than the wrong rule.
695    #[test]
696    fn a_guard_that_refuses_takes_its_rule_out_of_the_running() {
697        let mut terms = Terms::default();
698        let negative = terms.constant(-1);
699        let term = add(&mut terms, negative);
700        assert_eq!(TABLE.find(&terms, term), None);
701    }
702
703    /// Two rules with one pattern, and the second is asked when the first one's guard refuses.
704    #[test]
705    fn a_rule_that_shares_its_pattern_fires_when_the_one_before_it_refuses() {
706        let mut terms = Terms::default();
707        let far = terms.constant(-20);
708        let term = add(&mut terms, far);
709        let found = TABLE.find(&terms, term).expect("a rule fires");
710        assert_eq!(TABLE.rule(&found).head(), Some("add_far"));
711        let near = terms.constant(20);
712        let term = add(&mut terms, near);
713        let found = TABLE.find(&terms, term).expect("a rule fires");
714        assert_eq!(TABLE.rule(&found).head(), Some("add_immediate"));
715    }
716
717    /// A head the root has no branch for opens nothing, and one it has a branch for opens the
718    /// table even when no rule under the branch goes on to fire.
719    #[test]
720    fn only_a_head_the_root_branches_on_opens_the_table() {
721        assert!(TABLE.opens(Some(("add", 2))));
722        assert!(TABLE.opens(Some(("and", 2))));
723        assert!(!TABLE.opens(Some(("add", 3))));
724        assert!(!TABLE.opens(Some(("sub", 2))));
725        assert!(!TABLE.opens(None));
726        let mut terms = Terms::default();
727        let first = terms.app("v0", &[]);
728        let second = terms.app("v1", &[]);
729        let term = terms.app("sub", &[first, second]);
730        assert_eq!(TABLE.find(&terms, term), None);
731    }
732
733    /// A walk that starts from what the root said finds what a walk from the top finds, for a
734    /// term a rule fires on, one where the guards refuse, and one the root branches on that no
735    /// rule takes. A head the root has no branch for opens nothing.
736    #[test]
737    fn a_walk_from_the_opening_finds_what_a_walk_from_the_top_does() {
738        let mut terms = Terms::default();
739        let zero = terms.constant(0);
740        let fires = add(&mut terms, zero);
741        let negative = terms.constant(-1);
742        let refused = add(&mut terms, negative);
743        let x = terms.app("v0", &[]);
744        let both = terms.app("and", &[x, x]);
745        let (mut left, mut bindings) = (Vec::new(), Vec::new());
746        for term in [fires, refused, both] {
747            let opening = TABLE.opening(terms.head(term)).expect("the root branches on it");
748            let from_top = TABLE.find_in(&terms, term, &mut left, &mut bindings);
749            let from_top = from_top.map(|rule| (rule, bindings.clone()));
750            let opened = TABLE.find_opened(&terms, term, opening, &mut left, &mut bindings);
751            assert_eq!(opened.map(|rule| (rule, bindings.clone())), from_top);
752        }
753        assert!(TABLE.opening(Some(("sub", 2))).is_none());
754        assert!(TABLE.opening(None).is_none());
755    }
756
757    /// The same guard against an operand that is not a constant at all. A guard is a claim about
758    /// a number, so a register makes it false rather than an error.
759    #[test]
760    fn a_guard_about_a_number_refuses_an_operand_that_is_not_one() {
761        let mut terms = Terms::default();
762        let other = terms.app("v1", &[]);
763        let term = add(&mut terms, other);
764        assert_eq!(TABLE.find(&terms, term), None);
765    }
766
767    #[test]
768    fn a_term_no_rule_covers_finds_no_rule() {
769        let mut terms = Terms::default();
770        let x = terms.app("v0", &[]);
771        let y = terms.app("v1", &[]);
772        let term = terms.app("no.such.head", &[x, y]);
773        assert_eq!(TABLE.find(&terms, term), None);
774    }
775
776    /// The rule that writes one name twice. Both operands are the same term, so the test that
777    /// they are holds and the rule fires, and what comes back is the one binding the pattern
778    /// made rather than two.
779    #[test]
780    fn a_pattern_that_names_one_hole_twice_matches_a_term_that_has_one_thing_in_both() {
781        let mut terms = Terms::default();
782        let x = terms.app("v0", &[]);
783        let term = terms.app("and", &[x, x]);
784        let found = TABLE.find(&terms, term).expect("a rule fires");
785        assert_eq!(TABLE.rule(&found).pattern, "(and x x)");
786        assert_eq!(found.bindings, vec![x]);
787    }
788
789    /// The same rule against two different terms. There is no wildcard beside the test, so a
790    /// conjunction of two things is a conjunction no rule covers rather than one this rule
791    /// wrongly claims.
792    #[test]
793    fn a_pattern_that_names_one_hole_twice_refuses_a_term_that_has_two_things_in_it() {
794        let mut terms = Terms::default();
795        let x = terms.app("v0", &[]);
796        let y = terms.app("v1", &[]);
797        let term = terms.app("and", &[x, y]);
798        assert_eq!(TABLE.find(&terms, term), None);
799    }
800
801    /// The branch is found rather than looked for, which is the thing a node being sorted buys.
802    /// A node as wide as the root of a real rule set answers in the same number of comparisons a
803    /// node with eight branches does, and it answers about the head it was never given by not
804    /// finding one rather than by reading to the end.
805    #[test]
806    fn a_branch_is_found_by_searching_the_node_and_not_by_reading_it() {
807        static WIDE: &[(u128, &str, usize, u32)] = &[
808            head("add.i16", 2, 1),
809            head("add.i32", 2, 2),
810            head("add.i64", 2, 3),
811            head("add.i64", 3, 4),
812            head("sub.i32", 2, 5),
813            head("sub.i64", 2, 6),
814            head("xor.i8", 2, 7),
815        ];
816        let node = Node { heads: WIDE, ..NOTHING };
817        assert!(WIDE.is_sorted(), "the search is only a search if the node is in order");
818        assert_eq!(node.branch("add.i64", 2), Some(3));
819        assert_eq!(node.branch("add.i16", 2), Some(1));
820        assert_eq!(node.branch("xor.i8", 2), Some(7));
821        // The same name at two arities is two branches, and they are told apart.
822        assert_eq!(node.branch("add.i64", 3), Some(4));
823        // A head no branch is about, and one the node has at another arity, are both nothing.
824        assert_eq!(node.branch("mul.i64", 2), None);
825        assert_eq!(node.branch("sub.i32", 3), None);
826    }
827
828    /// Names that agree in their first sixteen bytes, and names that run out before that, are
829    /// still told apart, which is what the search has to get right to leave the names alone at
830    /// every step but the last.
831    #[test]
832    fn names_that_share_their_first_sixteen_bytes_are_still_told_apart() {
833        static LONG: &[(u128, &str, usize, u32)] = &[
834            head("load.i64", 1, 1),
835            head("load.i64", 2, 2),
836            head("load.i64.012345678", 1, 3),
837            head("load.i64.012345679", 1, 4),
838            head("load.i64.01234567x", 1, 5),
839            head("load.i64x", 1, 6),
840            head("load.i8", 1, 7),
841        ];
842        let node = Node { heads: LONG, ..NOTHING };
843        assert!(LONG.is_sorted(), "the search is only a search if the node is in order");
844        for &(_, name, arity, next) in LONG {
845            assert_eq!(node.branch(name, arity), Some(next), "{name} with {arity}");
846        }
847        for name in ["load.i6", "load.i64.", "load.i64.0123456", "load.i64.01234567", "load"] {
848            assert_eq!(node.branch(name, 1), None, "{name}");
849        }
850        assert_eq!(node.branch("load.i64.012345677", 1), None);
851    }
852
853    /// The same for a constant, which is the other kind of branch that can be searched.
854    #[test]
855    fn a_literal_is_found_by_searching_too() {
856        let node = Node { ints: &[(-8, 1), (0, 2), (1, 3), (4096, 4)], ..NOTHING };
857        assert_eq!(node.literal(-8), Some(1));
858        assert_eq!(node.literal(0), Some(2));
859        assert_eq!(node.literal(4096), Some(4));
860        assert_eq!(node.literal(7), None);
861    }
862
863    /// The order the kinds of question are asked in, which is the heuristic the module doc
864    /// states. It only decides anything when one node asks two kinds about one place and the
865    /// subject answers both, which is why this needs a subject of its own: the one above answers
866    /// either what a term is called or what number it is, never both, and so does the IR. What is
867    /// asserted is the order that is written down, so that a rule set which starts to depend on
868    /// it gets the answer somebody chose rather than the one that fell out.
869    #[test]
870    fn the_head_is_asked_about_before_the_value_and_the_value_before_a_repeat() {
871        /// `(f a a)`, where each operand is an application and a number at the same time and the
872        /// two of them are one thing. Every question a node can ask is true of them, so which
873        /// one is asked first is the only thing that decides the answer.
874        #[derive(Debug)]
875        struct Both;
876
877        impl Subject for Both {
878            type Node = u8;
879
880            fn head(&self, node: u8) -> Option<(&str, usize)> {
881                if node == 0 { Some(("f", 2)) } else { Some(("k", 0)) }
882            }
883
884            fn int(&self, node: u8) -> Option<i128> {
885                if node == 0 { None } else { Some(7) }
886            }
887
888            fn arg(&self, _: u8, _: usize) -> u8 {
889                1
890            }
891
892            fn same(&self, _: u8, _: u8) -> bool {
893                true
894            }
895        }
896
897        static FOUR: &[Rule] = &[
898            Rule { pattern: "the head", replacement: &[], guard: None, line: 1 },
899            Rule { pattern: "the value", replacement: &[], guard: None, line: 2 },
900            Rule { pattern: "the repeat", replacement: &[], guard: None, line: 3 },
901            Rule { pattern: "the hole", replacement: &[], guard: None, line: 4 },
902        ];
903
904        /// The four ends, and in front of them the node that binds the first operand so that
905        /// there is something for a repeat to be a repeat of.
906        fn table(second: &'static Node) -> Table {
907            const F: &[(u128, &str, usize, u32)] = &[head("f", 2, 1)];
908            let nodes: &'static [Node] = Box::leak(Box::new([
909                Node { heads: F, ..NOTHING },
910                Node { wildcard: Some(("x", 2)), ..NOTHING },
911                *second,
912                Node { accept: &[0], ..NOTHING },
913                Node { accept: &[1], ..NOTHING },
914                Node { accept: &[2], ..NOTHING },
915                Node { accept: &[3], ..NOTHING },
916            ]));
917            Table { source: "rules/test.rules", nodes, rules: FOUR }
918        }
919
920        // All three kinds on one node, with a hole behind them.
921        static MIXED: Node = Node {
922            heads: &[head("k", 0, 3)],
923            ints: &[(7, 4)],
924            same: &[(0, 5)],
925            wildcard: Some(("y", 6)),
926            accept: &[],
927        };
928        assert_eq!(table(&MIXED).find(&Both, 0).map(|found| found.rule), Some(0));
929
930        // The same node without the head, which is what puts the value in front.
931        static WITHOUT_HEAD: Node = Node { heads: &[], ..MIXED };
932        assert_eq!(table(&WITHOUT_HEAD).find(&Both, 0).map(|found| found.rule), Some(1));
933
934        // And without either, which leaves the repeat in front of the hole. That last pair is
935        // the one that is not a heuristic: a concrete question always comes before the hole.
936        static REPEAT: Node = Node { ints: &[], ..WITHOUT_HEAD };
937        assert_eq!(table(&REPEAT).find(&Both, 0).map(|found| found.rule), Some(2));
938
939        // And with nothing concrete left, the hole.
940        static HOLE: Node = Node { same: &[], ..REPEAT };
941        assert_eq!(table(&HOLE).find(&Both, 0).map(|found| found.rule), Some(3));
942    }
943
944    /// A match is what a caller keeps, so it says what it is when a test prints it.
945    #[test]
946    fn a_match_names_the_rule_it_found() {
947        let mut terms = Terms::default();
948        let zero = terms.constant(0);
949        let term = add(&mut terms, zero);
950        assert_eq!(TABLE.find(&terms, term), Some(Match { rule: 0, bindings: vec![term - 1] }));
951    }
952}