rucc_rules/lib.rs
1//! The rule DSL compiler.
2//!
3//! Design: `spec/09-optimizer.md` and `spec/10-backend.md`. Outside the layer stack: this is
4//! a build dependency, never a runtime one.
5//!
6//! Middle-end rewrites and instruction selection patterns are written once, in one language,
7//! and this crate compiles them into the matching code the compiler runs. The same rule text
8//! is what `rucc-verify` discharges against an SMT solver, which is the point: a rule that is
9//! verified and a rule that is applied cannot drift apart if they are the same text.
10//!
11//! # The language
12//!
13//! A rule names what to match, what to put in its place, and why that is sound:
14//!
15//! ```text
16//! (rule (lower (add.i64 (value x) (mul.i64 (value y) (iconst 4))))
17//! (x64.lea (amode_base_index_scale x y 4))
18//! (spec (= (bvadd x (bvmul y 4)) (result))))
19//! ```
20//!
21//! There are two kinds and the word after `rule` says which. A `lower` rule puts a machine term
22//! in place of an IR one, which is the last thing that happens to a value. A `simplify` rule
23//! puts IR in place of IR, which means what it produces is matched again:
24//!
25//! ```text
26//! (rule (simplify (add.i32 (value.i32 x) (iconst.i32 0)))
27//! (value.i32 x)
28//! (spec (= x (result))))
29//! ```
30//!
31//! Everything else about the two is the same. They share the reader, the trie, the emitter and
32//! the verification obligation, because a rewrite and a lowering are the same claim about two
33//! terms and there is no reason to say it twice. `spec/optimizer/13-rewrite-rules.md` is what
34//! the rewrite half is for and it says the rule set comes before the rewriter that runs it.
35//!
36//! A rule that only holds under a condition says so between the two, where it can be read as
37//! part of deciding whether the rule fires rather than as part of what firing produces:
38//!
39//! ```text
40//! (rule (lower (shl.i64 (value x) (iconst k)))
41//! (if (and (>= k 0) (< k 64)))
42//! (x64.shl x k)
43//! (spec (= (bvshl x k) (result))))
44//! ```
45//!
46//! Everything is a term, and a term is a name, a number, or a head applied to arguments. A bare
47//! name is a variable and a parenthesised one is an application, which is the whole of the
48//! distinction and is why a constructor taking nothing is still written `(result)`. Variables
49//! are bound by the pattern and used everywhere else, `(result)` stands for what the
50//! replacement computes and so appears only in a specification, and a name may not be bound
51//! twice in one pattern, because that would be asking the matcher for an equality test it does
52//! not have.
53//!
54//! A replacement may work a number out of the numbers the pattern matched, which is what lets a
55//! rule be written once per width rather than once per constant:
56//!
57//! ```text
58//! (rule (simplify (mul.i32 (value.i32 x) (iconst.i32 k)))
59//! (if (power_of_two.i32 k))
60//! (shl.i32 (value.i32 x) (iconst.i32 (ctz.i32 k)))
61//! (spec (= (bvmul x k) (result))))
62//! ```
63//!
64//! `(ctz.i32 k)` is arithmetic rather than a term to build, and what says so is its head being
65//! one of the arithmetic ones, which is the same closed list a guard is written in. It compiles
66//! to a function of the bindings exactly as a guard does, and the model file says what it means
67//! at each width exactly as it does for every other head, so the rule is proved as it stands
68//! rather than as the sixty three instances of it nobody wants to read.
69//!
70//! The `spec` clause is required rather than optional. `spec/17-milestones.md` asks that a rule
71//! the solver cannot discharge never enter the rule set, and making the claim part of the
72//! grammar is what gives that somewhere to stand: a rule without one is not an unverified rule,
73//! it is a syntax error.
74//!
75//! One rule in a hundred has a claim no solver will settle in the time anybody will wait, which
76//! is usually a multiplication of two unknowns at full width. Such a rule may carry a last
77//! clause saying why a proof at narrower widths would be enough:
78//!
79//! ```text
80//! (rule (lower (mul.i64 (value x) (value y)))
81//! (x64.imul x y)
82//! (spec (= (bvmul x y) (result)))
83//! (bounded "multiplication of two unknowns at 64 bits is out of reach"))
84//! ```
85//!
86//! The clause excuses nothing on its own. The rule is still asked at its own width first, and
87//! all the clause does is say what a person is willing to sign for if the answer comes back as
88//! a shrug. `rucc-verify` is what acts on it, and what counts how often it had to.
89//!
90//! # The matcher
91//!
92//! A rule set compiles into a trie over its patterns rather than into a conditional per rule.
93//! Every pattern is flattened into the steps that match it, read in pre-order, and patterns that
94//! begin the same way share the steps they agree on, so testing that a term is an `add.i64`
95//! happens once however many rules begin with one. Specificity is the shape rather than a sort:
96//! the concrete tests at a node are tried before the wildcard, so a rule that names an operand
97//! is tried before a rule that takes anything there.
98//!
99//! A name may be written twice in one pattern, which says that the two places hold the same
100//! thing. That is how the identities of `spec/optimizer/13-rewrite-rules.md` section 13.4 are
101//! written, and four of them cannot be said without it:
102//!
103//! ```text
104//! (rule (simplify (and.i32 (value.i32 x) (value.i32 x)))
105//! (value.i32 x)
106//! (spec (= x (result))))
107//! ```
108//!
109//! The second occurrence binds nothing. It compiles into a test that the subterm is what the
110//! first occurrence took, which sits with the other concrete tests ahead of the wildcard,
111//! because a rule about one value in both operands is more specific than a rule about any two.
112//! Whether two of a subject's terms are the same thing is a question for the subject, since a
113//! term is a place there and two places can hold one value.
114//!
115//! A rule set is also emitted as Rust, which is how the compiler gets to match with it. The
116//! build script of the crate that owns a rule file reads the file, builds the trie, and writes
117//! the table into its build directory, so the rules are read from one place and the table is
118//! never a copy anybody has to keep up to date. What comes out is data rather than code, except
119//! for the guards, which are the one part of a rule that has to be evaluated. The walk over the
120//! table is in the crate that includes it, because what it walks there is the compiler's own IR
121//! rather than a term.
122//!
123//! # Status
124//!
125//! The language, its reader, the matcher and the emitter are here, and `rucc-verify` discharges
126//! the specifications. What follows is the selector: the pass that finds the terms in a function
127//! worth matching, and that builds machine instructions out of what the table gives back. All of
128//! it lands in `M3` because `spec/10-backend.md` says retrofitting verification onto an existing
129//! rule set is the thing not to do.
130
131#![doc(html_root_url = "https://docs.rs/rucc-rules/0.3.3")]
132
133mod ast;
134mod emit;
135mod error;
136mod lex;
137mod matcher;
138mod parse;
139
140pub use ast::{Rule, RuleKind, Term, TermKind};
141pub use emit::emit;
142pub use error::Error;
143pub use matcher::{Match, Matcher, Shape};
144pub use parse::{parse, parse_terms};
145
146/// The milestone in `spec/17-milestones.md` that fills this crate in.
147pub const MILESTONE: &str = "M3";