Skip to main content

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//! The `spec` clause is required rather than optional. `spec/17-milestones.md` asks that a rule
55//! the solver cannot discharge never enter the rule set, and making the claim part of the
56//! grammar is what gives that somewhere to stand: a rule without one is not an unverified rule,
57//! it is a syntax error.
58//!
59//! One rule in a hundred has a claim no solver will settle in the time anybody will wait, which
60//! is usually a multiplication of two unknowns at full width. Such a rule may carry a last
61//! clause saying why a proof at narrower widths would be enough:
62//!
63//! ```text
64//! (rule (lower (mul.i64 (value x) (value y)))
65//!       (x64.imul x y)
66//!       (spec (= (bvmul x y) (result)))
67//!       (bounded "multiplication of two unknowns at 64 bits is out of reach"))
68//! ```
69//!
70//! The clause excuses nothing on its own. The rule is still asked at its own width first, and
71//! all the clause does is say what a person is willing to sign for if the answer comes back as
72//! a shrug. `rucc-verify` is what acts on it, and what counts how often it had to.
73//!
74//! # The matcher
75//!
76//! A rule set compiles into a trie over its patterns rather than into a conditional per rule.
77//! Every pattern is flattened into the steps that match it, read in pre-order, and patterns that
78//! begin the same way share the steps they agree on, so testing that a term is an `add.i64`
79//! happens once however many rules begin with one. Specificity is the shape rather than a sort:
80//! the concrete tests at a node are tried before the wildcard, so a rule that names an operand
81//! is tried before a rule that takes anything there.
82//!
83//! A name may be written twice in one pattern, which says that the two places hold the same
84//! thing. That is how the identities of `spec/optimizer/13-rewrite-rules.md` section 13.4 are
85//! written, and four of them cannot be said without it:
86//!
87//! ```text
88//! (rule (simplify (and.i32 (value.i32 x) (value.i32 x)))
89//!       (value.i32 x)
90//!       (spec (= x (result))))
91//! ```
92//!
93//! The second occurrence binds nothing. It compiles into a test that the subterm is what the
94//! first occurrence took, which sits with the other concrete tests ahead of the wildcard,
95//! because a rule about one value in both operands is more specific than a rule about any two.
96//! Whether two of a subject's terms are the same thing is a question for the subject, since a
97//! term is a place there and two places can hold one value.
98//!
99//! A rule set is also emitted as Rust, which is how the compiler gets to match with it. The
100//! build script of the crate that owns a rule file reads the file, builds the trie, and writes
101//! the table into its build directory, so the rules are read from one place and the table is
102//! never a copy anybody has to keep up to date. What comes out is data rather than code, except
103//! for the guards, which are the one part of a rule that has to be evaluated. The walk over the
104//! table is in the crate that includes it, because what it walks there is the compiler's own IR
105//! rather than a term.
106//!
107//! # Status
108//!
109//! The language, its reader, the matcher and the emitter are here, and `rucc-verify` discharges
110//! the specifications. What follows is the selector: the pass that finds the terms in a function
111//! worth matching, and that builds machine instructions out of what the table gives back. All of
112//! it lands in `M3` because `spec/10-backend.md` says retrofitting verification onto an existing
113//! rule set is the thing not to do.
114
115#![doc(html_root_url = "https://docs.rs/rucc-rules/0.3.3")]
116
117mod ast;
118mod emit;
119mod error;
120mod lex;
121mod matcher;
122mod parse;
123
124pub use ast::{Rule, RuleKind, Term, TermKind};
125pub use emit::emit;
126pub use error::Error;
127pub use matcher::{Match, Matcher};
128pub use parse::{parse, parse_terms};
129
130/// The milestone in `spec/17-milestones.md` that fills this crate in.
131pub const MILESTONE: &str = "M3";