Skip to main content

bitwright/prove/
mod.rs

1//! A native prover (feature `prove`): expressions are bit-blasted into an and-inverter graph,
2//! encoded as clauses, and decided by bitwright's own CDCL SAT solver. A proof of validity comes
3//! with a certificate, the clauses and a DRUP proof of their unsatisfiability, that an
4//! independent checker ([`Certificate::check`]) verifies, and that any DRAT checker can read
5//! ([`Certificate::dimacs`], [`Certificate::drat`]). A refutation comes with a counterexample:
6//! a value for every symbol, checked by bitwright's evaluator before it is returned. A question
7//! the search does not settle within its budget says why ([`Unknown`]), and a [`Question`]
8//! goes on from where the search stopped, under a larger budget, without starting over.
9//! For a small joint output superset above unresolved predicates, [`domain::analyze`]
10//! preserves shared Boolean structure under explicit premises without solving reachability.
11//!
12//! No other solver is involved. Bit-vector operators are blasted with bitwright's total
13//! semantics (SMT-LIB's); floating-point operators with IEEE 754's and bitwright's canonical
14//! NaN ([`crate::fp`]); extension operations through their [`expand`](crate::ext::ExtOp::expand)
15//! definition, if they have one (otherwise the question is not decided).
16//!
17//! ```
18//! use bitwright::prove::{Config, Outcome, equal};
19//! use bitwright::{Context, ParseOptions, Width};
20//!
21//! let mut cx = Context::new();
22//! let o = ParseOptions::width(Width::W32);
23//! let (a, b) = (cx.parse("(x ^ y) + 2 * (x & y)", &o)?, cx.parse("x + y", &o)?);
24//! let Outcome::Proved(Some(cert)) = equal(&mut cx, a, b, &Config::default().with_certificate(true))? else {
25//!     panic!("not proved")
26//! };
27//! assert!(cert.check().is_ok());
28//! let c = cx.parse("x | y", &o)?;
29//! assert!(matches!(equal(&mut cx, a, c, &Config::default())?, Outcome::Refuted(_)));
30//! # Ok::<(), bitwright::Error>(())
31//! ```
32
33pub mod aig;
34pub mod blast;
35mod cuts;
36pub mod domain;
37pub mod drup;
38mod exhaustion;
39pub mod finite;
40mod fp;
41mod relations;
42mod rule;
43mod sample_words;
44pub mod sat;
45#[cfg(test)]
46mod tests;
47
48use crate::error::Error;
49use crate::expr::{Context, Expr, OpCode};
50use crate::facts::Assumptions;
51use crate::hash::IdMap;
52use crate::{BitVec, SymbolKey, Width};
53
54pub use exhaustion::Exhaustion;
55pub use rule::{RuleOutcome, WidthReport, rule, rule_all_widths};
56
57use aig::{Aig, Cnf, L};
58use blast::Bits;
59pub use sat::Limits;
60use sat::{Answer, Lit, Solver, Step};
61
62/// How assignments are selected before SAT search. Sampling can find a concrete
63/// counterexample. Complete ordered enumeration on the word path can also prove validity;
64/// partial coverage and pseudorandom samples cannot.
65#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
66pub enum SampleMode {
67    /// Seeded pseudorandom assignments, with all-zero and all-one unknown bits first.
68    #[default]
69    Random,
70    /// Enumerate assignments from zero upwards over the circuit's unknown input bits,
71    /// least significant input first. Useful for small integer witnesses in wide domains.
72    /// Multiple symbols' unknown bits are concatenated in circuit input order; declared
73    /// fixed bits retain their values. Higher unknown bits stay zero until reached.
74    Small,
75    /// Enumerate small values of reconstructed words: unknown bits of larger pure
76    /// concatenations and standalone symbols are visited from low to high. A bounded
77    /// portfolio varies words separately and together within the shared sample allowance.
78    /// Shared bits keep one assignment. Uses the bounded word sampler when supported, falling
79    /// back to [`Small`](Self::Small) circuit ordering for other graphs.
80    Words,
81}
82
83/// How hard to try, and what to keep.
84#[derive(Copy, Clone, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86pub struct Config {
87    /// The most conflicts the SAT solver may meet (then [`Unknown::Budget`]).
88    pub max_conflicts: u64,
89    /// The most propagations the SAT solver may do (then [`Unknown::Budget`]; `u64::MAX`, the
90    /// default: no limit). A conflict costs more propagation on a larger circuit, so this bounds
91    /// the time a question takes more closely than conflicts do, and as deterministically.
92    pub max_propagations: u64,
93    /// The most AIG nodes the question may take (then [`Unknown::TooLarge`]).
94    pub max_nodes: usize,
95    /// Keep a [`Certificate`] for a proof.
96    pub certificate: bool,
97    /// Simplify the question with bitwright's own engine before blasting it (the
98    /// deobfuscation strategy, with the MBA service when the `mba` feature is on): identities
99    /// the engine settles at the word level (products of MBA, say, which bit-level SAT finds
100    /// hard) are answered at once, and the rest is blasted smaller. Not with a certificate: a
101    /// certificate is of the question as asked, with its symbols' declared known bits.
102    pub simplify: bool,
103    /// Assignments of the symbols to evaluate before the search, 64 at a time
104    /// (in batches of 64, selected by [`sample_mode`](Self::sample_mode)): a refutation that a fraction of
105    /// the values give is found without the search, which needs many conflicts for it on a
106    /// large circuit. With [`word_sampling`](Self::word_sampling), supported samples precede
107    /// bit-blasting. 0: none.
108    pub samples: u32,
109    /// Assignment selection for [`samples`](Self::samples). Default: [`SampleMode::Random`].
110    pub sample_mode: SampleMode,
111    /// Add certified transitive bit-equality lemmas exposed by the asserted query. Cycle
112    /// contradictions are detected regardless; additional lemmas are opt-in because their
113    /// effect on arithmetic search depends on the workload.
114    pub relational_lemmas: bool,
115    /// Initially prefer the unknown bits of small shift counts as decisions. This specializes
116    /// variable shifts during search without restricting which selector values are allowed.
117    pub selector_branching: bool,
118    /// Cancel a shared input through bounded XOR paths at every bitwise boundary. This can
119    /// remove direct input dependencies through odd products, but changes arithmetic search
120    /// behavior; opt-in pending workload-specific measurement.
121    pub input_cancellation: bool,
122    /// Factor ORs of XORs with a shared operand at one-valued equality target bits.
123    /// Opt-in because the smaller encoding can alter longer arithmetic searches adversely.
124    pub join_factoring: bool,
125    /// Inline private intermediate XORs into three-input parity gates. This reduces encoding
126    /// variables without changing the clause count, but its search performance is workload-dependent.
127    pub xor3_encoding: bool,
128    /// Compress modular multiplication columns before one final carry-propagating addition.
129    /// Opt-in pending measurements on arithmetic search workloads.
130    pub carry_save_multiplication: bool,
131    /// Try samples directly on a bounded integer DAG of at most 64-bit words before
132    /// bit-blasting. Ordered modes can prove after enumerating every legal input assignment;
133    /// partial samples and unsupported DAGs use the circuit path. Certificates use that path
134    /// even after complete enumeration.
135    /// [`SampleMode::Words`] also enables this path.
136    pub word_sampling: bool,
137    /// Decide a question by evaluating it at every legal assignment when it has at most this
138    /// many unknown input bits, before bit-blasting. It applies to bounded integer DAGs of at
139    /// most 64-bit words, the same as [`word_sampling`](Self::word_sampling), and costs
140    /// `2^bits` evaluations, in parallel across threads; it is not bounded by the search
141    /// limits. A failing assignment is replayed on the original question. A proof asked for
142    /// with a [`certificate`](Self::certificate) comes with an [`Exhaustion`], which checks the
143    /// question's bit-blasted circuit at every assignment of its inputs, independently of the
144    /// word-level decision. If that circuit has more inputs than this bound (it may keep bits
145    /// the word program fixes from scoped assumptions as constrained inputs), the search
146    /// decides the question instead. 0 (the default): never.
147    pub exhaustive_inputs: u8,
148}
149
150impl Default for Config {
151    fn default() -> Self {
152        Config {
153            max_conflicts: 1_000_000,
154            max_propagations: u64::MAX,
155            max_nodes: 4_000_000,
156            certificate: false,
157            simplify: true,
158            samples: 256,
159            sample_mode: SampleMode::Random,
160            relational_lemmas: false,
161            selector_branching: false,
162            input_cancellation: false,
163            join_factoring: false,
164            xor3_encoding: false,
165            carry_save_multiplication: false,
166            word_sampling: false,
167            exhaustive_inputs: 0,
168        }
169    }
170}
171
172setters!(Config {
173    with_max_conflicts: max_conflicts: u64,
174    with_max_propagations: max_propagations: u64,
175    with_max_nodes: max_nodes: usize,
176    with_certificate: certificate: bool,
177    with_simplify: simplify: bool,
178    with_samples: samples: u32,
179    with_sample_mode: sample_mode: SampleMode,
180    with_relational_lemmas: relational_lemmas: bool,
181    with_selector_branching: selector_branching: bool,
182    with_input_cancellation: input_cancellation: bool,
183    with_join_factoring: join_factoring: bool,
184    with_xor3_encoding: xor3_encoding: bool,
185    with_carry_save_multiplication: carry_save_multiplication: bool,
186    with_word_sampling: word_sampling: bool,
187    with_exhaustive_inputs: exhaustive_inputs: u8,
188});
189
190impl Config {
191    /// The search's limits: [`max_conflicts`](Self::max_conflicts) and
192    /// [`max_propagations`](Self::max_propagations).
193    pub fn limits(&self) -> Limits {
194        Limits {
195            conflicts: self.max_conflicts,
196            propagations: self.max_propagations,
197        }
198    }
199}
200
201/// The engine that simplifies questions (see [`Config::simplify`]).
202fn simplifier() -> &'static crate::engine::Engine {
203    static ENGINE: std::sync::OnceLock<crate::engine::Engine> = std::sync::OnceLock::new();
204    ENGINE.get_or_init(|| {
205        #[cfg(feature = "mba")]
206        let strategy =
207            crate::engine::Strategy::deobfuscate().with_mba(crate::mba::MbaConfig::default());
208        #[cfg(not(feature = "mba"))]
209        let strategy = crate::engine::Strategy::deobfuscate();
210        crate::engine::Engine::builder()
211            .builtin()
212            .strategy(strategy)
213            .build()
214            .unwrap_or_else(|_| crate::engine::Engine::standard())
215    })
216}
217
218/// Clauses and a DRUP proof that they are unsatisfiable, or, for a proof by complete
219/// enumeration ([`Config::exhaustive_inputs`]), the circuit to enumerate.
220#[derive(Clone, Debug)]
221pub struct Certificate {
222    /// The number of variables.
223    pub vars: u32,
224    /// The clauses.
225    pub clauses: Vec<Vec<Lit>>,
226    /// The proof.
227    pub proof: Vec<Step>,
228    /// The circuit of a proof by complete enumeration. Such a certificate has no clauses or
229    /// DRUP proof, so its [`dimacs`](Self::dimacs) and [`drat`](Self::drat) export nothing a
230    /// DRAT checker would accept.
231    pub exhaustion: Option<Exhaustion>,
232}
233
234impl Certificate {
235    /// Checks the proof with the independent checker: the DRUP proof against the clauses, or
236    /// the circuit of an [`Exhaustion`] at every assignment of its inputs.
237    pub fn check(&self) -> Result<(), String> {
238        match &self.exhaustion {
239            Some(exhaustion) => exhaustion.check(),
240            None => drup::check(&self.clauses, &self.proof),
241        }
242    }
243
244    /// The clauses in DIMACS CNF.
245    pub fn dimacs(&self) -> String {
246        let mut s = format!("p cnf {} {}\n", self.vars, self.clauses.len());
247        for c in &self.clauses {
248            for l in c {
249                s.push_str(&l.dimacs().to_string());
250                s.push(' ');
251            }
252            s.push_str("0\n");
253        }
254        s
255    }
256
257    /// The proof in DRAT's text format (additions, and deletions prefixed `d`).
258    pub fn drat(&self) -> String {
259        let mut s = String::new();
260        for step in &self.proof {
261            let (pre, c) = match step {
262                Step::Add(c) => ("", c),
263                Step::Delete(c) => ("d ", c),
264            };
265            s.push_str(pre);
266            for l in c {
267                s.push_str(&l.dimacs().to_string());
268                s.push(' ');
269            }
270            s.push_str("0\n");
271        }
272        s
273    }
274}
275
276/// The answer to a question.
277#[derive(Clone, Debug)]
278#[non_exhaustive]
279pub enum Outcome {
280    /// It holds for every value of the symbols (with a certificate, if asked for).
281    Proved(Option<Certificate>),
282    /// It fails at these values of the symbols (checked by evaluation).
283    Refuted(Vec<(SymbolKey, BitVec)>),
284    /// Not decided: why.
285    Unknown(Unknown),
286}
287
288/// Why a question is not decided.
289#[derive(Clone, Debug, PartialEq, Eq)]
290#[non_exhaustive]
291pub enum Unknown {
292    /// The search reached a limit ([`Config::max_conflicts`] or
293    /// [`Config::max_propagations`]) after this much work on the question: more may decide
294    /// it, and a [`Question`] goes on from where it stopped.
295    Budget {
296        /// Conflicts met on the question.
297        conflicts: u64,
298        /// Propagations done on the question.
299        propagations: u64,
300    },
301    /// The circuit passed [`Config::max_nodes`]: `nodes` had been built when blasting stopped.
302    /// The same under any search budget with that cap.
303    TooLarge {
304        /// AIG nodes built.
305        nodes: usize,
306    },
307    /// Something the prover does not reason about (an extension operation without an
308    /// expansion, say). The same under any budget.
309    Unsupported(String),
310}
311
312impl Unknown {
313    /// Whether a larger search budget may decide the question ([`Unknown::Budget`]).
314    pub fn is_budget(&self) -> bool {
315        matches!(self, Unknown::Budget { .. })
316    }
317}
318
319impl core::fmt::Display for Unknown {
320    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
321        match self {
322            Unknown::Budget {
323                conflicts,
324                propagations,
325            } => write!(
326                f,
327                "no answer within {conflicts} conflicts ({propagations} propagations)"
328            ),
329            Unknown::TooLarge { nodes } => {
330                write!(f, "the circuit is too large ({nodes} nodes built)")
331            }
332            Unknown::Unsupported(why) => f.write_str(why),
333        }
334    }
335}
336
337/// The work a question took, so far.
338#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
339#[non_exhaustive]
340pub struct Stats {
341    /// AIG nodes of the circuit (inputs and gates).
342    pub nodes: usize,
343    /// Assignments evaluated before search, on words or the circuit ([`Config::samples`]).
344    pub samples: u64,
345    /// Variables of the clauses.
346    pub vars: u32,
347    /// Clauses of the question (not learned ones).
348    pub clauses: usize,
349    /// Learned clauses kept.
350    pub learned: usize,
351    /// Conflicts.
352    pub conflicts: u64,
353    /// Decisions.
354    pub decisions: u64,
355    /// Propagations.
356    pub propagations: u64,
357}
358
359/// The bits of expressions, blasted on demand.
360pub(crate) struct Blaster<'c> {
361    pub(crate) cx: &'c mut Context,
362    pub(crate) g: Aig,
363    bits: IdMap<u32, Bits>,
364    /// Each symbol met, with its bits.
365    pub(crate) symbols: Vec<(u32, Bits)>,
366    selectors: Vec<L>,
367    selector_hints: bool,
368    input_cancellation: bool,
369    join_factoring: bool,
370    carry_save_multiplication: bool,
371    max_nodes: usize,
372    /// Blasting stopped at `max_nodes`.
373    too_large: bool,
374}
375
376impl<'c> Blaster<'c> {
377    pub(crate) fn new(cx: &'c mut Context, max_nodes: usize) -> Blaster<'c> {
378        Blaster {
379            cx,
380            g: Aig::new(),
381            bits: IdMap::default(),
382            symbols: Vec::new(),
383            selectors: Vec::new(),
384            selector_hints: false,
385            input_cancellation: false,
386            join_factoring: false,
387            carry_save_multiplication: false,
388            max_nodes,
389            too_large: false,
390        }
391    }
392
393    /// The bits of node `root`.
394    pub(crate) fn blast(&mut self, root: u32) -> Result<Bits, Error> {
395        let bits = &self.bits;
396        let order = self
397            .cx
398            .post_order_ids_pruned(&[root], |e| bits.contains_key(&e.index()));
399        for i in order {
400            if self.bits.contains_key(&i) {
401                continue;
402            }
403            if self.g.len() > self.max_nodes {
404                self.too_large = true;
405                return Err(Error::Unsupported("the circuit is too large".into()));
406            }
407            let b = self.node(i)?;
408            self.bits.insert(i, b);
409        }
410        Ok(self.bits[&root].clone())
411    }
412
413    fn node(&mut self, i: u32) -> Result<Bits, Error> {
414        use blast::*;
415        let n = self.cx.node(i);
416        let w = usize::from(n.width);
417        let get = |s: &Self, j: u32| s.bits[&j].clone();
418        let g = &mut self.g;
419        Ok(match n.op {
420            OpCode::Const => konst(&self.cx.const_val(i).unwrap_or(BitVec::zero(Width::W1))),
421            OpCode::Sym => {
422                // Declarations are part of a symbol's meaning, including in certificate
423                // mode. Only its unknown bits are inputs of the circuit.
424                let known = self.cx.declared_at(i);
425                let b: Bits = (0..w)
426                    .map(|j| match known {
427                        Some(k) if k.known_one().bit(j as u16) == Some(true) => aig::TRUE,
428                        Some(k) if k.known_zero().bit(j as u16) == Some(true) => aig::FALSE,
429                        _ => g.input(),
430                    })
431                    .collect();
432                self.symbols.push((i, b.clone()));
433                b
434            }
435            OpCode::Not => not(g, &self.bits[&n.a]),
436            OpCode::Neg => {
437                let a = get(self, n.a);
438                neg(&mut self.g, &a)
439            }
440            OpCode::Popcnt => popcnt(g, &self.bits[&n.a]),
441            OpCode::Clz => clz(g, &self.bits[&n.a]),
442            OpCode::Ctz => ctz(g, &self.bits[&n.a]),
443            OpCode::Bswap => bswap(&self.bits[&n.a]),
444            OpCode::BitRev => bitrev(&self.bits[&n.a]),
445            OpCode::Zext => {
446                let mut a = get(self, n.a);
447                a.resize(w, aig::FALSE);
448                a
449            }
450            OpCode::Sext => {
451                let mut a = get(self, n.a);
452                let top = *a.last().unwrap_or(&aig::FALSE);
453                a.resize(w, top);
454                a
455            }
456            OpCode::Extract => {
457                let a = get(self, n.a);
458                let lo = n.b as usize;
459                a[lo..lo + w].to_vec()
460            }
461            OpCode::Concat => {
462                let (hi, mut lo) = (get(self, n.a), get(self, n.b));
463                lo.extend(hi);
464                lo
465            }
466            OpCode::Select => {
467                let (c, t, e) = (get(self, n.a)[0], get(self, n.b), get(self, n.c));
468                mux(&mut self.g, c, &t, &e)
469            }
470            OpCode::Eq | OpCode::Ne | OpCode::Ult | OpCode::Ule | OpCode::Slt | OpCode::Sle => {
471                let (a, b) = (get(self, n.a), get(self, n.b));
472                let g = &mut self.g;
473                let r = match n.op {
474                    OpCode::Eq if self.join_factoring => eq_factored(g, &a, &b),
475                    OpCode::Ne if self.join_factoring => eq_factored(g, &a, &b) ^ 1,
476                    OpCode::Eq => eq(g, &a, &b),
477                    OpCode::Ne => eq(g, &a, &b) ^ 1,
478                    OpCode::Ult => ult(g, &a, &b),
479                    OpCode::Ule => ule(g, &a, &b),
480                    OpCode::Slt => slt(g, &a, &b),
481                    _ => sle(g, &a, &b),
482                };
483                vec![r]
484            }
485            op if op.as_bin().is_some() => {
486                let (a, b) = (get(self, n.a), get(self, n.b));
487                if self.selector_hints && matches!(op, OpCode::Shl | OpCode::LShr | OpCode::AShr) {
488                    let unknown: Vec<_> = b
489                        .iter()
490                        .copied()
491                        .filter(|&l| l > aig::TRUE)
492                        .take(5)
493                        .collect();
494                    if !unknown.is_empty() && unknown.len() <= 4 {
495                        self.selectors.extend(unknown);
496                    }
497                }
498                let g = &mut self.g;
499                match op {
500                    OpCode::Add => add(g, &a, &b),
501                    OpCode::Sub => sub(g, &a, &b),
502                    OpCode::Mul if self.carry_save_multiplication => mul_carry_save(g, &a, &b),
503                    OpCode::Mul => mul(g, &a, &b),
504                    OpCode::UMulHi => mulhi(g, &a, &b, false),
505                    OpCode::SMulHi => mulhi(g, &a, &b, true),
506                    OpCode::UDiv => udivrem(g, &a, &b).0,
507                    OpCode::URem => udivrem(g, &a, &b).1,
508                    OpCode::SDiv => sdiv(g, &a, &b),
509                    OpCode::SRem => srem(g, &a, &b),
510                    OpCode::And => and(g, &a, &b),
511                    OpCode::Or => or(g, &a, &b),
512                    OpCode::Xor if self.input_cancellation => xor_cancel_input(g, &a, &b),
513                    OpCode::Xor => xor(g, &a, &b),
514                    OpCode::Shl => shift(g, &a, &b, true, aig::FALSE),
515                    OpCode::LShr => shift(g, &a, &b, false, aig::FALSE),
516                    OpCode::AShr => {
517                        let s = *a.last().unwrap_or(&aig::FALSE);
518                        shift(g, &a, &b, false, s)
519                    }
520                    OpCode::RotL => rotate(g, &a, &b, true),
521                    OpCode::RotR => rotate(g, &a, &b, false),
522                    OpCode::Pdep => pdep(g, &a, &b),
523                    _ => pext(g, &a, &b),
524                }
525            }
526            op if op.as_ext().is_some() => self.ext(i)?,
527            _ => {
528                let kids: Vec<Bits> = n.children().map(|c| self.bits[&c].clone()).collect();
529                fp::blast(self, i, &kids)?
530            }
531        })
532    }
533
534    /// An extension output, through the operation's expansion.
535    fn ext(&mut self, i: u32) -> Result<Bits, Error> {
536        let n = self.cx.node(i);
537        let Some((_, k)) = n.op.as_ext() else {
538            return Err(Error::Unsupported("not an extension output".into()));
539        };
540        let args: Vec<Expr> = n.children().map(|c| self.cx.handle(c)).collect();
541        let reg = self
542            .cx
543            .registry
544            .clone()
545            .ok_or_else(|| Error::Unsupported("an extension without its registry".into()))?;
546        let op = reg
547            .op_at(n.aux)
548            .ok_or_else(|| Error::Unsupported("an extension without its operation".into()))?;
549        let Some(ex) = op.expand(self.cx, &args) else {
550            return Err(Error::Unsupported(format!(
551                "`{}` has no expansion to reason about",
552                op.name()
553            )));
554        };
555        let outs = ex?;
556        let e = self.cx.id(outs[k])?;
557        self.blast(e)
558    }
559
560    /// Why blasting failed with `e`: [`Unknown::TooLarge`] past `max_nodes`, else the reason
561    /// of an unsupported operation. Other errors are errors.
562    fn unknown(&self, e: Error) -> Result<Unknown, Error> {
563        match e {
564            _ if self.too_large => Ok(Unknown::TooLarge {
565                nodes: self.g.len(),
566            }),
567            Error::Unsupported(why) => Ok(Unknown::Unsupported(why)),
568            e => Err(e),
569        }
570    }
571}
572
573/// Where a SAT-solved question stands.
574struct Search {
575    solver: Solver,
576    cnf: Cnf,
577}
578
579/// A question, blasted and encoded once, then decided in as many steps as its caller likes: a
580/// search that ends on its limits stops where it is, keeping what it learned, and the next
581/// [`solve`](Self::solve) goes on from there. Asking under 5,000 conflicts, then 15,000 more,
582/// costs what asking under 20,000 once does, and answers the same.
583///
584/// ```
585/// use bitwright::prove::{Config, Limits, Outcome, Question};
586/// use bitwright::{Context, ParseOptions, Width};
587///
588/// let mut cx = Context::new();
589/// let o = ParseOptions::width(Width::W16);
590/// let p = cx.parse("x * x != 0x1234", &o)?;
591/// // Without the engine's simplification first, which decides it by residues.
592/// let cfg = Config::default().with_samples(0).with_simplify(false);
593/// let mut q = Question::valid(&mut cx, p, &cfg)?;
594/// let mut out = q.solve(&mut cx, Limits::conflicts(10))?;
595/// while let Outcome::Unknown(why) = &out {
596///     assert!(why.is_budget());
597///     out = q.solve(&mut cx, Limits::conflicts(10))?;
598/// }
599/// assert!(matches!(out, Outcome::Proved(_))); // 0x1234 is not a square mod 2^16
600/// assert!(q.stats().conflicts > 0);
601/// # Ok::<(), bitwright::Error>(())
602/// ```
603pub struct Question {
604    /// The question as asked (with the constraints, as `¬(c1 ∧ …) ∨ p`), checked by evaluation.
605    root: Expr,
606    /// Each symbol of the question with its bits (one the circuit does not read takes its
607    /// declared ones, or 0 without a declaration).
608    symbols: Vec<(SymbolKey, Width, Bits)>,
609    search: Option<Box<Search>>,
610    outcome: Option<Outcome>,
611    stats: Stats,
612}
613
614impl core::fmt::Debug for Question {
615    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
616        f.debug_struct("Question")
617            .field("outcome", &self.outcome)
618            .field("stats", &self.stats)
619            .finish_non_exhaustive()
620    }
621}
622
623impl Question {
624    /// Whether the 1-bit `p` is true for every value of its symbols agreeing with their
625    /// declared known bits.
626    pub fn valid(cx: &mut Context, p: Expr, cfg: &Config) -> Result<Question, Error> {
627        Question::valid_under(cx, p, None, cfg)
628    }
629
630    /// Whether `a` and `b` (of one width) are equal for every value of their symbols agreeing
631    /// with their declared known bits.
632    pub fn equal(cx: &mut Context, a: Expr, b: Expr, cfg: &Config) -> Result<Question, Error> {
633        let (ai, bi) = (cx.id(a)?, cx.id(b)?);
634        if cx.wid(ai) != cx.wid(bi) {
635            return Err(crate::WidthError::Mismatch {
636                left: cx.wid(ai),
637                right: cx.wid(bi),
638            }
639            .into());
640        }
641        let p = cx.c_cmp(crate::CmpOp::Eq, ai, bi)?;
642        let p = cx.handle(p);
643        Question::valid(cx, p, cfg)
644    }
645
646    /// Whether `p` is true wherever the constraints of `assumptions` hold. Complete original
647    /// facts are retained, including partial masks, signed/unsigned
648    /// intervals and unsigned strides; they remain in certificates and counterexample replay.
649    /// The question is simplified (see [`Config::simplify`]), optionally sampled at the word level, then
650    /// blasted, sampled (see [`Config::samples`]) and encoded here; each stage may decide it.
651    pub fn valid_under(
652        cx: &mut Context,
653        p: Expr,
654        assumptions: Option<&Assumptions>,
655        cfg: &Config,
656    ) -> Result<Question, Error> {
657        Self::valid_under_with_pairs(cx, p, assumptions, &[], cfg)
658    }
659
660    /// Ask the original question after applying explicitly supplied, exhaustively checked
661    /// finite-domain pair facts. Verification costs are separate from this query's budget.
662    /// Models are replayed on the original predicate. Certificate requests retain the
663    /// original bit-blast path and do not use these word-level facts.
664    pub fn valid_with_pairs(
665        cx: &mut Context,
666        p: Expr,
667        pairs: &[finite::CheckedPair],
668        cfg: &Config,
669    ) -> Result<Question, Error> {
670        Self::valid_under_with_pairs(cx, p, None, pairs, cfg)
671    }
672
673    /// [`valid_with_pairs`](Self::valid_with_pairs) under explicit assumptions.
674    pub fn valid_under_with_pairs(
675        cx: &mut Context,
676        p: Expr,
677        assumptions: Option<&Assumptions>,
678        pairs: &[finite::CheckedPair],
679        cfg: &Config,
680    ) -> Result<Question, Error> {
681        let pi = cx.id(p)?;
682        if cx.wid(pi) != 1 {
683            return Err(crate::WidthError::Mismatch {
684                left: 1,
685                right: cx.wid(pi),
686            }
687            .into());
688        }
689        let mut q = Question {
690            root: p,
691            symbols: Vec::new(),
692            search: None,
693            outcome: None,
694            stats: Stats::default(),
695        };
696        let decided = |mut q: Question, o: Outcome| {
697            q.outcome = Some(o);
698            Ok(q)
699        };
700        // Exact original constraints, including masks, signed/unsigned bounds and strides.
701        let mut cons: Vec<u32> = Vec::new();
702        if let Some(a) = assumptions {
703            for predicate in a.predicates(cx)? {
704                cons.push(cx.id(predicate)?);
705            }
706        }
707        // The root checked by evaluation: `p` where the constraints hold, as `¬(c1 ∧ …) ∨ p`.
708        let mut root = pi;
709        for &c in &cons {
710            let nc = cx.c_un(crate::UnOp::Not, c)?;
711            root = cx.c_bin(crate::BinOp::Or, nc, root)?;
712        }
713        q.root = cx.handle(root);
714        // The question simplified (not for a certificate, which must be of the question itself).
715        let mut goal_node = pi;
716        if !cfg.certificate {
717            for pair in pairs {
718                let rewritten = pair.simplify(cx, cx.handle(goal_node))?;
719                goal_node = cx.id(rewritten)?;
720            }
721            if cx
722                .const_val(goal_node)
723                .is_some_and(|value| !value.is_zero())
724            {
725                return decided(q, Outcome::Proved(None));
726            }
727        }
728        if cfg.simplify && !cfg.certificate {
729            let run = match assumptions {
730                Some(a) => crate::engine::Run::default().with_assumptions(a),
731                None => crate::engine::Run::default(),
732            };
733            let goal = cx.handle(goal_node);
734            let out = simplifier().run(cx, &[goal], run)?.roots[0];
735            let s = cx.id(out.expr)?;
736            if cx.const_val(s).is_some_and(|v| !v.is_zero()) {
737                return decided(q, Outcome::Proved(None));
738            }
739            // A result that relies on constraints holds where they do, which is all a proof
740            // under them needs.
741            goal_node = s;
742        }
743        let mut word_sampled = false;
744        if (cfg.word_sampling || cfg.sample_mode == SampleMode::Words)
745            && cfg.samples != 0
746            && let Some(trial) = sample_words::try_sample(cx, goal_node, &cons, assumptions, cfg)
747        {
748            q.stats.samples = trial.samples;
749            word_sampled = true;
750            if let Some(mut model) = trial.model {
751                complete_model(cx, q.root, &mut model)?;
752                let outcome = q.refuted(cx, model)?;
753                return decided(q, outcome);
754            }
755            if trial.complete && !cfg.certificate {
756                return decided(q, Outcome::Proved(None));
757            }
758        }
759        // Every legal assignment, when there are few enough. A proof that asks for a
760        // certificate takes it from the circuit below, which is checked independently.
761        let mut exhausted = false;
762        if cfg.exhaustive_inputs != 0
763            && let Some((failure, evaluated)) =
764                sample_words::try_exhaust(cx, goal_node, &cons, assumptions, cfg.exhaustive_inputs)
765        {
766            q.stats.samples += evaluated;
767            match failure {
768                Some(mut model) => {
769                    complete_model(cx, q.root, &mut model)?;
770                    let outcome = q.refuted(cx, model)?;
771                    return decided(q, outcome);
772                }
773                None if !cfg.certificate => return decided(q, Outcome::Proved(None)),
774                None => exhausted = true,
775            }
776        }
777        let constant_goal = cuts::constant_true(cx, goal_node);
778        let mut b = Blaster::new(cx, cfg.max_nodes);
779        b.selector_hints = cfg.selector_branching;
780        b.input_cancellation = cfg.input_cancellation;
781        b.join_factoring = cfg.join_factoring;
782        b.carry_save_multiplication = cfg.carry_save_multiplication;
783        if constant_goal {
784            b.bits.insert(goal_node, vec![aig::TRUE]);
785        }
786        let mut blasted = || -> Result<(L, Vec<L>), Error> {
787            let goal = b.blast(goal_node)?[0];
788            let mut extra = Vec::new();
789            for &c in &cons {
790                extra.push(b.blast(c)?[0]);
791            }
792            Ok((goal, extra))
793        };
794        let (goal, extra) = match blasted() {
795            Ok(r) => r,
796            Err(e) => {
797                let why = b.unknown(e)?;
798                return decided(q, Outcome::Unknown(why));
799            }
800        };
801        q.stats.nodes = b.g.len();
802        if b.g.len() > cfg.max_nodes {
803            let why = Unknown::TooLarge { nodes: b.g.len() };
804            return decided(q, Outcome::Unknown(why));
805        }
806        // Every symbol of the question, with its bits if the circuit reads it.
807        for (s, bits) in &b.symbols {
808            let h = b.cx.handle(*s);
809            let key =
810                b.cx.symbol_id(h)?
811                    .and_then(|id| b.cx.symbol_key(id))
812                    .cloned()
813                    .ok_or_else(|| Error::Contract("a symbol without its key".into()))?;
814            q.symbols.push((key, b.cx.width_of(*s), bits.clone()));
815        }
816        for id in b.cx.symbols_in(&[q.root])? {
817            if let (Some(k), Some(w)) = (b.cx.symbol_key(id).cloned(), b.cx.symbol_width(id))
818                && !q.symbols.iter().any(|(kk, _, _)| *kk == k)
819            {
820                let bits =
821                    b.cx.find_symbol(&k)
822                        .and_then(|e| b.cx.declared_at(e.index()))
823                        .map(|known| blast::konst(&known.known_one()))
824                        .unwrap_or_default();
825                q.symbols.push((k, w, bits));
826            }
827        }
828        if exhausted {
829            let exhaustion = Exhaustion::of(&b.g, goal, &extra);
830            if exhaustion.inputs() <= u32::from(cfg.exhaustive_inputs.min(63)) {
831                let cert = Certificate {
832                    vars: 0,
833                    clauses: Vec::new(),
834                    proof: Vec::new(),
835                    exhaustion: Some(exhaustion),
836                };
837                return decided(q, Outcome::Proved(Some(cert)));
838            }
839        }
840        // Sampled: a lane where the constraints hold and the goal does not refutes it.
841        let batches = if word_sampled {
842            0
843        } else {
844            cfg.samples.div_ceil(64)
845        };
846        for batch in 0..batches {
847            let vals = b.g.eval_words(|k| match cfg.sample_mode {
848                SampleMode::Random => sample(batch, k),
849                SampleMode::Small | SampleMode::Words => small_sample(batch, k),
850            });
851            q.stats.samples += 64;
852            let mut bad = !Aig::word(&vals, goal);
853            for &e in &extra {
854                bad &= Aig::word(&vals, e);
855            }
856            if bad != 0 {
857                let lane = bad.trailing_zeros();
858                let ce = Question::counterexample(&q.symbols, |l| {
859                    (Aig::word(&vals, l) >> lane) & 1 == 1
860                });
861                let o = q.refuted(b.cx, ce)?;
862                return decided(q, o);
863            }
864        }
865        let mut solver = Solver::new();
866        if cfg.certificate {
867            solver.log_proof();
868        }
869        let mut roots = vec![goal];
870        roots.extend_from_slice(&extra);
871        let mut cnf = Cnf::encode_with_xor3(
872            &b.g,
873            &roots,
874            &mut solver,
875            cfg.certificate,
876            cfg.xor3_encoding,
877        );
878        for &e in &extra {
879            cnf.assert(e, &mut solver);
880        }
881        cnf.assert(goal ^ 1, &mut solver);
882        let mut asserted = vec![goal ^ 1];
883        asserted.extend_from_slice(&extra);
884        relations::strengthen(&b.g, &asserted, &cnf, &mut solver, cfg.relational_lemmas);
885        if cfg.selector_branching {
886            for &l in &b.selectors {
887                if let Some(l) = cnf.mapped_lit(l) {
888                    solver.prefer_selector(l);
889                }
890            }
891        }
892        q.stats.vars = solver.num_vars();
893        q.stats.clauses = cnf.emitted();
894        q.stats.learned = solver.num_learnts();
895        q.stats.propagations = solver.propagations;
896        q.search = Some(Box::new(Search { solver, cnf }));
897        Ok(q)
898    }
899
900    /// Searches within `limits` more work (counted from this call): the answer, or
901    /// [`Unknown::Budget`] with the work done on the question so far, and the next call goes on
902    /// from there. Once decided (here or when built), the answer is kept and returned again.
903    /// `cx` is the context the question was built in.
904    pub fn solve(&mut self, cx: &mut Context, limits: Limits) -> Result<Outcome, Error> {
905        if let Some(o) = &self.outcome {
906            return Ok(o.clone());
907        }
908        let Some(s) = self.search.as_mut() else {
909            return Err(Error::Contract(
910                "a question neither decided nor open".into(),
911            ));
912        };
913        let answer = s.solver.solve_within(limits);
914        self.stats.learned = s.solver.num_learnts();
915        self.stats.conflicts = s.solver.conflicts;
916        self.stats.decisions = s.solver.decisions;
917        self.stats.propagations = s.solver.propagations;
918        let o = match answer {
919            Answer::Unknown => {
920                return Ok(Outcome::Unknown(Unknown::Budget {
921                    conflicts: s.solver.conflicts,
922                    propagations: s.solver.propagations,
923                }));
924            }
925            Answer::Unsat => {
926                let cert = s.solver.take_proof().map(|proof| Certificate {
927                    vars: s.solver.num_vars(),
928                    clauses: core::mem::take(&mut s.cnf.clauses),
929                    proof,
930                    exhaustion: None,
931                });
932                Outcome::Proved(cert)
933            }
934            Answer::Sat(model) => {
935                let cnf = &s.cnf;
936                let ce = Question::counterexample(&self.symbols, |l| cnf.value(l, &model));
937                self.refuted(cx, ce)?
938            }
939        };
940        self.search = None;
941        self.outcome = Some(o.clone());
942        Ok(o)
943    }
944
945    /// The answer, once decided.
946    pub fn outcome(&self) -> Option<&Outcome> {
947        self.outcome.as_ref()
948    }
949
950    /// The work done on the question so far.
951    pub fn stats(&self) -> Stats {
952        self.stats
953    }
954
955    /// The counterexample an assignment of the circuit's inputs gives.
956    fn counterexample(symbols: &[(SymbolKey, Width, Bits)], bit: impl Fn(L) -> bool) -> Model {
957        symbols
958            .iter()
959            .map(|(k, w, bits)| {
960                let mut limbs = [0u64; 8];
961                for (j, &b) in bits.iter().enumerate() {
962                    if bit(b) {
963                        limbs[j / 64] |= 1 << (j % 64);
964                    }
965                }
966                (k.clone(), BitVec::wrapping_from_limbs(*w, &limbs))
967            })
968            .collect()
969    }
970
971    /// `Refuted(ce)`, checked by bitwright's evaluator: a solver or blaster bug cannot produce a
972    /// false refutation unnoticed.
973    fn refuted(&self, cx: &mut Context, ce: Model) -> Result<Outcome, Error> {
974        let env =
975            crate::FnEnv(|k: &SymbolKey, _| ce.iter().find(|(kk, _)| kk == k).map(|(_, v)| *v));
976        let v = cx.eval(&[self.root], &env)?[0];
977        if v.is_zero() {
978            Ok(Outcome::Refuted(ce))
979        } else {
980            Err(Error::Contract(format!(
981                "the prover's counterexample does not refute {}",
982                cx.display(self.root)
983            )))
984        }
985    }
986}
987
988/// Simplification can omit original symbols: supply values consistent with their declarations
989/// before replaying the original constrained predicate.
990fn complete_model(cx: &mut Context, root: Expr, model: &mut Model) -> Result<(), Error> {
991    for id in cx.symbols_in(&[root])? {
992        let key = cx.symbol_key(id).unwrap().clone();
993        if !model.iter().any(|(k, _)| *k == key) {
994            let symbol = cx.find_symbol(&key).unwrap();
995            let value = cx.declared_known(symbol)?.map_or_else(
996                || BitVec::zero(cx.symbol_width(id).unwrap()),
997                |k| k.known_one(),
998            );
999            model.push((key, value));
1000        }
1001    }
1002    Ok(())
1003}
1004
1005/// The 64 consecutive assignments `64*batch .. 64*batch+64`, transposed into input words.
1006fn small_sample(batch: u32, k: u32) -> u64 {
1007    const LOW: [u64; 6] = [
1008        0xaaaa_aaaa_aaaa_aaaa,
1009        0xcccc_cccc_cccc_cccc,
1010        0xf0f0_f0f0_f0f0_f0f0,
1011        0xff00_ff00_ff00_ff00,
1012        0xffff_0000_ffff_0000,
1013        0xffff_ffff_0000_0000,
1014    ];
1015    if k < 6 {
1016        LOW[k as usize]
1017    } else if k - 6 < 32 && (batch >> (k - 6)) & 1 == 1 {
1018        u64::MAX
1019    } else {
1020        0
1021    }
1022}
1023
1024/// Input `k`'s word in sample batch `batch` (splitmix64 of the pair); batch 0's lanes 0 and 1
1025/// are all zeros and all ones.
1026fn sample(batch: u32, k: u32) -> u64 {
1027    let mut z = (u64::from(batch) << 32 | u64::from(k)).wrapping_add(0x9e37_79b9_7f4a_7c15);
1028    z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
1029    z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
1030    z ^= z >> 31;
1031    if batch == 0 { (z & !0b11) | 0b10 } else { z }
1032}
1033
1034/// Whether the 1-bit `p` is true for every value of its symbols agreeing with their declared
1035/// known bits.
1036pub fn valid(cx: &mut Context, p: Expr, cfg: &Config) -> Result<Outcome, Error> {
1037    valid_under(cx, p, None, cfg)
1038}
1039
1040/// Whether `p` is true wherever the constraints of `assumptions` and the symbols' declared
1041/// known bits hold.
1042pub fn valid_under(
1043    cx: &mut Context,
1044    p: Expr,
1045    assumptions: Option<&Assumptions>,
1046    cfg: &Config,
1047) -> Result<Outcome, Error> {
1048    Question::valid_under(cx, p, assumptions, cfg)?.solve(cx, cfg.limits())
1049}
1050
1051/// Whether `a` and `b` (of one width) are equal for every value of their symbols agreeing with
1052/// their declared known bits.
1053pub fn equal(cx: &mut Context, a: Expr, b: Expr, cfg: &Config) -> Result<Outcome, Error> {
1054    Question::equal(cx, a, b, cfg)?.solve(cx, cfg.limits())
1055}
1056
1057/// Values of symbols, as a counterexample or a model gives them.
1058pub type Model = Vec<(SymbolKey, BitVec)>;
1059
1060/// A value of the symbols that makes the 1-bit `p` true, if there is one: `Refuted` of `¬p`
1061/// read back as a model (`Ok(Some)`), `Ok(None)` when `p` is unsatisfiable, and `Err` with the
1062/// reason when not decided.
1063pub fn satisfy(
1064    cx: &mut Context,
1065    p: Expr,
1066    cfg: &Config,
1067) -> Result<Result<Option<Model>, Unknown>, Error> {
1068    let np = cx.un(crate::UnOp::Not, p)?;
1069    Ok(match valid(cx, np, cfg)? {
1070        Outcome::Proved(_) => Ok(None),
1071        Outcome::Refuted(m) => Ok(Some(m)),
1072        Outcome::Unknown(why) => Err(why),
1073    })
1074}