Skip to main content

sv_sim_sim/constraints/
array.rs

1//! Array constraints (plan C11, C12, C14; LRM 18.4, 18.5.7).
2//!
3//! The builder turns `A.size`, `A[i]`, `foreach (A[i]) ...` and `A.sum()
4//! with (...)` into array-level IR ([`ArrayNode`]) that does not know how many
5//! elements `A` has. Two steps at `randomize()` time turn that into the plain
6//! scalar IR the BDD solver understands:
7//!
8//! 1. **Sizes** ([`solve_sizes`]). LRM 18.4: "a constraint on the size of a
9//!    dynamic array or queue ... resizes the array"; an array whose size no
10//!    constraint mentions keeps its size. Every `rand`-active array whose
11//!    `size` appears in a constraint with no element/loop reference gets a
12//!    32-bit size variable, and the constraints connected to it (through shared
13//!    variables) form a small sub-problem solved first.
14//! 2. **Expansion** ([`expand`]). With the sizes fixed, `foreach` unrolls,
15//!    reductions fold, `size` is a constant and every element of a `rand`-
16//!    active array is a fresh random variable (LRM 18.4: the elements are
17//!    all randomized); an element of an inactive or non-`rand` array is a
18//!    state variable holding its current value.
19//!
20//! The size sub-problem cannot see element constraints, so a size that
21//! satisfies it may leave the full problem unsatisfiable: the caller blocks
22//! that size tuple ([`solve_sizes`]'s `blocked`) and asks again, up to
23//! [`MAX_SIZE_RETRIES`] times (an exhausted search is its own loud outcome,
24//! not "unsatisfiable").
25//!
26//! # Limits (stated, not silent)
27//!
28//! * An array size is drawn uniformly from the sizes that satisfy its
29//!   constraints and are at most [`SIZE_TIERS`]`[0]` (1024); if there are
30//!   none, at most `SIZE_TIERS[1]` (65536). A constraint that only allows
31//!   larger sizes is [`SizeError::TooLarge`], never a silent clamp.
32//! * An element index must not depend on a random variable (the builder
33//!   rejects those); after loop-variable substitution it must be a constant.
34//! * An index outside `0..size` where the constraint is live is
35//!   [`ExpandError::IndexOutOfRange`]; one in an arm a constant guard
36//!   (`if (i < A.size-1) A[i] < A[i+1]`) never selects is not evaluated.
37
38use super::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
39use super::func::{self, Func, FuncError};
40use super::guard::{self, GuardValue};
41use super::ir::{
42    ArrayId, ArrayNode, Assignment, BinOp, ConstraintEvalError, Expr, LoopId, ReduceOp, Ty, UnOp,
43    VarId, eval, mask,
44};
45use super::solve::{SolveError, Solver};
46use num_bigint::BigUint;
47use num_traits::{One, Zero};
48use std::collections::HashMap;
49
50/// Size tiers tried in order (module docs).
51pub const SIZE_TIERS: [u64; 2] = [1024, 65536];
52
53/// How many size tuples [`solve_sizes`] is asked to block before the search
54/// is declared exhausted.
55pub const MAX_SIZE_RETRIES: usize = 256;
56
57/// One array of a program as seen by one `randomize()` call.
58#[derive(Clone, Debug)]
59pub struct ArrayState {
60    /// Element type (the solver's 2-state view).
61    pub elem_ty: Ty,
62    /// Whether the array's elements (and size) are being randomized this call:
63    /// a `rand` property whose `rand_mode` bit is set.
64    pub active: bool,
65    /// The current elements; `None` is an element with an X/Z bit.
66    pub cur: Vec<Option<BigUint>>,
67    /// The enum values an element must be one of (a `rand` enum array).
68    pub domain: Option<Vec<BigUint>>,
69}
70
71/// Why [`expand`] failed.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum ExpandError {
74    /// An element index outside `0..size` in a live constraint.
75    IndexOutOfRange {
76        /// The array.
77        array: ArrayId,
78        /// The index (may be negative).
79        index: i64,
80        /// The array's size in this call.
81        size: usize,
82    },
83    /// An index outside the elements of a fixed-size array held in a class
84    /// property ([`ArrayNode::Pick`]) in a live constraint.
85    LeafIndexOutOfRange {
86        /// The index (may be negative).
87        index: i64,
88        /// The array's size.
89        size: usize,
90    },
91    /// An element index that is not a constant once the loop variables are
92    /// substituted.
93    NonConstantIndex,
94    /// A guard of an implication or `if` constraint is `ERROR` (LRM 18.5.12).
95    GuardError,
96    /// A state an index or constant guard reads is X/Z.
97    StateUnknown(u32),
98    /// A function called from a constraint failed on fixed arguments.
99    Func(FuncError),
100    /// A weight or bound of a `dist` inside a `foreach` over a dynamic array
101    /// reads a random element once the loop is unrolled (LRM 18.5.3: weights
102    /// and bounds are fixed values).
103    DistRandomBound,
104    /// The array-level IR is malformed (a builder bug).
105    Malformed(&'static str),
106}
107
108/// The result of [`expand`].
109#[derive(Clone, Debug, Default)]
110pub struct Expanded {
111    /// Scalar constraints over the field variables `0..n_vars0` and the element
112    /// variables `n_vars0..`.
113    pub conjuncts: Vec<Expr>,
114    /// Element variable `n_vars0 + k` is `(array, index)` of `elem_vars[k]`.
115    pub elem_vars: Vec<(ArrayId, usize)>,
116    /// Types of the element variables.
117    pub elem_tys: Vec<Ty>,
118    /// State values appended after the caller's states (an element of an
119    /// inactive or non-`rand` array).
120    pub extra_states: Vec<Option<BigUint>>,
121    /// The instances of the program's `dist` templates ([`Dist::subject`]),
122    /// one per iteration that met one. The instance `k` has the selector
123    /// `SELECTOR_BASE + n_program_dists + k`, and its variable ids are already
124    /// the expansion's (element variables numbered after the field variables).
125    pub extra_dists: Vec<Dist>,
126}
127
128struct Expander<'a> {
129    arrays: &'a [ArrayState],
130    sizes: &'a [usize],
131    /// `elem_base[a]`: the variable id of element 0 of active array `a`.
132    elem_base: Vec<u32>,
133    asg: Assignment,
134    synth: HashMap<(ArrayId, usize), u32>,
135    env: Vec<(LoopId, Expr)>,
136    funcs: &'a [Func],
137    /// The program's dists; those with a `subject` are templates.
138    dists: &'a [Dist],
139    extra_dists: Vec<Dist>,
140    /// The instance made for a template under a loop binding: the template's
141    /// index, the bindings, and the instance's index in `extra_dists`. A
142    /// template's selector occurs once per item code in its membership
143    /// conjunct; all of them name one instance.
144    instanced: Vec<InstancedDist>,
145}
146
147/// A template dist's instance: template index, loop bindings, index in `extra_dists`.
148type InstancedDist = (usize, Vec<(LoopId, Expr)>, usize);
149
150fn konst(t: Ty, v: u64) -> Expr {
151    Expr::constant(t, v)
152}
153
154fn has_vars(e: &Expr) -> bool {
155    let mut any = false;
156    e.for_each_var(&mut |_, _| any = true);
157    any
158}
159
160impl Expander<'_> {
161    fn array(&self, a: ArrayId) -> Result<&ArrayState, ExpandError> {
162        self.arrays.get(a as usize).ok_or(ExpandError::Malformed(
163            "an array id outside the program's table",
164        ))
165    }
166
167    fn size_of(&self, a: ArrayId) -> Result<usize, ExpandError> {
168        self.sizes
169            .get(a as usize)
170            .copied()
171            .ok_or(ExpandError::Malformed("no size for an array"))
172    }
173
174    /// The constant value of the (var-free) expression `e`.
175    fn const_value(&self, e: &Expr) -> Result<BigUint, ExpandError> {
176        if has_vars(e) {
177            return Err(ExpandError::NonConstantIndex);
178        }
179        eval(e, &self.asg).map_err(|err| match err {
180            ConstraintEvalError::StateUnknown(s) => ExpandError::StateUnknown(s),
181            _ => ExpandError::NonConstantIndex,
182        })
183    }
184
185    /// The value of the index expression `index` (expanded here), signed per
186    /// its type; it must be free of random variables.
187    fn index_value(&mut self, index: &Expr) -> Result<i64, ExpandError> {
188        let idx = self.expand(index)?;
189        let v = self.const_value(&idx)?;
190        let t = idx.ty();
191        Ok(if t.signed && v.bit(u64::from(t.width) - 1) {
192            -i64::try_from((BigUint::one() << t.width) - &v).unwrap_or(i64::MAX)
193        } else {
194            i64::try_from(&v).unwrap_or(i64::MAX)
195        })
196    }
197
198    /// Element `idx` of array `a`: a random variable, or a state variable
199    /// holding its current value.
200    fn element(&mut self, a: ArrayId, idx: usize, ty: Ty) -> Result<Expr, ExpandError> {
201        let arr = self.array(a)?;
202        if arr.elem_ty != ty {
203            return Err(ExpandError::Malformed("an element read at the wrong type"));
204        }
205        let (active, cur) = (arr.active, arr.cur.get(idx).cloned());
206        if active {
207            let id = self.elem_base[a as usize] + u32::try_from(idx).expect("an element index");
208            return Ok(Expr::Var(id, ty));
209        }
210        let Some(cur) = cur else {
211            return Err(ExpandError::Malformed("an element of a stale array view"));
212        };
213        if let Some(&id) = self.synth.get(&(a, idx)) {
214            return Ok(Expr::State(id, ty));
215        }
216        let id = u32::try_from(self.asg.states.len()).expect("a state id");
217        self.asg.states.push(cur);
218        self.synth.insert((a, idx), id);
219        Ok(Expr::State(id, ty))
220    }
221
222    fn lookup(&self, id: LoopId) -> Result<Expr, ExpandError> {
223        self.env
224            .iter()
225            .rev()
226            .find(|(l, _)| *l == id)
227            .map(|(_, e)| e.clone())
228            .ok_or(ExpandError::Malformed("a loop variable outside its loop"))
229    }
230
231    fn expand(&mut self, e: &Expr) -> Result<Expr, ExpandError> {
232        match e {
233            Expr::Guard(p, then, otherwise, t) => {
234                let p = self.expand(p)?;
235                match guard::value(&p, &self.asg) {
236                    GuardValue::True => self.expand(then),
237                    GuardValue::False => self.expand(otherwise),
238                    GuardValue::Error => Err(ExpandError::GuardError),
239                    GuardValue::Random => Ok(Expr::Guard(
240                        Box::new(p),
241                        Box::new(self.expand(then)?),
242                        Box::new(self.expand(otherwise)?),
243                        *t,
244                    )),
245                }
246            }
247            Expr::Ite(c, x, y, t) => {
248                let c = self.expand(c)?;
249                if !has_vars(&c)
250                    && let Ok(v) = eval(&c, &self.asg)
251                {
252                    return if v.is_zero() {
253                        self.expand(y)
254                    } else {
255                        self.expand(x)
256                    };
257                }
258                Ok(Expr::Ite(
259                    Box::new(c),
260                    Box::new(self.expand(x)?),
261                    Box::new(self.expand(y)?),
262                    *t,
263                ))
264            }
265            Expr::Binary(op @ (BinOp::LogAnd | BinOp::LogOr | BinOp::LogImpl), l, r, t) => {
266                let l = self.expand(l)?;
267                if !has_vars(&l)
268                    && let Ok(v) = eval(&l, &self.asg)
269                {
270                    let l_true = !v.is_zero();
271                    let decided = match op {
272                        BinOp::LogAnd | BinOp::LogImpl => !l_true,
273                        _ => l_true,
274                    };
275                    if decided {
276                        return Ok(konst(*t, u64::from(*op != BinOp::LogAnd)));
277                    }
278                }
279                Ok(Expr::Binary(
280                    *op,
281                    Box::new(l),
282                    Box::new(self.expand(r)?),
283                    *t,
284                ))
285            }
286            Expr::Array(n) => self.array_node(n),
287            Expr::Var(v, t) if *v >= SELECTOR_BASE => {
288                let j = (*v - SELECTOR_BASE) as usize;
289                match self.dists.get(j) {
290                    Some(d) if d.subject.is_some() => {
291                        let k = match self
292                            .instanced
293                            .iter()
294                            .find(|(tj, env, _)| *tj == j && *env == self.env)
295                        {
296                            Some(&(_, _, k)) => k,
297                            None => {
298                                let inst = self.instantiate(d)?;
299                                self.extra_dists.push(inst);
300                                let k = self.extra_dists.len() - 1;
301                                self.instanced.push((j, self.env.clone(), k));
302                                k
303                            }
304                        };
305                        Ok(Expr::Var(SELECTOR_BASE + (self.dists.len() + k) as u32, *t))
306                    }
307                    _ => Ok(e.clone()),
308                }
309            }
310            other => other.try_map_children(&mut |c| self.expand(c)),
311        }
312    }
313
314    /// The instance of the template `d` (a `dist` inside a `foreach`) for the
315    /// current loop bindings: its subject, bounds and weights expanded. The
316    /// items' `member` predicates are not needed (the membership conjunct is
317    /// the expanded template's own) and are left true.
318    fn instantiate(&mut self, d: &Dist) -> Result<Dist, ExpandError> {
319        let Some(subject) = &d.subject else {
320            return Err(ExpandError::Malformed("an instance of a plain dist"));
321        };
322        let subject = self.expand(subject)?;
323        let mut vars: Vec<VarId> = Vec::new();
324        subject.for_each_var(&mut |v, _| vars.push(v));
325        vars.sort_unstable();
326        vars.dedup();
327        let plain_var = match &subject {
328            Expr::Var(v, _) => Some(*v),
329            _ => None,
330        };
331        let fixed = |this: &mut Self, e: &Expr| -> Result<Expr, ExpandError> {
332            let x = this.expand(e)?;
333            if has_vars(&x) {
334                return Err(ExpandError::DistRandomBound);
335            }
336            Ok(x)
337        };
338        let mut items = Vec::with_capacity(d.items.len());
339        for it in &d.items {
340            let span = match &it.span {
341                Span::Point(v) => Span::Point(fixed(self, v)?),
342                Span::Range { lo, hi } => Span::Range {
343                    lo: lo.as_ref().map(|b| fixed(self, b)).transpose()?,
344                    hi: hi.as_ref().map(|b| fixed(self, b)).transpose()?,
345                },
346            };
347            items.push(Item {
348                member: konst(Ty::BIT, 1),
349                ty: it.ty,
350                span,
351                weight: fixed(self, &it.weight)?,
352                per_range: it.per_range,
353            });
354        }
355        let default = match &d.default {
356            Some(w) => Some(DefaultWeight {
357                weight: fixed(self, &w.weight)?,
358                per_range: w.per_range,
359            }),
360            None => None,
361        };
362        Ok(Dist {
363            subject_ty: d.subject_ty,
364            plain_var,
365            vars,
366            items,
367            default,
368            subject: None,
369        })
370    }
371
372    fn array_node(&mut self, n: &ArrayNode) -> Result<Expr, ExpandError> {
373        match n {
374            ArrayNode::Call { func, args, ty } => {
375                let args = args
376                    .iter()
377                    .map(|a| self.expand(a))
378                    .collect::<Result<Vec<_>, _>>()?;
379                let call = Expr::Array(ArrayNode::Call {
380                    func: *func,
381                    args,
382                    ty: *ty,
383                });
384                // The arguments are now scalar IR, so a call over fixed values
385                // folds; one over random variables stays.
386                func::fold_ground_calls(&call, self.funcs, &self.asg.states).map_err(|e| match e {
387                    FuncError::Eval(ConstraintEvalError::StateUnknown(s)) => {
388                        ExpandError::StateUnknown(s)
389                    }
390                    other => ExpandError::Func(other),
391                })
392            }
393            ArrayNode::Size(a) => Ok(konst(Ty::signed(32), self.size_of(*a)? as u64)),
394            ArrayNode::Loop(id, t) => {
395                let v = self.lookup(*id)?;
396                if v.ty() != *t {
397                    return Err(ExpandError::Malformed(
398                        "a loop variable bound at another type",
399                    ));
400                }
401                Ok(v)
402            }
403            ArrayNode::Pick {
404                index,
405                low,
406                elems,
407                ty,
408            } => {
409                let i = self.index_value(index)?;
410                let pos = i
411                    .checked_sub(*low)
412                    .and_then(|p| usize::try_from(p).ok())
413                    .filter(|&p| p < elems.len());
414                let Some(pos) = pos else {
415                    return Err(ExpandError::LeafIndexOutOfRange {
416                        index: i,
417                        size: elems.len(),
418                    });
419                };
420                let x = self.expand(&elems[pos])?;
421                if x.ty() != *ty {
422                    return Err(ExpandError::Malformed("a pick element of another type"));
423                }
424                Ok(x)
425            }
426            ArrayNode::Elem { array, index, ty } => {
427                let i = self.index_value(index)?;
428                let size = self.size_of(*array)?;
429                if i < 0 || i as usize >= size {
430                    return Err(ExpandError::IndexOutOfRange {
431                        array: *array,
432                        index: i,
433                        size,
434                    });
435                }
436                self.element(*array, i as usize, *ty)
437            }
438            ArrayNode::Distinct { scalars, arrays } => {
439                let mut members = Vec::new();
440                for s in scalars {
441                    members.push(self.expand(s)?);
442                }
443                for a in arrays {
444                    let size = self.size_of(*a)?;
445                    let elem_ty = self.array(*a)?.elem_ty;
446                    for i in 0..size {
447                        members.push(self.element(*a, i, elem_ty)?);
448                    }
449                }
450                if members.iter().any(|m| m.ty() != members[0].ty()) {
451                    return Err(ExpandError::Malformed(
452                        "the members of a unique group are of different types",
453                    ));
454                }
455                Ok(if members.len() < 2 {
456                    konst(Ty::BIT, 1)
457                } else {
458                    Expr::AllDistinct(members)
459                })
460            }
461            ArrayNode::Foreach { array, var, body } => {
462                let size = self.size_of(*array)?;
463                let mut acc: Option<Expr> = None;
464                for i in 0..size {
465                    self.env.push((*var, konst(Ty::signed(32), i as u64)));
466                    let b = self.expand(body);
467                    self.env.pop();
468                    let b = b?;
469                    acc = Some(match acc {
470                        None => b,
471                        Some(a) => Expr::Binary(BinOp::LogAnd, Box::new(a), Box::new(b), Ty::BIT),
472                    });
473                }
474                Ok(acc.unwrap_or_else(|| konst(Ty::BIT, 1)))
475            }
476            ArrayNode::Reduce {
477                op,
478                array,
479                var,
480                index_var,
481                body,
482            } => {
483                let size = self.size_of(*array)?;
484                let elem_ty = self.array(*array)?.elem_ty;
485                let ty = body.ty();
486                let mut acc: Option<Expr> = None;
487                for i in 0..size {
488                    let item = self.element(*array, i, elem_ty)?;
489                    self.env.push((*var, item));
490                    self.env.push((*index_var, konst(Ty::signed(32), i as u64)));
491                    let b = self.expand(body);
492                    self.env.pop();
493                    self.env.pop();
494                    let b = b?;
495                    acc = Some(match acc {
496                        None => b,
497                        Some(a) => {
498                            let bop = match op {
499                                ReduceOp::Sum => BinOp::Add,
500                                ReduceOp::Product => BinOp::Mul,
501                                ReduceOp::And => BinOp::And,
502                                ReduceOp::Or => BinOp::Or,
503                                ReduceOp::Xor => BinOp::Xor,
504                            };
505                            Expr::Binary(bop, Box::new(a), Box::new(b), ty)
506                        }
507                    });
508                }
509                Ok(acc.unwrap_or_else(|| {
510                    Expr::Const(
511                        ty,
512                        match op {
513                            ReduceOp::Product => BigUint::one(),
514                            ReduceOp::And => mask(ty.width),
515                            ReduceOp::Sum | ReduceOp::Or | ReduceOp::Xor => BigUint::zero(),
516                        },
517                    )
518                }))
519            }
520        }
521    }
522}
523
524/// `v == d0 || v == d1 || ...` for an enum element.
525fn domain_constraint(v: Expr, domain: &[BigUint]) -> Expr {
526    let t = v.ty();
527    let mut acc: Option<Expr> = None;
528    for d in domain {
529        let eq = Expr::Binary(
530            BinOp::Eq,
531            Box::new(v.clone()),
532            Box::new(Expr::Const(t, d.clone() & mask(t.width))),
533            Ty::BIT,
534        );
535        acc = Some(match acc {
536            None => eq,
537            Some(a) => Expr::Binary(BinOp::LogOr, Box::new(a), Box::new(eq), Ty::BIT),
538        });
539    }
540    acc.unwrap_or_else(|| konst(Ty::BIT, 0))
541}
542
543/// Expands the array-level `conjuncts` for the array sizes `sizes` (module
544/// docs). `n_vars0` is how many field variables the conjuncts already
545/// number; element variables are numbered after them. `states` are the
546/// caller's state values (an element of an inactive array becomes a state
547/// appended after them).
548pub fn expand(
549    conjuncts: &[Expr],
550    arrays: &[ArrayState],
551    sizes: &[usize],
552    n_vars0: u32,
553    states: &[Option<BigUint>],
554) -> Result<Expanded, ExpandError> {
555    expand_with(conjuncts, arrays, sizes, n_vars0, states, &[], &[])
556}
557
558/// [`expand`] for a program with functions: a call whose arguments the
559/// expansion makes fixed (an array size, an element of an inactive array, a
560/// state) is evaluated into a constant; a call over random variables stays for
561/// the staged solve ([`super::func_order`]).
562pub fn expand_with(
563    conjuncts: &[Expr],
564    arrays: &[ArrayState],
565    sizes: &[usize],
566    n_vars0: u32,
567    states: &[Option<BigUint>],
568    funcs: &[Func],
569    dists: &[Dist],
570) -> Result<Expanded, ExpandError> {
571    if arrays.len() != sizes.len() {
572        return Err(ExpandError::Malformed("one size per array"));
573    }
574    let mut out = Expanded::default();
575    let mut elem_base = vec![0u32; arrays.len()];
576    let mut next = n_vars0;
577    for (a, arr) in arrays.iter().enumerate() {
578        if !arr.active {
579            continue;
580        }
581        elem_base[a] = next;
582        for i in 0..sizes[a] {
583            out.elem_vars.push((a as ArrayId, i));
584            out.elem_tys.push(arr.elem_ty);
585            next += 1;
586            if let Some(dom) = &arr.domain {
587                let id = elem_base[a] + i as u32;
588                out.conjuncts
589                    .push(domain_constraint(Expr::Var(id, arr.elem_ty), dom));
590            }
591        }
592    }
593    let mut ex = Expander {
594        arrays,
595        sizes,
596        elem_base,
597        asg: Assignment {
598            vars: Vec::new(),
599            states: states.to_vec(),
600        },
601        synth: HashMap::new(),
602        env: Vec::new(),
603        funcs,
604        dists,
605        extra_dists: Vec::new(),
606        instanced: Vec::new(),
607    };
608    for c in conjuncts {
609        let e = ex.expand(c)?;
610        out.conjuncts.push(e);
611    }
612    out.extra_dists = std::mem::take(&mut ex.extra_dists);
613    out.extra_states = ex.asg.states.split_off(states.len());
614    Ok(out)
615}
616
617/// Why [`solve_sizes`] failed.
618#[derive(Clone, Debug, PartialEq, Eq)]
619pub enum SizeError {
620    /// No size assignment satisfies the size constraints.
621    Unsatisfiable,
622    /// Only sizes beyond [`SIZE_TIERS`] satisfy them.
623    TooLarge,
624    /// A guard in a size constraint is `ERROR`.
625    GuardError,
626    /// The size sub-problem failed otherwise.
627    Solve(SolveError),
628}
629
630/// The array sizes chosen for one attempt.
631#[derive(Clone, Debug, PartialEq, Eq)]
632pub struct SizeSolution {
633    /// The size of every array (current size unless a constraint chose one).
634    pub sizes: Vec<usize>,
635    /// The arrays a constraint sized, in the order of [`Self::tuple`].
636    pub sized: Vec<ArrayId>,
637    /// The chosen sizes of [`Self::sized`] (what a retry blocks).
638    pub tuple: Vec<usize>,
639}
640
641/// Whether `c` takes part in the size sub-problem: no element, loop or `dist`
642/// selector reference.
643fn size_eligible(c: &Expr) -> bool {
644    !c.any_node(&mut |n| match n {
645        Expr::Array(
646            ArrayNode::Elem { .. }
647            | ArrayNode::Loop(..)
648            | ArrayNode::Foreach { .. }
649            | ArrayNode::Reduce { .. }
650            | ArrayNode::Distinct { .. }
651            // An index is only known once the node is expanded.
652            | ArrayNode::Pick { .. }
653            // A call over a size (the pre-pass folded every fixed one) is
654            // only decidable once the arguments are known.
655            | ArrayNode::Call { .. },
656        ) => true,
657        Expr::Var(v, _) => *v >= super::dist::SELECTOR_BASE,
658        _ => false,
659    })
660}
661
662fn replace_sizes(e: &Expr, f: &mut impl FnMut(ArrayId) -> Expr) -> Expr {
663    match e {
664        Expr::Array(ArrayNode::Size(a)) => f(*a),
665        other => other
666            .try_map_children::<()>(&mut |c| Ok(replace_sizes(c, f)))
667            .expect("infallible"),
668    }
669}
670
671fn find(parent: &mut [usize], x: usize) -> usize {
672    let mut r = x;
673    while parent[r] != r {
674        r = parent[r];
675    }
676    let mut c = x;
677    while parent[c] != r {
678        let n = parent[c];
679        parent[c] = r;
680        c = n;
681    }
682    r
683}
684
685/// Chooses the sizes of the `rand`-active arrays whose `size` the size-eligible
686/// `conjuncts` constrain (module docs). `var_tys[v]` is the type of field
687/// variable `v` (`conjuncts` are over `0..var_tys.len()`), `states` the state
688/// values, `blocked` size tuples ruled out by earlier attempts (in the
689/// `sized` order every call reproduces).
690pub fn solve_sizes(
691    conjuncts: &[Expr],
692    arrays: &[ArrayState],
693    var_tys: &[Ty],
694    states: &[Option<BigUint>],
695    blocked: &[Vec<usize>],
696    seed: &mut u64,
697) -> Result<SizeSolution, SizeError> {
698    let current: Vec<usize> = arrays.iter().map(|a| a.cur.len()).collect();
699    let n_fields = var_tys.len() as u32;
700    // 1. Which active arrays does an eligible conjunct size?
701    let mut sized: Vec<ArrayId> = Vec::new();
702    let mut eligible: Vec<&Expr> = Vec::new();
703    for c in conjuncts {
704        if !size_eligible(c) {
705            continue;
706        }
707        c.any_node(&mut |n| {
708            if let Expr::Array(ArrayNode::Size(a)) = n
709                && arrays.get(*a as usize).is_some_and(|x| x.active)
710                && !sized.contains(a)
711            {
712                sized.push(*a);
713            }
714            false
715        });
716        eligible.push(c);
717    }
718    if sized.is_empty() {
719        return Ok(SizeSolution {
720            sizes: current,
721            sized,
722            tuple: Vec::new(),
723        });
724    }
725    sized.sort_unstable();
726    let size_var = |a: ArrayId| -> u32 {
727        n_fields + u32::try_from(sized.iter().position(|x| *x == a).expect("sized")).unwrap()
728    };
729    // 2. Substitute the size variables, then keep the conjuncts connected to one.
730    let total = n_fields as usize + sized.len();
731    let subbed: Vec<Expr> = eligible
732        .iter()
733        .map(|c| {
734            replace_sizes(c, &mut |a| {
735                if arrays[a as usize].active {
736                    Expr::Var(size_var(a), Ty::signed(32))
737                } else {
738                    konst(Ty::signed(32), current[a as usize] as u64)
739                }
740            })
741        })
742        .collect();
743    let mut parent: Vec<usize> = (0..total).collect();
744    let vars_of: Vec<Vec<VarId>> = subbed
745        .iter()
746        .map(|c| {
747            let mut v = Vec::new();
748            c.for_each_var(&mut |x, _| v.push(x));
749            v
750        })
751        .collect();
752    for vs in &vars_of {
753        for w in vs.windows(2) {
754            let (a, b) = (
755                find(&mut parent, w[0] as usize),
756                find(&mut parent, w[1] as usize),
757            );
758            parent[a] = b;
759        }
760    }
761    let size_roots: Vec<usize> = (0..sized.len())
762        .map(|k| find(&mut parent, n_fields as usize + k))
763        .collect();
764    let mut keep: Vec<Expr> = Vec::new();
765    let mut used = vec![false; total];
766    for k in 0..sized.len() {
767        used[n_fields as usize + k] = true;
768    }
769    for (c, vs) in subbed.iter().zip(&vars_of) {
770        let Some(&v0) = vs.first() else { continue };
771        let root = find(&mut parent, v0 as usize);
772        if size_roots.contains(&root) {
773            for &v in vs {
774                used[v as usize] = true;
775            }
776            keep.push(c.clone());
777        }
778    }
779    // 3. Compact the variables.
780    let mut new_id = vec![0u32; total];
781    let mut tys: Vec<Ty> = Vec::new();
782    for (v, u) in used.iter().enumerate() {
783        if *u {
784            new_id[v] = tys.len() as u32;
785            tys.push(if v < n_fields as usize {
786                var_tys[v]
787            } else {
788                Ty::signed(32)
789            });
790        }
791    }
792    let mut keep: Vec<Expr> = keep
793        .iter()
794        .map(|c| c.substitute_vars(&mut |v, t| Expr::Var(new_id[v as usize], t)))
795        .collect();
796    let svar = |k: usize| Expr::Var(new_id[n_fields as usize + k], Ty::signed(32));
797    // Blocked tuples.
798    for tup in blocked {
799        let mut same: Option<Expr> = None;
800        for (k, &sz) in tup.iter().enumerate() {
801            let eq = Expr::Binary(
802                BinOp::Eq,
803                Box::new(svar(k)),
804                Box::new(konst(Ty::signed(32), sz as u64)),
805                Ty::BIT,
806            );
807            same = Some(match same {
808                None => eq,
809                Some(a) => Expr::Binary(BinOp::LogAnd, Box::new(a), Box::new(eq), Ty::BIT),
810            });
811        }
812        if let Some(s) = same {
813            keep.push(Expr::Unary(UnOp::LogNot, Box::new(s), Ty::BIT));
814        }
815    }
816    let assignment = Assignment {
817        vars: Vec::new(),
818        states: states.to_vec(),
819    };
820    let order: Vec<VarId> = (0..tys.len() as u32).collect();
821    let tiers: [u64; 3] = [SIZE_TIERS[0], SIZE_TIERS[1], i32::MAX as u64];
822    for (ti, bound) in tiers.iter().enumerate() {
823        let mut cs = keep.clone();
824        for k in 0..sized.len() {
825            cs.push(Expr::Binary(
826                BinOp::Ge,
827                Box::new(svar(k)),
828                Box::new(konst(Ty::signed(32), 0)),
829                Ty::BIT,
830            ));
831            cs.push(Expr::Binary(
832                BinOp::Le,
833                Box::new(svar(k)),
834                Box::new(konst(Ty::signed(32), *bound)),
835                Ty::BIT,
836            ));
837        }
838        let mut resolved = Vec::with_capacity(cs.len());
839        for c in &cs {
840            resolved.push(guard::resolve(c, &assignment).map_err(|_| SizeError::GuardError)?);
841        }
842        let solver = match Solver::new(&tys, states, &resolved) {
843            Ok(s) => s,
844            Err(SolveError::Unsatisfiable) => continue,
845            Err(e) => return Err(SizeError::Solve(e)),
846        };
847        let values = match solver.sample(&order, seed) {
848            Ok(v) => v,
849            Err(SolveError::Unsatisfiable) => continue,
850            Err(e) => return Err(SizeError::Solve(e)),
851        };
852        if ti == 2 {
853            return Err(SizeError::TooLarge);
854        }
855        let mut sizes = current;
856        let mut tuple = Vec::with_capacity(sized.len());
857        for (k, &a) in sized.iter().enumerate() {
858            let v = &values[new_id[n_fields as usize + k] as usize];
859            let n = usize::try_from(v).expect("a bounded size");
860            sizes[a as usize] = n;
861            tuple.push(n);
862        }
863        return Ok(SizeSolution {
864            sizes,
865            sized,
866            tuple,
867        });
868    }
869    Err(SizeError::Unsatisfiable)
870}
871
872#[cfg(test)]
873mod tests {
874    use super::*;
875
876    fn s32() -> Ty {
877        Ty::signed(32)
878    }
879    fn u(w: u32) -> Ty {
880        Ty::unsigned(w)
881    }
882    fn size(a: ArrayId) -> Expr {
883        Expr::Array(ArrayNode::Size(a))
884    }
885    fn elem(a: ArrayId, i: Expr, t: Ty) -> Expr {
886        Expr::Array(ArrayNode::Elem {
887            array: a,
888            index: Box::new(i),
889            ty: t,
890        })
891    }
892    fn lp(id: LoopId) -> Expr {
893        Expr::Array(ArrayNode::Loop(id, s32()))
894    }
895    fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
896        Expr::Binary(op, Box::new(a), Box::new(b), t)
897    }
898    fn arr(active: bool, cur: &[u64]) -> ArrayState {
899        ArrayState {
900            elem_ty: s32(),
901            active,
902            cur: cur.iter().map(|v| Some(BigUint::from(*v))).collect(),
903            domain: None,
904        }
905    }
906    fn ev(e: &Expr, vars: &[u64], states: &[Option<BigUint>]) -> bool {
907        let a = Assignment {
908            vars: vars.iter().map(|v| BigUint::from(*v)).collect(),
909            states: states.to_vec(),
910        };
911        !eval(e, &a).expect("evaluates").is_zero()
912    }
913
914    /// LRM 18.5.7.1: `foreach (A[j]) A[j] > 2*j` unrolls to one comparison per
915    /// element, over one fresh variable each.
916    #[test]
917    fn foreach_unrolls_over_the_size() {
918        let body = bin(
919            BinOp::Gt,
920            elem(0, lp(0), s32()),
921            bin(BinOp::Mul, konst(s32(), 2), lp(0), s32()),
922            Ty::BIT,
923        );
924        let c = Expr::Array(ArrayNode::Foreach {
925            array: 0,
926            var: 0,
927            body: Box::new(body),
928        });
929        let x = expand(&[c], &[arr(true, &[])], &[3], 0, &[]).expect("expands");
930        assert_eq!(x.elem_vars, vec![(0, 0), (0, 1), (0, 2)]);
931        assert_eq!(x.conjuncts.len(), 1);
932        assert!(ev(&x.conjuncts[0], &[1, 3, 5], &[]));
933        assert!(!ev(&x.conjuncts[0], &[1, 2, 5], &[]));
934        assert!(!ev(&x.conjuncts[0], &[0, 3, 5], &[]));
935    }
936
937    /// The ascending-sort example: `if (k < A.size-1) A[k] < A[k+1]` never
938    /// reads `A[size]` because the constant guard drops that arm.
939    #[test]
940    fn a_constant_guard_keeps_the_last_neighbour_unread() {
941        let pred = bin(
942            BinOp::Lt,
943            lp(0),
944            bin(BinOp::Sub, size(0), konst(s32(), 1), s32()),
945            Ty::BIT,
946        );
947        let then = bin(
948            BinOp::Lt,
949            elem(0, lp(0), s32()),
950            elem(0, bin(BinOp::Add, lp(0), konst(s32(), 1), s32()), s32()),
951            Ty::BIT,
952        );
953        let body = Expr::Guard(
954            Box::new(pred),
955            Box::new(then),
956            Box::new(konst(Ty::BIT, 1)),
957            Ty::BIT,
958        );
959        let c = Expr::Array(ArrayNode::Foreach {
960            array: 0,
961            var: 0,
962            body: Box::new(body),
963        });
964        let x = expand(&[c], &[arr(true, &[])], &[4], 0, &[]).expect("expands");
965        assert_eq!(x.elem_vars.len(), 4);
966        assert!(ev(&x.conjuncts[0], &[1, 2, 3, 4], &[]));
967        assert!(!ev(&x.conjuncts[0], &[1, 3, 2, 4], &[]));
968    }
969
970    #[test]
971    fn an_unguarded_out_of_range_index_is_a_loud_error() {
972        let c = bin(
973            BinOp::Eq,
974            elem(0, konst(s32(), 5), s32()),
975            konst(s32(), 0),
976            Ty::BIT,
977        );
978        assert_eq!(
979            expand(&[c], &[arr(true, &[])], &[2], 0, &[]).unwrap_err(),
980            ExpandError::IndexOutOfRange {
981                array: 0,
982                index: 5,
983                size: 2
984            }
985        );
986    }
987
988    /// A pick (a fixed-size array in a class property, XC-059) expands to the
989    /// element its state index names, counted from the array's lowest index.
990    #[test]
991    fn a_pick_selects_the_element_its_index_names() {
992        let pick = |idx: Expr| {
993            Expr::Array(ArrayNode::Pick {
994                index: Box::new(idx),
995                low: 2,
996                elems: vec![Expr::Var(0, u(8)), Expr::Var(1, u(8)), Expr::Var(2, u(8))],
997                ty: u(8),
998            })
999        };
1000        let states = [Some(BigUint::from(3u32))];
1001        let c = bin(
1002            BinOp::Eq,
1003            pick(Expr::State(0, s32())),
1004            konst(u(8), 9),
1005            Ty::BIT,
1006        );
1007        let x = expand(&[c], &[], &[], 3, &states).expect("expands");
1008        assert_eq!(x.conjuncts.len(), 1);
1009        assert!(ev(&x.conjuncts[0], &[0, 9, 0], &states));
1010        assert!(!ev(&x.conjuncts[0], &[9, 0, 9], &states));
1011    }
1012
1013    /// Only the chosen element is expanded, so a constant-false guard keeps an
1014    /// out-of-range pick unread; a live one is a loud error naming the index
1015    /// and the size (LRM 18.5.7.1).
1016    #[test]
1017    fn an_out_of_range_pick_is_an_error_only_when_live() {
1018        let pick = Expr::Array(ArrayNode::Pick {
1019            index: Box::new(Expr::State(0, s32())),
1020            low: 0,
1021            elems: vec![Expr::Var(0, u(8)), Expr::Var(1, u(8))],
1022            ty: u(8),
1023        });
1024        let body = bin(BinOp::Eq, pick, konst(u(8), 1), Ty::BIT);
1025        let guarded = Expr::Guard(
1026            Box::new(bin(
1027                BinOp::Lt,
1028                Expr::State(0, s32()),
1029                konst(s32(), 2),
1030                Ty::BIT,
1031            )),
1032            Box::new(body.clone()),
1033            Box::new(konst(Ty::BIT, 1)),
1034            Ty::BIT,
1035        );
1036        let out = [Some(BigUint::from(7u32))];
1037        let x = expand(&[guarded], &[], &[], 2, &out).expect("the guard drops the pick");
1038        assert!(ev(&x.conjuncts[0], &[], &out));
1039        assert_eq!(
1040            expand(std::slice::from_ref(&body), &[], &[], 2, &out).unwrap_err(),
1041            ExpandError::LeafIndexOutOfRange { index: 7, size: 2 }
1042        );
1043        // A negative index (an `int` state of -1) is out of range too.
1044        let neg = [Some((BigUint::from(1u32) << 32) - 1u32)];
1045        assert_eq!(
1046            expand(&[body], &[], &[], 2, &neg).unwrap_err(),
1047            ExpandError::LeafIndexOutOfRange { index: -1, size: 2 }
1048        );
1049    }
1050
1051    /// An inactive array is state: its elements read as the current values
1052    /// and its size as the current length.
1053    #[test]
1054    fn an_inactive_array_is_state() {
1055        let c = bin(
1056            BinOp::Eq,
1057            elem(0, konst(s32(), 1), s32()),
1058            konst(s32(), 7),
1059            Ty::BIT,
1060        );
1061        let x = expand(&[c], &[arr(false, &[3, 7])], &[2], 0, &[]).expect("expands");
1062        assert!(x.elem_vars.is_empty());
1063        assert_eq!(x.extra_states, vec![Some(BigUint::from(7u32))]);
1064        assert!(ev(&x.conjuncts[0], &[], &x.extra_states));
1065    }
1066
1067    #[test]
1068    fn reductions_fold_and_empty_ones_are_the_identity() {
1069        let sum = |op| {
1070            Expr::Array(ArrayNode::Reduce {
1071                op,
1072                array: 0,
1073                var: 0,
1074                index_var: 1,
1075                body: Box::new(Expr::Array(ArrayNode::Loop(0, s32()))),
1076            })
1077        };
1078        let c = bin(BinOp::Eq, sum(ReduceOp::Sum), konst(s32(), 6), Ty::BIT);
1079        let x = expand(&[c], &[arr(true, &[])], &[3], 0, &[]).expect("expands");
1080        assert!(ev(&x.conjuncts[0], &[1, 2, 3], &[]));
1081        assert!(!ev(&x.conjuncts[0], &[1, 2, 4], &[]));
1082        for (op, want) in [
1083            (ReduceOp::Sum, 0u64),
1084            (ReduceOp::Product, 1),
1085            (ReduceOp::Or, 0),
1086            (ReduceOp::Xor, 0),
1087            (ReduceOp::And, 0xFFFF_FFFF),
1088        ] {
1089            let c = bin(BinOp::Eq, sum(op), konst(s32(), want), Ty::BIT);
1090            let x = expand(&[c], &[arr(true, &[])], &[0], 0, &[]).expect("expands");
1091            assert!(ev(&x.conjuncts[0], &[], &[]), "{op:?}");
1092        }
1093    }
1094
1095    /// LRM 18.4: `data.size == len` sizes the array to a drawn `len`.
1096    #[test]
1097    fn a_size_constraint_resizes_the_array() {
1098        let c = bin(
1099            BinOp::Eq,
1100            size(0),
1101            Expr::Resize(Box::new(Expr::Var(0, u(8))), s32()),
1102            Ty::BIT,
1103        );
1104        let bound = bin(BinOp::Lt, Expr::Var(0, u(8)), konst(u(8), 5), Ty::BIT);
1105        let mut seen = std::collections::BTreeSet::new();
1106        let mut seed = 1u64;
1107        for _ in 0..200 {
1108            let s = solve_sizes(
1109                &[c.clone(), bound.clone()],
1110                &[arr(true, &[9, 9])],
1111                &[u(8)],
1112                &[],
1113                &[],
1114                &mut seed,
1115            )
1116            .expect("solves");
1117            assert_eq!(s.sized, vec![0]);
1118            assert!(s.sizes[0] < 5);
1119            seen.insert(s.sizes[0]);
1120        }
1121        assert_eq!(seen.len(), 5, "every size 0..5 is reachable: {seen:?}");
1122    }
1123
1124    #[test]
1125    fn an_unconstrained_size_keeps_the_array() {
1126        let mut seed = 1u64;
1127        let s = solve_sizes(&[], &[arr(true, &[1, 2, 3])], &[], &[], &[], &mut seed).unwrap();
1128        assert_eq!(s.sizes, vec![3]);
1129        assert!(s.sized.is_empty());
1130    }
1131
1132    #[test]
1133    fn an_impossible_size_is_unsatisfiable_and_a_huge_one_is_too_large() {
1134        let mut seed = 1u64;
1135        let lt = |n: u64| bin(BinOp::Lt, size(0), konst(s32(), n), Ty::BIT);
1136        let gt = |n: u64| bin(BinOp::Gt, size(0), konst(s32(), n), Ty::BIT);
1137        let a = [arr(true, &[])];
1138        assert_eq!(
1139            solve_sizes(&[lt(3), gt(5)], &a, &[], &[], &[], &mut seed).unwrap_err(),
1140            SizeError::Unsatisfiable
1141        );
1142        assert_eq!(
1143            solve_sizes(&[gt(10_000_000)], &a, &[], &[], &[], &mut seed).unwrap_err(),
1144            SizeError::TooLarge
1145        );
1146        // Beyond the first tier but within the second.
1147        let s = solve_sizes(&[gt(2000)], &a, &[], &[], &[], &mut seed).unwrap();
1148        assert!(s.sizes[0] > 2000);
1149    }
1150
1151    #[test]
1152    fn blocked_tuples_are_excluded() {
1153        let c = bin(BinOp::Le, size(0), konst(s32(), 1), Ty::BIT);
1154        let a = [arr(true, &[])];
1155        let mut seed = 3u64;
1156        let s = solve_sizes(
1157            std::slice::from_ref(&c),
1158            &a,
1159            &[],
1160            &[],
1161            &[vec![0]],
1162            &mut seed,
1163        )
1164        .unwrap();
1165        assert_eq!(s.sizes, vec![1]);
1166        assert_eq!(
1167            solve_sizes(&[c], &a, &[], &[], &[vec![0], vec![1]], &mut seed).unwrap_err(),
1168            SizeError::Unsatisfiable
1169        );
1170    }
1171
1172    /// A conjunct with an element reference is not part of the size problem,
1173    /// and a disconnected one is left out.
1174    #[test]
1175    fn the_size_problem_holds_only_connected_size_constraints() {
1176        let elem_c = bin(
1177            BinOp::Eq,
1178            elem(0, konst(s32(), 0), s32()),
1179            konst(s32(), 1),
1180            Ty::BIT,
1181        );
1182        let unrelated = bin(BinOp::Eq, Expr::Var(0, u(8)), konst(u(8), 200), Ty::BIT);
1183        let c = bin(BinOp::Eq, size(0), konst(s32(), 2), Ty::BIT);
1184        let mut seed = 1u64;
1185        let s = solve_sizes(
1186            &[elem_c, unrelated, c],
1187            &[arr(true, &[])],
1188            &[u(8)],
1189            &[],
1190            &[],
1191            &mut seed,
1192        )
1193        .unwrap();
1194        assert_eq!(s.sizes, vec![2]);
1195    }
1196}