rucc_verify/lib.rs
1//! SMT verification of the rule set.
2//!
3//! Design: `spec/15-testing.md` section 15.5. Outside the layer stack: this runs in CI, not
4//! in the compiler.
5//!
6//! Every rewrite rule and every lowering rule carries a specification, and this crate
7//! discharges it. An unverified rule does not enter the rule set. Rules that the solver
8//! cannot discharge, usually wide bitvector multiplication, get a bounded proof over
9//! restricted widths plus a reviewed justification, and the count of those is a reported
10//! metric that going up is a signal about.
11//!
12//! The approach follows Crocus (ASPLOS 2024), cited in `spec/01-research-2026.md`.
13//!
14//! # What is asked
15//!
16//! A rule says that its pattern and its replacement compute the same thing, and its `spec`
17//! clause says what that thing is in bitvectors. Two obligations follow and both are asked as
18//! one question. The first is the one that matters: what the pattern means and what the
19//! replacement means have to be the same, both read out of the machine model rather than out of
20//! anybody's description of them. The second is the `spec` clause itself, which is written by
21//! hand and so is worth checking rather than trusting, because a rule whose stated claim is not
22//! what its pattern actually means would otherwise be verified against its own mistake.
23//!
24//! The question put to the solver is the negation: is there any assignment to the pattern's
25//! variables that makes either of those false? An `unsat` back means there is not, which is the
26//! rule discharged. A `sat` back is a counterexample and the rule is wrong. Nothing else counts
27//! as a pass, and in particular a solver that gives up is reported as having given up rather
28//! than folded into either answer.
29//!
30//! The guard is an assumption rather than part of the claim, which is what makes a rule that is
31//! only true for some constants provable at all.
32//!
33//! # When the solver gives up
34//!
35//! Some claims no solver settles in the time anybody will wait, and a multiplication of two
36//! unknowns at sixty four bits is the usual one. Such a rule may carry a `bounded` clause
37//! saying why a proof at narrower widths would be enough, and then the shrug is answered by
38//! asking the same question again at [`BOUNDED_WIDTHS`]. Every one of them has to come back
39//! `unsat`, the clause has to be there before any of it happens, and the result is a verdict of
40//! its own rather than a discharge, because a claim proved at four, eight and sixteen bits is
41//! not the claim the compiler relies on.
42//!
43//! The clause is a judgement somebody makes and signs for. A tool that fell back to narrow
44//! widths on its own would turn every rule the solver is slow on into a rule nobody checked,
45//! which is the failure this whole crate exists to prevent, so the fallback is never taken
46//! without a written reason and the number of times it was taken is printed.
47//!
48//! Giving up and running out of the budget are the same shrug here, and `spec/15-testing.md`
49//! section 15.5 says why: a solver that stops does not say which of the two it did, so the budget
50//! is part of what a run means and is printed with every one of them. [`Ask`] is what a test uses
51//! to get the other kind, a solver that gives up because it says so rather than because a clock
52//! ran out.
53//!
54//! # The gate
55//!
56//! [`admit`] is the rule set's front door and the `rucc-verify` program is what CI runs it
57//! from. A file with anything in it that is not a proof is refused whole rather than having the
58//! failing rules dropped, because a compiler built from the rules that happened to pass is a
59//! compiler nobody described.
60//!
61//! # The list
62//!
63//! The rules that got a bounded proof are written out by name, with their reasons, to
64//! `docs/UNVERIFIED.md`, and `--check` says so when the file on disk no longer matches. That is
65//! `spec/optimizer/41-correctness.md` section 41.8: the set of rules nobody has proved at the
66//! width the compiler runs them at is allowed to be non-empty and is not allowed to grow quietly,
67//! and a count in a build log is not something anybody notices growing. [`render`] is where the
68//! file is made and [`Unverified`] is what goes in it.
69//!
70//! # Widths
71//!
72//! A rule is written at the width its pattern's opcode names, and the terms inside it may name
73//! another. `(add.i32 (value.i64 x) (value.i64 y))` is a thirty two bit add of two sixty four bit
74//! registers, and both numbers are in the question that gets asked: `x` is declared sixty four
75//! bits wide and what the rule computes is thirty two. That is what a rule has to be able to say
76//! before `sext`, `zext` and `trunc` can be lowered at all, and the conversions themselves are
77//! written the way `spec/10-backend.md` writes them, `(sign_extend 32 64 x)` and
78//! `(extract 31 0 x)`, with the widths spelled out rather than inferred.
79//!
80//! When the machine term is wider than the IR term it replaces, which is what lowering a value
81//! into a wider register looks like, the two are asked to agree on the bits the IR term has. What
82//! the rest of the register holds is then left to the rule's `spec` clause, which is the only
83//! place a target's sign extension rule is written down and so the only place it can be checked.
84
85#![doc(html_root_url = "https://docs.rs/rucc-verify/0.10.74")]
86
87mod model;
88mod report;
89mod solver;
90mod verify;
91
92pub use model::{
93 ADDRESS_WIDTH, BYTE_WIDTH, DEFAULT_WIDTH, MEMORY_CONST, Model, Sort, Widths, rule_width,
94};
95pub use report::{Unverified, difference, listed, render};
96pub use solver::{Answer, Ask, Solver};
97pub use verify::{BOUNDED_WIDTHS, Report, Verdict, admit, query, query_at, verify};
98
99/// The milestone in `spec/17-milestones.md` that fills this crate in.
100pub const MILESTONE: &str = "M3";