Skip to main content

rumba_core/
expr.rs

1use std::{
2    cmp::max,
3    fmt::{self, Display},
4    ops::{Add, BitAnd, BitOr, BitXor, Mul, Neg, Not, Sub},
5    vec,
6};
7
8use rand::random_range;
9use rustc_hash::FxHashSet as HashSet;
10
11use crate::varint::{VarInt, make_mask};
12
13#[cfg(feature = "jit")]
14use crate::jit;
15
16/// Identifies a variable within an [`Expr`] by its index.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct VarId(pub usize);
19
20impl Display for VarId {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        f.write_fmt(format_args!("{}", self.0))
23    }
24}
25
26impl From<usize> for VarId {
27    fn from(value: usize) -> Self {
28        VarId(value)
29    }
30}
31
32/// A Mixed Boolean-Arithmetic expression tree.
33#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum Expr {
35    /// A variable, referenced by its [`VarId`].
36    Var(VarId),
37
38    /// A constant value.
39    Const(u64),
40
41    // Unary
42    /// Bitwise complement (`~e`).
43    Not(Box<Expr>),
44    /// Multiplication of an expression by a constant coefficient.
45    Scale(u64, Box<Expr>),
46
47    // Bitwise
48    /// Bitwise AND of all operands.
49    And(Vec<Expr>),
50    /// Bitwise OR of all operands.
51    Or(Vec<Expr>),
52    /// Bitwise XOR of all operands.
53    Xor(Vec<Expr>),
54
55    // Arithmetic
56    /// Arithmetic sum of all operands.
57    Add(Vec<Expr>),
58    /// Arithmetic product of all operands.
59    Mul(Vec<Expr>),
60}
61
62impl Not for Expr {
63    type Output = Expr;
64
65    fn not(self) -> Self::Output {
66        Expr::Not(Box::new(self))
67    }
68}
69
70impl BitXor for Expr {
71    type Output = Expr;
72
73    fn bitxor(self, rhs: Self) -> Self::Output {
74        Expr::Xor(vec![self, rhs])
75    }
76}
77
78impl BitAnd for Expr {
79    type Output = Expr;
80
81    fn bitand(self, rhs: Self) -> Self::Output {
82        Expr::And(vec![self, rhs])
83    }
84}
85
86impl BitOr for Expr {
87    type Output = Expr;
88
89    fn bitor(self, rhs: Self) -> Self::Output {
90        Expr::Or(vec![self, rhs])
91    }
92}
93
94impl Add for Expr {
95    type Output = Expr;
96
97    fn add(self, rhs: Self) -> Self::Output {
98        Expr::Add(vec![self, rhs])
99    }
100}
101
102impl Sub for Expr {
103    type Output = Expr;
104
105    fn sub(self, rhs: Self) -> Self::Output {
106        Expr::Add(vec![self, -rhs])
107    }
108}
109
110impl Mul for Expr {
111    type Output = Expr;
112
113    fn mul(self, rhs: Self) -> Self::Output {
114        Expr::Mul(vec![self, rhs])
115    }
116}
117
118impl Mul<Expr> for u64 {
119    type Output = Expr;
120
121    fn mul(self, rhs: Expr) -> Self::Output {
122        Expr::scale(self, rhs)
123    }
124}
125
126impl Neg for Expr {
127    type Output = Expr;
128
129    fn neg(self) -> Self::Output {
130        Expr::Scale(u64::MAX, Box::new(self))
131    }
132}
133
134impl fmt::Display for Expr {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        f.write_str(&self.repr_masked(64, u64::MAX, true, false))
137    }
138}
139
140impl From<u64> for Expr {
141    fn from(value: u64) -> Self {
142        Expr::Const(value)
143    }
144}
145
146impl Expr {
147    /// Builds a constant expression from `c`.
148    pub fn make_const(c: u64) -> Self {
149        Expr::Const(c)
150    }
151
152    /// An null expression
153    pub const fn zero() -> Self {
154        Expr::Const(0)
155    }
156
157    pub(crate) fn scale(c: u64, e: Expr) -> Expr {
158        match c {
159            0 => Expr::zero(),
160            1 => e,
161            _ => Expr::Scale(c, Box::new(e)),
162        }
163    }
164
165    /// The common scalar factor of a linear term: `c` for `Scale(c, _)`, and the
166    /// shared `c` for an `Add` whose terms are all `Scale(c, _)` (a bare term
167    /// counts as `c = 1`); `1` otherwise.
168    ///
169    /// `reduce` distributes a scalar over a sum, so `2·(x + y)` is stored as
170    /// `2·x + 2·y`; this recovers the `2` that is no longer syntactically
171    /// present, letting callers see `x + y`, `2·x + 2·y` and `-x - y` as scalar
172    /// multiples of the same core.
173    pub(crate) fn get_factor(&self, mask: u64) -> u64 {
174        let term_factor = |t: &Expr| match t {
175            Expr::Scale(c, _) => c & mask,
176            _ => 1,
177        };
178        match self {
179            Expr::Scale(c, _) => c & mask,
180            Expr::Add(terms) => {
181                let f = term_factor(&terms[0]);
182                if terms.iter().all(|t| term_factor(t) == f) {
183                    f
184                } else {
185                    1
186                }
187            }
188            _ => 1,
189        }
190    }
191
192    /// Counts the number of nodes in the expression
193    pub fn size(&self) -> usize {
194        let children_size = match self {
195            Expr::Var(_) | Expr::Const(_) => 0,
196
197            Expr::Not(expr) | Expr::Scale(_, expr) => expr.size(),
198
199            Expr::And(exprs)
200            | Expr::Or(exprs)
201            | Expr::Xor(exprs)
202            | Expr::Add(exprs)
203            | Expr::Mul(exprs) => exprs.iter().map(|e| e.size()).sum(),
204        };
205
206        children_size + 1
207    }
208
209    /// Evaluates the expression with the given variable values
210    pub(crate) fn eval_bits(&self, vars: &[u64]) -> VarInt {
211        match self {
212            Expr::Var(i) => vars[i.0].into(),
213
214            Expr::Const(c) => (*c).into(),
215
216            Expr::And(exprs) => exprs
217                .iter()
218                .map(|e| e.eval_bits(vars))
219                .fold(VarInt::MAX, |x, y| x & y),
220
221            Expr::Or(exprs) => exprs
222                .iter()
223                .map(|e| e.eval_bits(vars))
224                .fold(VarInt::ZERO, |x, y| x | y),
225
226            Expr::Xor(exprs) => exprs
227                .iter()
228                .map(|e| e.eval_bits(vars))
229                .fold(VarInt::ZERO, |x, y| x ^ y),
230
231            Expr::Add(exprs) => exprs
232                .iter()
233                .map(|e| e.eval_bits(vars))
234                .fold(VarInt::ZERO, |x, y| x + y),
235
236            Expr::Mul(exprs) => exprs
237                .iter()
238                .map(|e| e.eval_bits(vars))
239                .fold(VarInt::ONE, |x, y| x * y),
240
241            Expr::Scale(v, e) => VarInt::from(*v) * e.eval_bits(vars),
242
243            Expr::Not(e) => !e.eval_bits(vars),
244        }
245    }
246
247    /// Evaluates the expression for the given variable values on `n` bits.
248    pub fn eval(&self, vars: &[u64], n: u8) -> u64 {
249        self.eval_bits(vars).get(make_mask(n))
250    }
251
252    /// Checks, by random sampling on `n` bits, whether two expressions are
253    /// semantically equal. On a counterexample returns the sampled variable
254    /// values and the two differing evaluations.
255    pub fn sem_equal(
256        &self,
257        other: &Expr,
258        n: u8,
259        samples: usize,
260    ) -> Result<(), (Vec<u64>, u64, u64)> {
261        self.sem_equal_masked(other, make_mask(n), samples)
262    }
263
264    /// The truth table of this expression over its first `t` variables on `n`
265    /// bits (one entry per assignment of the `t` variables to 0/1).
266    pub fn truth_table(&self, t: usize, n: u8) -> Vec<u64> {
267        self.truth_table_masked(t, make_mask(n))
268    }
269
270    /// Checks if two expressions are semantically equal
271    /// In case of error returns the variables that caused the error
272    /// as well as the evaluations of self and other
273    pub(crate) fn sem_equal_masked(
274        &self,
275        other: &Expr,
276        mask: u64,
277        count: usize,
278    ) -> Result<(), (Vec<u64>, u64, u64)> {
279        let t = max(
280            self.get_vars()
281                .iter()
282                .copied()
283                .map(|v| v.0)
284                .max()
285                .unwrap_or(0),
286            other
287                .get_vars()
288                .iter()
289                .copied()
290                .map(|v| v.0)
291                .max()
292                .unwrap_or(0),
293        );
294
295        for _ in 0..count {
296            let vars: Vec<_> = (0..=t).map(|_| random_range(0..=mask)).collect();
297
298            let v1 = self.eval_bits(&vars).get(mask);
299            let v2 = other.eval_bits(&vars).get(mask);
300
301            if v1 != v2 {
302                return Err((vars, v1, v2));
303            }
304        }
305
306        Ok(())
307    }
308
309    /// Calculates the truth table of an expression on n values with t variables
310    pub(crate) fn truth_table_masked(&self, t: usize, mask: u64) -> Vec<u64> {
311        // if t > 20 {
312        //     panic!("CRAZYY");
313        // }
314
315        let size = 1usize << t;
316        let mut tt = Vec::with_capacity(size);
317
318        let mut vars = vec![0u64; t];
319
320        // Decode i into 2^t binary values (one value per variable)
321        let vars_from_i = |i: usize, vars: &mut [u64]| {
322            let mut idx = i;
323            for var in vars {
324                *var = (idx & 1) as u64;
325                idx >>= 1;
326            }
327        };
328
329        #[cfg(feature = "jit")]
330        {
331            // Compiling costs ~169us; a compiled evaluation is ~4.7ns against
332            // ~114ns interpreted (see `benches/bench.rs`). So the JIT pays for
333            // itself past 169us / (114ns - 4.7ns) ~= 1546 evaluations, i.e.
334            // from t = 11 (2048) up. At t = 10 (1024) it is still a net loss.
335            if t > 10 {
336                let jit_fn = jit::compile(self);
337
338                for i in 0..size {
339                    vars_from_i(i, &mut vars);
340                    tt.push(jit_fn.eval(&vars) & mask);
341                }
342
343                return tt;
344            }
345        }
346
347        for i in 0..size {
348            vars_from_i(i, &mut vars);
349            tt.push(self.eval_bits(&vars).get(mask));
350        }
351
352        tt
353    }
354
355    /// Calls a function recursively on each node of an expression
356    pub(crate) fn visit<T, F>(&self, mut f: F) -> T
357    where
358        F: FnMut(&Expr, Vec<T>) -> T + Clone,
359    {
360        match self {
361            Expr::Var(_) | Expr::Const(_) => f(self, vec![]),
362
363            Expr::Not(expr) | Expr::Scale(_, expr) => {
364                let v = vec![expr.visit::<T, F>(f.clone())];
365                f(self, v)
366            }
367
368            Expr::And(exprs)
369            | Expr::Or(exprs)
370            | Expr::Xor(exprs)
371            | Expr::Add(exprs)
372            | Expr::Mul(exprs) => {
373                let v = exprs.iter().map(|e| e.visit(f.clone())).collect();
374                f(self, v)
375            }
376        }
377    }
378
379    /// Counts the number of variables in the expression
380    pub fn get_vars(&self) -> HashSet<VarId> {
381        self.visit(|e, children: Vec<HashSet<VarId>>| {
382            let mut acc: HashSet<VarId> = children.into_iter().flatten().collect();
383            if let Expr::Var(v) = e {
384                acc.insert(*v);
385            }
386            acc
387        })
388    }
389
390    // Operator precedence
391    // https://en.cppreference.com/w/c/language/operator_precedence.html
392    fn precedence(&self) -> usize {
393        match self {
394            // A single-element collection is semantically its sole child, so it
395            // binds exactly as tightly (see also `repr_masked`, which renders
396            // straight through it).
397            Expr::And(e) | Expr::Or(e) | Expr::Xor(e) | Expr::Add(e) | Expr::Mul(e)
398                if e.len() == 1 =>
399            {
400                e[0].precedence()
401            }
402
403            Expr::Var(_) | Expr::Const(_) => 0,
404
405            Expr::Not(_) => 2,
406
407            Expr::Mul(_) | Expr::Scale(_, _) => 3,
408
409            Expr::Add(_) => 4,
410
411            Expr::And(_) => 8,
412
413            Expr::Xor(_) => 9,
414
415            Expr::Or(_) => 10,
416        }
417    }
418
419    /// Parenthesizes an expression if needed
420    fn parenthesize(&self, parent: &Expr, s: String) -> String {
421        // Strict `<`: every operator here is flat and associative/commutative,
422        // so a same-precedence child never needs grouping. This also drops the
423        // cosmetic parens around a degenerate single-child node (e.g. `(v1)`).
424        if parent.precedence() < self.precedence() {
425            format!("({})", s)
426        } else {
427            s
428        }
429    }
430
431    pub(crate) fn symbol(&self, latex: bool) -> &str {
432        match (self, latex) {
433            (Expr::Var(_), _) | (Expr::Const(_), _) => "",
434
435            (Expr::Not(_), true) => "\\neg",
436            (Expr::Not(_), false) => "~",
437
438            (Expr::Scale(_, _), true) => "\\cdot",
439            (Expr::Scale(_, _), false) => "*",
440
441            (Expr::And(_), true) => "\\land",
442            (Expr::And(_), false) => "&",
443
444            (Expr::Or(_), true) => "\\lor",
445            (Expr::Or(_), false) => "|",
446
447            (Expr::Xor(_), true) => "\\oplus",
448            (Expr::Xor(_), false) => "^",
449
450            (Expr::Add(_), _) => "+",
451
452            (Expr::Mul(_), true) => "\\cdot",
453            (Expr::Mul(_), false) => "*",
454        }
455    }
456
457    /// A string representation of this expression
458    pub(crate) fn repr_masked(&self, n: u8, mask: u64, hex: bool, latex: bool) -> String {
459        let recurs = |e: &Expr| e.parenthesize(self, e.repr_masked(n, mask, hex, latex));
460
461        let join = |exprs: &Vec<Expr>, c: &str| {
462            exprs
463                .iter()
464                .map(recurs)
465                .collect::<Vec<String>>()
466                .join(c)
467                .to_string()
468        };
469
470        // A single-element collection node is just its child; render through it
471        // so no spurious `(v1)` wrapper appears.
472        if let Expr::And(e) | Expr::Or(e) | Expr::Xor(e) | Expr::Add(e) | Expr::Mul(e) = self
473            && e.len() == 1
474        {
475            return e[0].repr_masked(n, mask, hex, latex);
476        }
477
478        match self {
479            Expr::Var(v) => {
480                if latex {
481                    format!("v_{{{}}}", v.0)
482                } else {
483                    format!("v{}", v.0)
484                }
485            }
486
487            Expr::Const(c) => VarInt::from(*c).repr(n, mask, hex, latex),
488
489            Expr::Scale(c, expr) => format!(
490                "{} {} {}",
491                VarInt::from(*c).repr(n, mask, hex, latex),
492                self.symbol(latex),
493                recurs(expr)
494            ),
495
496            Expr::Not(expr) => {
497                format!("{} {}", self.symbol(latex), recurs(expr))
498            }
499
500            Expr::Add(exprs) => {
501                let star = if latex { "\\cdot" } else { "*" };
502                let sign_bit = 1u64 << (n - 1);
503
504                // Splits a term into (is_negative, magnitude). A negative
505                // constant or a `Scale` with a negative coefficient is folded
506                // into a subtraction so the sum reads `a - b` rather than
507                // `a + (-b)`. Everything else keeps its normal, unsigned repr.
508                let term = |e: &Expr| -> (bool, String) {
509                    match e {
510                        Expr::Const(c) if c & mask & sign_bit != 0 => {
511                            let m = VarInt::from(c.wrapping_neg());
512                            (true, m.repr(n, mask, hex, latex))
513                        }
514                        Expr::Scale(c, inner) if c & mask & sign_bit != 0 => {
515                            let m = c.wrapping_neg() & mask;
516                            // The magnitude sits in a product context, so the
517                            // inner expression is parenthesized against `Mul`.
518                            let is = inner.parenthesize(
519                                &Expr::Mul(vec![]),
520                                inner.repr_masked(n, mask, hex, latex),
521                            );
522                            if m == 1 {
523                                (true, is)
524                            } else {
525                                let cs = VarInt::from(m).repr(n, mask, hex, latex);
526                                (true, format!("{cs} {star} {is}"))
527                            }
528                        }
529                        _ => (false, recurs(e)),
530                    }
531                };
532
533                let mut out = String::new();
534                for (i, e) in exprs.iter().enumerate() {
535                    let (neg, s) = term(e);
536                    if i == 0 {
537                        out.push_str(&if neg { format!("-{s}") } else { s });
538                    } else {
539                        out.push_str(if neg { " - " } else { " + " });
540                        out.push_str(&s);
541                    }
542                }
543                out
544            }
545
546            Expr::And(exprs) | Expr::Or(exprs) | Expr::Xor(exprs) | Expr::Mul(exprs) => {
547                join(exprs, &format!(" {} ", self.symbol(latex)))
548            }
549        }
550    }
551
552    /// A string representation of this expression on `n` bits.
553    pub fn repr(&self, n: u8, hex: bool, latex: bool) -> String {
554        self.repr_masked(n, make_mask(n), hex, latex)
555    }
556
557    /// Whether this expression is purely bitwise (no arithmetic operators).
558    pub fn is_bitwise(&self) -> bool {
559        match self {
560            Expr::Var(_) => true,
561
562            Expr::Not(expr) => expr.is_bitwise(),
563
564            Expr::And(exprs) | Expr::Or(exprs) | Expr::Xor(exprs) => {
565                exprs.iter().all(|e| e.is_bitwise())
566            }
567
568            _ => false,
569        }
570    }
571
572    /// Whether every variable in this expression is contained in `allowed_vars`.
573    pub fn variables_in(&self, allowed_vars: &Vec<usize>) -> bool {
574        match self {
575            Expr::Const(_) => true,
576
577            Expr::Var(i) => allowed_vars.contains(&i.0),
578
579            Expr::Not(expr) | Expr::Scale(_, expr) => expr.variables_in(allowed_vars),
580
581            Expr::And(exprs)
582            | Expr::Or(exprs)
583            | Expr::Xor(exprs)
584            | Expr::Add(exprs)
585            | Expr::Mul(exprs) => exprs.iter().all(|e| e.variables_in(allowed_vars)),
586        }
587    }
588
589    // Replaces a given var with another expression
590    pub(crate) fn replace_var(self, target_var: VarId, replacement: &Expr) -> Self {
591        match self {
592            Expr::Var(v) if v == target_var => replacement.clone(),
593            _ => self.map(|e| e.replace_var(target_var, replacement)),
594        }
595    }
596
597    // Helper recusive function that needs a reference
598    pub(crate) fn map<F>(self, mut f: F) -> Self
599    where
600        F: FnMut(Self) -> Self,
601    {
602        let vec_map = |exprs: Vec<Expr>, f: F| exprs.into_iter().map(f).collect();
603
604        match self {
605            Expr::Var(_) | Expr::Const(_) => self,
606
607            Expr::Not(expr) => !f(*expr),
608            Expr::Scale(v, expr) => v * f(*expr),
609
610            Expr::And(exprs) => Expr::And(vec_map(exprs, f)),
611            Expr::Or(exprs) => Expr::Or(vec_map(exprs, f)),
612            Expr::Xor(exprs) => Expr::Xor(vec_map(exprs, f)),
613            Expr::Add(exprs) => Expr::Add(vec_map(exprs, f)),
614            Expr::Mul(exprs) => Expr::Mul(vec_map(exprs, f)),
615        }
616    }
617
618    /// [`Expr::map`] for a fallible transform: rebuilds the node from mapped
619    /// children, short-circuiting on the first error. Lets the solver's
620    /// recursive rewrites return `Result` without hand-rolling the match at
621    /// each site.
622    pub(crate) fn try_map<F, E>(self, mut f: F) -> Result<Self, E>
623    where
624        F: FnMut(Self) -> Result<Self, E>,
625    {
626        fn vec_try_map<F, E>(exprs: Vec<Expr>, f: F) -> Result<Vec<Expr>, E>
627        where
628            F: FnMut(Expr) -> Result<Expr, E>,
629        {
630            exprs.into_iter().map(f).collect()
631        }
632
633        Ok(match self {
634            Expr::Var(_) | Expr::Const(_) => self,
635
636            Expr::Not(expr) => !f(*expr)?,
637            Expr::Scale(v, expr) => v * f(*expr)?,
638
639            Expr::And(exprs) => Expr::And(vec_try_map(exprs, f)?),
640            Expr::Or(exprs) => Expr::Or(vec_try_map(exprs, f)?),
641            Expr::Xor(exprs) => Expr::Xor(vec_try_map(exprs, f)?),
642            Expr::Add(exprs) => Expr::Add(vec_try_map(exprs, f)?),
643            Expr::Mul(exprs) => Expr::Mul(vec_try_map(exprs, f)?),
644        })
645    }
646}