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//! A rule that only holds under a condition says so between the two, where it can be read as
22//! part of deciding whether the rule fires rather than as part of what firing produces:
23//!
24//! ```text
25//! (rule (lower (shl.i64 (value x) (iconst k)))
26//! (if (and (>= k 0) (< k 64)))
27//! (x64.shl x k)
28//! (spec (= (bvshl x k) (result))))
29//! ```
30//!
31//! Everything is a term, and a term is a name, a number, or a head applied to arguments. A bare
32//! name is a variable and a parenthesised one is an application, which is the whole of the
33//! distinction and is why a constructor taking nothing is still written `(result)`. Variables
34//! are bound by the pattern and used everywhere else, `(result)` stands for what the
35//! replacement computes and so appears only in a specification, and a name may not be bound
36//! twice in one pattern, because that would be asking the matcher for an equality test it does
37//! not have.
38//!
39//! The `spec` clause is required rather than optional. `spec/17-milestones.md` asks that a rule
40//! the solver cannot discharge never enter the rule set, and making the claim part of the
41//! grammar is what gives that somewhere to stand: a rule without one is not an unverified rule,
42//! it is a syntax error.
43//!
44//! One rule in a hundred has a claim no solver will settle in the time anybody will wait, which
45//! is usually a multiplication of two unknowns at full width. Such a rule may carry a last
46//! clause saying why a proof at narrower widths would be enough:
47//!
48//! ```text
49//! (rule (lower (mul.i64 (value x) (value y)))
50//! (x64.imul x y)
51//! (spec (= (bvmul x y) (result)))
52//! (bounded "multiplication of two unknowns at 64 bits is out of reach"))
53//! ```
54//!
55//! The clause excuses nothing on its own. The rule is still asked at its own width first, and
56//! all the clause does is say what a person is willing to sign for if the answer comes back as
57//! a shrug. `rucc-verify` is what acts on it, and what counts how often it had to.
58//!
59//! # The matcher
60//!
61//! A rule set compiles into a trie over its patterns rather than into a conditional per rule.
62//! Every pattern is flattened into the steps that match it, read in pre-order, and patterns that
63//! begin the same way share the steps they agree on, so testing that a term is an `add.i64`
64//! happens once however many rules begin with one. Specificity is the shape rather than a sort:
65//! the concrete tests at a node are tried before the wildcard, so a rule that names an operand
66//! is tried before a rule that takes anything there.
67//!
68//! # Status
69//!
70//! The language, its reader and the matcher are here, and `rucc-verify` discharges the
71//! specifications. Emitting the matcher as Rust for the compiler to link against is the piece
72//! that follows. All of it lands in `M3` because `spec/10-backend.md` says retrofitting
73//! verification onto an existing rule set is the thing not to do.
74
75#![doc(html_root_url = "https://docs.rs/rucc-rules/0.2.21")]
76
77mod ast;
78mod error;
79mod lex;
80mod matcher;
81mod parse;
82
83pub use ast::{Rule, Term, TermKind};
84pub use error::Error;
85pub use matcher::{Match, Matcher};
86pub use parse::{parse, parse_terms};
87
88/// The milestone in `spec/17-milestones.md` that fills this crate in.
89pub const MILESTONE: &str = "M3";