Skip to main content

vj_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    /// For an array of dynamic arrays or queues (`rand int a[][]`): its rows.
70    /// [`Self::cur`] then holds one placeholder per row and [`Self::elem_ty`]
71    /// is the leaf type; the rows' own elements live here.
72    pub rows: Option<RowsState>,
73    /// For a row template ([`ArrayNode::RowScope`]): the array of arrays whose
74    /// rows it stands for. A template has no elements of its own.
75    pub template_of: Option<ArrayId>,
76}
77
78/// The rows of an array of dynamic arrays or queues as one `randomize()` call
79/// sees them.
80#[derive(Clone, Debug)]
81pub struct RowsState {
82    /// How many levels of rows lie below the array, itself included: 1 for
83    /// `int a[][]` (its rows are leaf arrays), 2 for `int a[][][]` (its rows
84    /// are arrays of arrays, whose rows are leaf arrays).
85    pub levels: u32,
86    /// The row template of each level, outermost first (`u32::MAX` for a level
87    /// no constraint names). `templates[0]` is the template the constraints
88    /// name this array's rows through; the next is that of its rows' rows.
89    pub templates: Vec<ArrayId>,
90    /// Each current row's elements when `levels == 1` (`None` is an element
91    /// with an X/Z bit).
92    pub cur_rows: Vec<Vec<Option<BigUint>>>,
93    /// Each current row when `levels > 1`: an array of arrays of its own.
94    pub deeper: Vec<ArrayState>,
95    /// Set by [`lower_rows`]: the id of the array standing for row 0, rows
96    /// `0..size` following it.
97    pub row_base: ArrayId,
98    /// Whether [`lower_rows`] has given the rows their arrays.
99    pub lowered: bool,
100}
101
102impl RowsState {
103    /// Rows of `levels` levels with the given contents and no template yet.
104    pub fn new(
105        levels: u32,
106        cur_rows: Vec<Vec<Option<BigUint>>>,
107        deeper: Vec<ArrayState>,
108    ) -> RowsState {
109        RowsState {
110            levels,
111            templates: vec![u32::MAX; levels as usize],
112            cur_rows,
113            deeper,
114            row_base: 0,
115            lowered: false,
116        }
117    }
118
119    /// Names `t` the row template of level `level` (0 is this array's own rows)
120    /// for these rows and every row below them.
121    pub fn set_template(&mut self, level: usize, t: ArrayId) {
122        if let Some(slot) = self.templates.get_mut(level) {
123            *slot = t;
124        }
125        if level > 0 {
126            for d in &mut self.deeper {
127                if let Some(r) = d.rows.as_mut() {
128                    r.set_template(level - 1, t);
129                }
130            }
131        }
132    }
133}
134
135impl ArrayState {
136    /// A state holding `cur`, no domain and no rows.
137    pub fn flat(elem_ty: Ty, active: bool, cur: Vec<Option<BigUint>>) -> ArrayState {
138        ArrayState {
139            elem_ty,
140            active,
141            cur,
142            domain: None,
143            rows: None,
144            template_of: None,
145        }
146    }
147
148    /// This state with the enum values an element must be one of.
149    pub fn with_domain(mut self, domain: Option<Vec<BigUint>>) -> ArrayState {
150        self.domain = domain;
151        self
152    }
153}
154
155/// Why [`expand`] failed.
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub enum ExpandError {
158    /// An element index outside `0..size` in a live constraint.
159    IndexOutOfRange {
160        /// The array.
161        array: ArrayId,
162        /// The index (may be negative).
163        index: i64,
164        /// The array's size in this call.
165        size: usize,
166    },
167    /// An index outside the elements of a fixed-size array held in a class
168    /// property ([`ArrayNode::Pick`]) in a live constraint.
169    LeafIndexOutOfRange {
170        /// The index (may be negative).
171        index: i64,
172        /// The array's size.
173        size: usize,
174    },
175    /// An element index that is not a constant once the loop variables are
176    /// substituted.
177    NonConstantIndex,
178    /// A guard of an implication or `if` constraint is `ERROR` (LRM 18.5.12).
179    GuardError,
180    /// A state an index or constant guard reads is X/Z.
181    StateUnknown(u32),
182    /// A function called from a constraint failed on fixed arguments.
183    Func(FuncError),
184    /// A weight or bound of a `dist` inside a `foreach` over a dynamic array
185    /// reads a random element once the loop is unrolled (LRM 18.5.3: weights
186    /// and bounds are fixed values).
187    DistRandomBound,
188    /// The array-level IR is malformed (a builder bug).
189    Malformed(&'static str),
190}
191
192/// The result of [`expand`].
193#[derive(Clone, Debug, Default)]
194pub struct Expanded {
195    /// Scalar constraints over the field variables `0..n_vars0` and the element
196    /// variables `n_vars0..`.
197    pub conjuncts: Vec<Expr>,
198    /// Element variable `n_vars0 + k` is `(array, index)` of `elem_vars[k]`.
199    pub elem_vars: Vec<(ArrayId, usize)>,
200    /// Types of the element variables.
201    pub elem_tys: Vec<Ty>,
202    /// State values appended after the caller's states (an element of an
203    /// inactive or non-`rand` array).
204    pub extra_states: Vec<Option<BigUint>>,
205    /// The instances of the program's `dist` templates ([`Dist::subject`]),
206    /// one per iteration that met one. The instance `k` has the selector
207    /// `SELECTOR_BASE + n_program_dists + k`, and its variable ids are already
208    /// the expansion's (element variables numbered after the field variables).
209    pub extra_dists: Vec<Dist>,
210}
211
212struct Expander<'a> {
213    arrays: &'a [ArrayState],
214    sizes: &'a [usize],
215    /// `elem_base[a]`: the variable id of element 0 of active array `a`.
216    elem_base: Vec<u32>,
217    asg: Assignment,
218    synth: HashMap<(ArrayId, usize), u32>,
219    env: Vec<(LoopId, Expr)>,
220    funcs: &'a [Func],
221    /// The program's dists; those with a `subject` are templates.
222    dists: &'a [Dist],
223    extra_dists: Vec<Dist>,
224    /// The instance made for a template under a loop binding: the template's
225    /// index, the bindings, and the instance's index in `extra_dists`. A
226    /// template's selector occurs once per item code in its membership
227    /// conjunct; all of them name one instance.
228    instanced: Vec<InstancedDist>,
229    /// The row templates in scope ([`ArrayNode::RowScope`]) and the concrete row
230    /// array each currently stands for.
231    rebind: Vec<(ArrayId, ArrayId)>,
232}
233
234/// A template dist's instance: template index, loop bindings, index in `extra_dists`.
235type InstancedDist = (usize, Vec<(LoopId, Expr)>, usize);
236
237fn konst(t: Ty, v: u64) -> Expr {
238    Expr::constant(t, v)
239}
240
241fn has_vars(e: &Expr) -> bool {
242    let mut any = false;
243    e.for_each_var(&mut |_, _| any = true);
244    any
245}
246
247impl Expander<'_> {
248    fn array(&self, a: ArrayId) -> Result<&ArrayState, ExpandError> {
249        self.arrays.get(a as usize).ok_or(ExpandError::Malformed(
250            "an array id outside the program's table",
251        ))
252    }
253
254    /// The array `a` names right now (a row template resolves to its row).
255    fn res(&self, a: ArrayId) -> ArrayId {
256        self.rebind
257            .iter()
258            .rev()
259            .find(|(t, _)| *t == a)
260            .map_or(a, |(_, r)| *r)
261    }
262
263    fn size_of(&self, a: ArrayId) -> Result<usize, ExpandError> {
264        let a = self.res(a);
265        self.sizes
266            .get(a as usize)
267            .copied()
268            .ok_or(ExpandError::Malformed("no size for an array"))
269    }
270
271    /// The constant value of the (var-free) expression `e`.
272    fn const_value(&self, e: &Expr) -> Result<BigUint, ExpandError> {
273        if has_vars(e) {
274            return Err(ExpandError::NonConstantIndex);
275        }
276        eval(e, &self.asg).map_err(|err| match err {
277            ConstraintEvalError::StateUnknown(s) => ExpandError::StateUnknown(s),
278            _ => ExpandError::NonConstantIndex,
279        })
280    }
281
282    /// The value of the index expression `index` (expanded here), signed per
283    /// its type; it must be free of random variables.
284    fn index_value(&mut self, index: &Expr) -> Result<i64, ExpandError> {
285        let idx = self.expand(index)?;
286        let v = self.const_value(&idx)?;
287        let t = idx.ty();
288        Ok(if t.signed && v.bit(u64::from(t.width) - 1) {
289            -i64::try_from((BigUint::one() << t.width) - &v).unwrap_or(i64::MAX)
290        } else {
291            i64::try_from(&v).unwrap_or(i64::MAX)
292        })
293    }
294
295    /// Element `idx` of array `a`: a random variable, or a state variable
296    /// holding its current value.
297    fn element(&mut self, a: ArrayId, idx: usize, ty: Ty) -> Result<Expr, ExpandError> {
298        let a = self.res(a);
299        let arr = self.array(a)?;
300        if arr.elem_ty != ty {
301            return Err(ExpandError::Malformed("an element read at the wrong type"));
302        }
303        let (active, cur) = (arr.active, arr.cur.get(idx).cloned());
304        if active {
305            let id = self.elem_base[a as usize] + u32::try_from(idx).expect("an element index");
306            return Ok(Expr::Var(id, ty));
307        }
308        let Some(cur) = cur else {
309            return Err(ExpandError::Malformed("an element of a stale array view"));
310        };
311        if let Some(&id) = self.synth.get(&(a, idx)) {
312            return Ok(Expr::State(id, ty));
313        }
314        let id = u32::try_from(self.asg.states.len()).expect("a state id");
315        self.asg.states.push(cur);
316        self.synth.insert((a, idx), id);
317        Ok(Expr::State(id, ty))
318    }
319
320    fn lookup(&self, id: LoopId) -> Result<Expr, ExpandError> {
321        self.env
322            .iter()
323            .rev()
324            .find(|(l, _)| *l == id)
325            .map(|(_, e)| e.clone())
326            .ok_or(ExpandError::Malformed("a loop variable outside its loop"))
327    }
328
329    fn expand(&mut self, e: &Expr) -> Result<Expr, ExpandError> {
330        match e {
331            Expr::Guard(p, then, otherwise, t) => {
332                let p = self.expand(p)?;
333                match guard::value(&p, &self.asg) {
334                    GuardValue::True => self.expand(then),
335                    GuardValue::False => self.expand(otherwise),
336                    GuardValue::Error => Err(ExpandError::GuardError),
337                    GuardValue::Random => Ok(Expr::Guard(
338                        Box::new(p),
339                        Box::new(self.expand(then)?),
340                        Box::new(self.expand(otherwise)?),
341                        *t,
342                    )),
343                }
344            }
345            Expr::Ite(c, x, y, t) => {
346                let c = self.expand(c)?;
347                if !has_vars(&c)
348                    && let Ok(v) = eval(&c, &self.asg)
349                {
350                    return if v.is_zero() {
351                        self.expand(y)
352                    } else {
353                        self.expand(x)
354                    };
355                }
356                Ok(Expr::Ite(
357                    Box::new(c),
358                    Box::new(self.expand(x)?),
359                    Box::new(self.expand(y)?),
360                    *t,
361                ))
362            }
363            Expr::Binary(op @ (BinOp::LogAnd | BinOp::LogOr | BinOp::LogImpl), l, r, t) => {
364                let l = self.expand(l)?;
365                if !has_vars(&l)
366                    && let Ok(v) = eval(&l, &self.asg)
367                {
368                    let l_true = !v.is_zero();
369                    let decided = match op {
370                        BinOp::LogAnd | BinOp::LogImpl => !l_true,
371                        _ => l_true,
372                    };
373                    if decided {
374                        return Ok(konst(*t, u64::from(*op != BinOp::LogAnd)));
375                    }
376                }
377                Ok(Expr::Binary(
378                    *op,
379                    Box::new(l),
380                    Box::new(self.expand(r)?),
381                    *t,
382                ))
383            }
384            Expr::Array(n) => self.array_node(n),
385            Expr::Var(v, t) if *v >= SELECTOR_BASE => {
386                let j = (*v - SELECTOR_BASE) as usize;
387                match self.dists.get(j) {
388                    Some(d) if d.subject.is_some() => {
389                        let k = match self
390                            .instanced
391                            .iter()
392                            .find(|(tj, env, _)| *tj == j && *env == self.env)
393                        {
394                            Some(&(_, _, k)) => k,
395                            None => {
396                                let inst = self.instantiate(d)?;
397                                self.extra_dists.push(inst);
398                                let k = self.extra_dists.len() - 1;
399                                self.instanced.push((j, self.env.clone(), k));
400                                k
401                            }
402                        };
403                        Ok(Expr::Var(SELECTOR_BASE + (self.dists.len() + k) as u32, *t))
404                    }
405                    _ => Ok(e.clone()),
406                }
407            }
408            other => other.try_map_children(&mut |c| self.expand(c)),
409        }
410    }
411
412    /// The instance of the template `d` (a `dist` inside a `foreach`) for the
413    /// current loop bindings: its subject, bounds and weights expanded. The
414    /// items' `member` predicates are not needed (the membership conjunct is
415    /// the expanded template's own) and are left true.
416    fn instantiate(&mut self, d: &Dist) -> Result<Dist, ExpandError> {
417        let Some(subject) = &d.subject else {
418            return Err(ExpandError::Malformed("an instance of a plain dist"));
419        };
420        let subject = self.expand(subject)?;
421        let mut vars: Vec<VarId> = Vec::new();
422        subject.for_each_var(&mut |v, _| vars.push(v));
423        vars.sort_unstable();
424        vars.dedup();
425        let plain_var = match &subject {
426            Expr::Var(v, _) => Some(*v),
427            _ => None,
428        };
429        let fixed = |this: &mut Self, e: &Expr| -> Result<Expr, ExpandError> {
430            let x = this.expand(e)?;
431            if has_vars(&x) {
432                return Err(ExpandError::DistRandomBound);
433            }
434            Ok(x)
435        };
436        let mut items = Vec::with_capacity(d.items.len());
437        for it in &d.items {
438            let span = match &it.span {
439                Span::Point(v) => Span::Point(fixed(self, v)?),
440                Span::Range { lo, hi } => Span::Range {
441                    lo: lo.as_ref().map(|b| fixed(self, b)).transpose()?,
442                    hi: hi.as_ref().map(|b| fixed(self, b)).transpose()?,
443                },
444            };
445            items.push(Item {
446                member: konst(Ty::BIT, 1),
447                ty: it.ty,
448                span,
449                weight: fixed(self, &it.weight)?,
450                per_range: it.per_range,
451            });
452        }
453        let default = match &d.default {
454            Some(w) => Some(DefaultWeight {
455                weight: fixed(self, &w.weight)?,
456                per_range: w.per_range,
457            }),
458            None => None,
459        };
460        Ok(Dist {
461            subject_ty: d.subject_ty,
462            plain_var,
463            vars,
464            items,
465            default,
466            subject: None,
467        })
468    }
469
470    /// The concrete row array row `row` of the array of arrays `template` is
471    /// the row template of names.
472    fn row_array(&mut self, template: ArrayId, row: &Expr) -> Result<ArrayId, ExpandError> {
473        let outer = self
474            .array(template)?
475            .template_of
476            .ok_or(ExpandError::Malformed("a row scope over a non-template"))?;
477        // The array of arrays may itself be the template of a deeper level,
478        // standing for a row an enclosing scope selected.
479        let outer = self.res(outer);
480        let k = self.index_value(row)?;
481        let size = self.size_of(outer)?;
482        if k < 0 || k as usize >= size {
483            return Err(ExpandError::IndexOutOfRange {
484                array: outer,
485                index: k,
486                size,
487            });
488        }
489        let base = self
490            .array(outer)?
491            .rows
492            .as_ref()
493            .ok_or(ExpandError::Malformed("rows of an array without rows"))?
494            .row_base;
495        Ok(base + k as ArrayId)
496    }
497
498    fn array_node(&mut self, n: &ArrayNode) -> Result<Expr, ExpandError> {
499        match n {
500            ArrayNode::Call { func, args, ty } => {
501                let args = args
502                    .iter()
503                    .map(|a| self.expand(a))
504                    .collect::<Result<Vec<_>, _>>()?;
505                let call = Expr::Array(ArrayNode::Call {
506                    func: *func,
507                    args,
508                    ty: *ty,
509                });
510                // The arguments are now scalar IR, so a call over fixed values
511                // folds; one over random variables stays.
512                func::fold_ground_calls(&call, self.funcs, &self.asg.states).map_err(|e| match e {
513                    FuncError::Eval(ConstraintEvalError::StateUnknown(s)) => {
514                        ExpandError::StateUnknown(s)
515                    }
516                    other => ExpandError::Func(other),
517                })
518            }
519            ArrayNode::Size(a) => Ok(konst(Ty::signed(32), self.size_of(*a)? as u64)),
520            ArrayNode::Loop(id, t) => {
521                let v = self.lookup(*id)?;
522                if v.ty() != *t {
523                    return Err(ExpandError::Malformed(
524                        "a loop variable bound at another type",
525                    ));
526                }
527                Ok(v)
528            }
529            ArrayNode::Pick {
530                index,
531                low,
532                elems,
533                ty,
534            } => {
535                let i = self.index_value(index)?;
536                let pos = i
537                    .checked_sub(*low)
538                    .and_then(|p| usize::try_from(p).ok())
539                    .filter(|&p| p < elems.len());
540                let Some(pos) = pos else {
541                    return Err(ExpandError::LeafIndexOutOfRange {
542                        index: i,
543                        size: elems.len(),
544                    });
545                };
546                let x = self.expand(&elems[pos])?;
547                if x.ty() != *ty {
548                    return Err(ExpandError::Malformed("a pick element of another type"));
549                }
550                Ok(x)
551            }
552            ArrayNode::Elem { array, index, ty } => {
553                let i = self.index_value(index)?;
554                let size = self.size_of(*array)?;
555                if i < 0 || i as usize >= size {
556                    return Err(ExpandError::IndexOutOfRange {
557                        array: self.res(*array),
558                        index: i,
559                        size,
560                    });
561                }
562                self.element(*array, i as usize, *ty)
563            }
564            ArrayNode::RowScope {
565                template,
566                row,
567                body,
568            } => {
569                let target = self.row_array(*template, row)?;
570                self.rebind.push((*template, target));
571                let r = self.expand(body);
572                self.rebind.pop();
573                r
574            }
575            ArrayNode::Distinct { scalars, arrays } => {
576                let mut members = Vec::new();
577                for s in scalars {
578                    members.push(self.expand(s)?);
579                }
580                for a in arrays {
581                    let size = self.size_of(*a)?;
582                    let elem_ty = self.array(self.res(*a))?.elem_ty;
583                    for i in 0..size {
584                        members.push(self.element(*a, i, elem_ty)?);
585                    }
586                }
587                if members.iter().any(|m| m.ty() != members[0].ty()) {
588                    return Err(ExpandError::Malformed(
589                        "the members of a unique group are of different types",
590                    ));
591                }
592                Ok(if members.len() < 2 {
593                    konst(Ty::BIT, 1)
594                } else {
595                    Expr::AllDistinct(members)
596                })
597            }
598            ArrayNode::Foreach { array, var, body } => {
599                let size = self.size_of(*array)?;
600                let mut acc: Option<Expr> = None;
601                for i in 0..size {
602                    self.env.push((*var, konst(Ty::signed(32), i as u64)));
603                    let b = self.expand(body);
604                    self.env.pop();
605                    let b = b?;
606                    acc = Some(match acc {
607                        None => b,
608                        Some(a) => Expr::Binary(BinOp::LogAnd, Box::new(a), Box::new(b), Ty::BIT),
609                    });
610                }
611                Ok(acc.unwrap_or_else(|| konst(Ty::BIT, 1)))
612            }
613            ArrayNode::Reduce {
614                op,
615                array,
616                var,
617                index_var,
618                body,
619            } => {
620                let size = self.size_of(*array)?;
621                let elem_ty = self.array(self.res(*array))?.elem_ty;
622                let ty = body.ty();
623                let mut acc: Option<Expr> = None;
624                for i in 0..size {
625                    let item = self.element(*array, i, elem_ty)?;
626                    self.env.push((*var, item));
627                    self.env.push((*index_var, konst(Ty::signed(32), i as u64)));
628                    let b = self.expand(body);
629                    self.env.pop();
630                    self.env.pop();
631                    let b = b?;
632                    acc = Some(match acc {
633                        None => b,
634                        Some(a) => {
635                            let bop = match op {
636                                ReduceOp::Sum => BinOp::Add,
637                                ReduceOp::Product => BinOp::Mul,
638                                ReduceOp::And => BinOp::And,
639                                ReduceOp::Or => BinOp::Or,
640                                ReduceOp::Xor => BinOp::Xor,
641                            };
642                            Expr::Binary(bop, Box::new(a), Box::new(b), ty)
643                        }
644                    });
645                }
646                Ok(acc.unwrap_or_else(|| {
647                    Expr::Const(
648                        ty,
649                        match op {
650                            ReduceOp::Product => BigUint::one(),
651                            ReduceOp::And => mask(ty.width),
652                            ReduceOp::Sum | ReduceOp::Or | ReduceOp::Xor => BigUint::zero(),
653                        },
654                    )
655                }))
656            }
657        }
658    }
659}
660
661/// `v == d0 || v == d1 || ...` for an enum element.
662fn domain_constraint(v: Expr, domain: &[BigUint]) -> Expr {
663    let t = v.ty();
664    let mut acc: Option<Expr> = None;
665    for d in domain {
666        let eq = Expr::Binary(
667            BinOp::Eq,
668            Box::new(v.clone()),
669            Box::new(Expr::Const(t, d.clone() & mask(t.width))),
670            Ty::BIT,
671        );
672        acc = Some(match acc {
673            None => eq,
674            Some(a) => Expr::Binary(BinOp::LogOr, Box::new(a), Box::new(eq), Ty::BIT),
675        });
676    }
677    acc.unwrap_or_else(|| konst(Ty::BIT, 0))
678}
679
680/// Expands the array-level `conjuncts` for the array sizes `sizes` (module
681/// docs). `n_vars0` is how many field variables the conjuncts already
682/// number; element variables are numbered after them. `states` are the
683/// caller's state values (an element of an inactive array becomes a state
684/// appended after them).
685pub fn expand(
686    conjuncts: &[Expr],
687    arrays: &[ArrayState],
688    sizes: &[usize],
689    n_vars0: u32,
690    states: &[Option<BigUint>],
691) -> Result<Expanded, ExpandError> {
692    expand_with(conjuncts, arrays, sizes, n_vars0, states, &[], &[])
693}
694
695/// [`expand`] for a program with functions: a call whose arguments the
696/// expansion makes fixed (an array size, an element of an inactive array, a
697/// state) is evaluated into a constant; a call over random variables stays for
698/// the staged solve ([`super::func_order`]).
699pub fn expand_with(
700    conjuncts: &[Expr],
701    arrays: &[ArrayState],
702    sizes: &[usize],
703    n_vars0: u32,
704    states: &[Option<BigUint>],
705    funcs: &[Func],
706    dists: &[Dist],
707) -> Result<Expanded, ExpandError> {
708    if arrays.len() != sizes.len() {
709        return Err(ExpandError::Malformed("one size per array"));
710    }
711    let mut out = Expanded::default();
712    let mut elem_base = vec![0u32; arrays.len()];
713    let mut next = n_vars0;
714    for (a, arr) in arrays.iter().enumerate() {
715        if !arr.active {
716            continue;
717        }
718        elem_base[a] = next;
719        for i in 0..sizes[a] {
720            out.elem_vars.push((a as ArrayId, i));
721            out.elem_tys.push(arr.elem_ty);
722            next += 1;
723            if let Some(dom) = &arr.domain {
724                let id = elem_base[a] + i as u32;
725                out.conjuncts
726                    .push(domain_constraint(Expr::Var(id, arr.elem_ty), dom));
727            }
728        }
729    }
730    let mut ex = Expander {
731        arrays,
732        sizes,
733        elem_base,
734        asg: Assignment {
735            vars: Vec::new(),
736            states: states.to_vec(),
737        },
738        synth: HashMap::new(),
739        env: Vec::new(),
740        funcs,
741        dists,
742        extra_dists: Vec::new(),
743        instanced: Vec::new(),
744        rebind: Vec::new(),
745    };
746    for c in conjuncts {
747        let e = ex.expand(c)?;
748        out.conjuncts.push(e);
749    }
750    out.extra_dists = std::mem::take(&mut ex.extra_dists);
751    out.extra_states = ex.asg.states.split_off(states.len());
752    Ok(out)
753}
754
755/// The constraints and arrays of the second sizing stage ([`lower_rows`]).
756#[derive(Clone, Debug)]
757pub struct Lowered {
758    /// The conjuncts with every `foreach` over an array of arrays unrolled and
759    /// every [`ArrayNode::RowScope`] whose row is known bound to its row array.
760    pub conjuncts: Vec<Expr>,
761    /// The arrays plus, after them, the row arrays; an array of arrays itself is
762    /// inactive with one placeholder element per row, so its size is a constant.
763    pub arrays: Vec<ArrayState>,
764    /// `(array of arrays, id of its row 0)`; row `k` is `base + k`.
765    pub bases: Vec<(ArrayId, ArrayId)>,
766}
767
768struct Lowerer<'a> {
769    /// The arrays after this pass made its row arrays (templates included).
770    arrays: &'a [ArrayState],
771    /// How many arrays the pass was given: those after them are its row
772    /// arrays, whose sizes are not yet chosen.
773    n_in: usize,
774    sizes: &'a [usize],
775    states: &'a [Option<BigUint>],
776    bases: &'a [(ArrayId, ArrayId)],
777    /// `(template, row array, template of the row's own rows)`: selecting the
778    /// row array for the first also rebinds the template of the rows below.
779    below: &'a [(ArrayId, ArrayId, ArrayId)],
780    env: Vec<(LoopId, i64)>,
781    rebind: Vec<(ArrayId, ArrayId)>,
782}
783
784impl Lowerer<'_> {
785    fn res(&self, a: ArrayId) -> ArrayId {
786        self.rebind
787            .iter()
788            .rev()
789            .find(|(t, _)| *t == a)
790            .map_or(a, |(_, r)| *r)
791    }
792
793    fn base_of(&self, outer: ArrayId) -> Option<ArrayId> {
794        self.bases
795            .iter()
796            .find(|(o, _)| *o == outer)
797            .map(|(_, b)| *b)
798    }
799
800    /// The value of `row` when it is fixed here: loop variables bound, sizes and
801    /// states read. A row that reads the size of an array whose size this pass
802    /// has not chosen yet is not fixed.
803    fn fixed(&self, row: &Expr) -> Option<i64> {
804        let mut stale = false;
805        let row = replace_sizes(row, &mut |a| {
806            let a = self.res(a);
807            stale |= a as usize >= self.n_in;
808            konst(
809                Ty::signed(32),
810                self.sizes.get(a as usize).copied().unwrap_or(0) as u64,
811            )
812        });
813        if stale {
814            return None;
815        }
816        let asg = Assignment {
817            vars: Vec::new(),
818            states: self.states.to_vec(),
819        };
820        let v = eval(&row, &asg).ok()?;
821        let t = row.ty();
822        Some(if t.signed && v.bit(u64::from(t.width) - 1) {
823            -i64::try_from((BigUint::one() << t.width) - &v).ok()?
824        } else {
825            i64::try_from(&v).ok()?
826        })
827    }
828
829    fn low(&mut self, e: &Expr) -> Result<Expr, ExpandError> {
830        let Expr::Array(n) = e else {
831            return e.try_map_children(&mut |c| self.low(c));
832        };
833        match n {
834            ArrayNode::Loop(id, t) => Ok(match self.env.iter().rev().find(|(l, _)| l == id) {
835                Some(&(_, i)) => Expr::constant(*t, i as u64),
836                None => e.clone(),
837            }),
838            ArrayNode::Size(a) => Ok(Expr::Array(ArrayNode::Size(self.res(*a)))),
839            ArrayNode::Elem { array, index, ty } => Ok(Expr::Array(ArrayNode::Elem {
840                array: self.res(*array),
841                index: Box::new(self.low(index)?),
842                ty: *ty,
843            })),
844            ArrayNode::Distinct { scalars, arrays } => Ok(Expr::Array(ArrayNode::Distinct {
845                scalars: scalars
846                    .iter()
847                    .map(|x| self.low(x))
848                    .collect::<Result<_, _>>()?,
849                arrays: arrays.iter().map(|a| self.res(*a)).collect(),
850            })),
851            ArrayNode::Reduce {
852                op,
853                array,
854                var,
855                index_var,
856                body,
857            } => Ok(Expr::Array(ArrayNode::Reduce {
858                op: *op,
859                array: self.res(*array),
860                var: *var,
861                index_var: *index_var,
862                body: Box::new(self.low(body)?),
863            })),
864            ArrayNode::Foreach { array, var, body } => {
865                let a = self.res(*array);
866                if self.base_of(a).is_none() {
867                    return Ok(Expr::Array(ArrayNode::Foreach {
868                        array: a,
869                        var: *var,
870                        body: Box::new(self.low(body)?),
871                    }));
872                }
873                let mut acc: Option<Expr> = None;
874                for i in 0..self.sizes[a as usize] {
875                    self.env.push((*var, i as i64));
876                    let b = self.low(body);
877                    self.env.pop();
878                    let b = b?;
879                    acc = Some(match acc {
880                        None => b,
881                        Some(x) => Expr::Binary(BinOp::LogAnd, Box::new(x), Box::new(b), Ty::BIT),
882                    });
883                }
884                Ok(acc.unwrap_or_else(|| konst(Ty::BIT, 1)))
885            }
886            ArrayNode::RowScope {
887                template,
888                row,
889                body,
890            } => {
891                let row = self.low(row)?;
892                // The template may stand for a row an enclosing scope selected
893                // (`a[i][j]` inside `foreach (a[i, j])`: the template of `a[i]`'s
894                // rows is bound by both scopes, the inner shadowing the outer);
895                // a template of a deeper level stands for the template made for
896                // the row above it.
897                let raw = *template;
898                let bound = self.res(raw);
899                let tpl = if self
900                    .arrays
901                    .get(bound as usize)
902                    .is_some_and(|t| t.template_of.is_some())
903                {
904                    bound
905                } else {
906                    raw
907                };
908                let outer = self
909                    .arrays
910                    .get(tpl as usize)
911                    .and_then(|t| t.template_of)
912                    .map(|o| self.res(o))
913                    .ok_or(ExpandError::Malformed("a row scope over a non-template"))?;
914                let depth = self.rebind.len();
915                let (Some(k), Some(base)) = (self.fixed(&row), self.base_of(outer)) else {
916                    // The row, or the rows of the array of arrays, are only
917                    // known later: it binds then, and the body names the
918                    // template, not the row an enclosing scope selected.
919                    self.rebind.push((raw, tpl));
920                    self.rebind.push((tpl, tpl));
921                    let body = self.low(body);
922                    self.rebind.truncate(depth);
923                    return Ok(Expr::Array(ArrayNode::RowScope {
924                        template: tpl,
925                        row: Box::new(row),
926                        body: Box::new(body?),
927                    }));
928                };
929                let size = self.sizes[outer as usize];
930                if k < 0 || k as usize >= size {
931                    return Err(ExpandError::IndexOutOfRange {
932                        array: outer,
933                        index: k,
934                        size,
935                    });
936                }
937                let picked = base + k as ArrayId;
938                self.rebind.push((raw, picked));
939                self.rebind.push((tpl, picked));
940                for &(t, r, below) in self.below {
941                    if r == picked {
942                        self.rebind.push((t, below));
943                    }
944                }
945                let r = self.low(body);
946                self.rebind.truncate(depth);
947                r
948            }
949            ArrayNode::Call { .. } | ArrayNode::Pick { .. } => {
950                e.try_map_children(&mut |c| self.low(c))
951            }
952        }
953    }
954}
955
956/// Binds the rows of every array of arrays whose rows are not bound yet, for
957/// one stage of the sizing (module docs of [`ArrayNode::RowScope`]): `sizes`
958/// are the sizes the stage before chose. Every such array gets one row array
959/// per row (the `row_base` ids), becomes inactive with that many placeholder
960/// elements, and the conjuncts name rows directly. A row that is itself an
961/// array of arrays (`int a[][][]`) comes out unlowered, with a template of its
962/// own for its rows, and is bound by the next call once its size is chosen.
963pub fn lower_rows(
964    conjuncts: &[Expr],
965    arrays: &[ArrayState],
966    sizes: &[usize],
967    states: &[Option<BigUint>],
968) -> Result<Lowered, ExpandError> {
969    let mut out: Vec<ArrayState> = arrays.to_vec();
970    let mut bases = Vec::new();
971    let mut below = Vec::new();
972    let mut sizes2: Vec<usize> = sizes.to_vec();
973    for (a, arr) in arrays.iter().enumerate() {
974        let Some(rows) = arr.rows.as_ref().filter(|r| !r.lowered) else {
975            continue;
976        };
977        let n = sizes[a];
978        let base = out.len() as ArrayId;
979        bases.push((a as ArrayId, base));
980        for k in 0..n {
981            let row = if rows.levels <= 1 {
982                ArrayState {
983                    elem_ty: arr.elem_ty,
984                    active: arr.active,
985                    cur: rows.cur_rows.get(k).cloned().unwrap_or_default(),
986                    domain: arr.domain.clone(),
987                    rows: None,
988                    template_of: None,
989                }
990            } else {
991                rows.deeper.get(k).cloned().unwrap_or_else(|| ArrayState {
992                    elem_ty: arr.elem_ty,
993                    active: arr.active,
994                    cur: Vec::new(),
995                    domain: arr.domain.clone(),
996                    rows: Some(RowsState::new(rows.levels - 1, Vec::new(), Vec::new())),
997                    template_of: None,
998                })
999            };
1000            sizes2.push(row.cur.len());
1001            out.push(row);
1002        }
1003        // A row that is an array of arrays names its own rows through a
1004        // template of its own (the shared one stands for "the row" of an
1005        // enclosing scope only until that scope picks a row).
1006        if rows.levels > 1 {
1007            for k in 0..n {
1008                let row = base + k as ArrayId;
1009                let shared = rows.templates.get(1).copied().unwrap_or(u32::MAX);
1010                let r = out[row as usize].rows.as_mut().expect("a row with rows");
1011                r.templates = rows.templates[1..].to_vec();
1012                if shared == u32::MAX {
1013                    continue;
1014                }
1015                let tid = out.len() as ArrayId;
1016                out[row as usize].rows.as_mut().expect("rows").templates[0] = tid;
1017                below.push((shared, row, tid));
1018                out.push(ArrayState {
1019                    elem_ty: arr.elem_ty,
1020                    active: false,
1021                    cur: Vec::new(),
1022                    domain: None,
1023                    rows: None,
1024                    template_of: Some(row),
1025                });
1026                sizes2.push(0);
1027            }
1028        }
1029        out[a].active = false;
1030        out[a].cur = vec![Some(BigUint::zero()); n];
1031        if let Some(r) = out[a].rows.as_mut() {
1032            r.row_base = base;
1033            r.lowered = true;
1034        }
1035    }
1036    let mut lw = Lowerer {
1037        arrays: &out,
1038        n_in: arrays.len(),
1039        sizes: &sizes2,
1040        states,
1041        bases: &bases,
1042        below: &below,
1043        env: Vec::new(),
1044        rebind: Vec::new(),
1045    };
1046    let lowered = conjuncts
1047        .iter()
1048        .map(|c| lw.low(c))
1049        .collect::<Result<Vec<_>, _>>()?;
1050    Ok(Lowered {
1051        conjuncts: lowered,
1052        arrays: out,
1053        bases,
1054    })
1055}
1056
1057/// Why [`solve_sizes`] failed.
1058#[derive(Clone, Debug, PartialEq, Eq)]
1059pub enum SizeError {
1060    /// No size assignment satisfies the size constraints.
1061    Unsatisfiable,
1062    /// Only sizes beyond [`SIZE_TIERS`] satisfy them.
1063    TooLarge,
1064    /// A guard in a size constraint is `ERROR`.
1065    GuardError,
1066    /// The size sub-problem failed otherwise.
1067    Solve(SolveError),
1068}
1069
1070/// The array sizes chosen for one attempt.
1071#[derive(Clone, Debug, PartialEq, Eq)]
1072pub struct SizeSolution {
1073    /// The size of every array (current size unless a constraint chose one).
1074    pub sizes: Vec<usize>,
1075    /// The arrays a constraint sized, in the order of [`Self::tuple`].
1076    pub sized: Vec<ArrayId>,
1077    /// The chosen sizes of [`Self::sized`] (what a retry blocks).
1078    pub tuple: Vec<usize>,
1079}
1080
1081/// Whether `c` takes part in the size sub-problem: no element, loop or `dist`
1082/// selector reference.
1083fn size_eligible(c: &Expr) -> bool {
1084    !c.any_node(&mut |n| match n {
1085        Expr::Array(
1086            ArrayNode::Elem { .. }
1087            | ArrayNode::Loop(..)
1088            | ArrayNode::Foreach { .. }
1089            | ArrayNode::Reduce { .. }
1090            | ArrayNode::Distinct { .. }
1091            // An index is only known once the node is expanded.
1092            | ArrayNode::Pick { .. }
1093            // A row is only known once the outer size is ([`lower_rows`]).
1094            | ArrayNode::RowScope { .. }
1095            // A call over a size (the pre-pass folded every fixed one) is
1096            // only decidable once the arguments are known.
1097            | ArrayNode::Call { .. },
1098        ) => true,
1099        Expr::Var(v, _) => *v >= super::dist::SELECTOR_BASE,
1100        _ => false,
1101    })
1102}
1103
1104fn replace_sizes(e: &Expr, f: &mut impl FnMut(ArrayId) -> Expr) -> Expr {
1105    match e {
1106        Expr::Array(ArrayNode::Size(a)) => f(*a),
1107        other => other
1108            .try_map_children::<()>(&mut |c| Ok(replace_sizes(c, f)))
1109            .expect("infallible"),
1110    }
1111}
1112
1113fn find(parent: &mut [usize], x: usize) -> usize {
1114    let mut r = x;
1115    while parent[r] != r {
1116        r = parent[r];
1117    }
1118    let mut c = x;
1119    while parent[c] != r {
1120        let n = parent[c];
1121        parent[c] = r;
1122        c = n;
1123    }
1124    r
1125}
1126
1127/// Chooses the sizes of the `rand`-active arrays whose `size` the size-eligible
1128/// `conjuncts` constrain (module docs). `var_tys[v]` is the type of field
1129/// variable `v` (`conjuncts` are over `0..var_tys.len()`), `states` the state
1130/// values, `blocked` size tuples ruled out by earlier attempts (in the
1131/// `sized` order every call reproduces).
1132pub fn solve_sizes(
1133    conjuncts: &[Expr],
1134    arrays: &[ArrayState],
1135    var_tys: &[Ty],
1136    states: &[Option<BigUint>],
1137    blocked: &[Vec<usize>],
1138    seed: &mut u64,
1139) -> Result<SizeSolution, SizeError> {
1140    let current: Vec<usize> = arrays.iter().map(|a| a.cur.len()).collect();
1141    let n_fields = var_tys.len() as u32;
1142    // 1. Which active arrays does an eligible conjunct size?
1143    let mut sized: Vec<ArrayId> = Vec::new();
1144    let mut eligible: Vec<&Expr> = Vec::new();
1145    for c in conjuncts {
1146        if !size_eligible(c) {
1147            continue;
1148        }
1149        c.any_node(&mut |n| {
1150            if let Expr::Array(ArrayNode::Size(a)) = n
1151                && arrays.get(*a as usize).is_some_and(|x| x.active)
1152                && !sized.contains(a)
1153            {
1154                sized.push(*a);
1155            }
1156            false
1157        });
1158        eligible.push(c);
1159    }
1160    if sized.is_empty() {
1161        return Ok(SizeSolution {
1162            sizes: current,
1163            sized,
1164            tuple: Vec::new(),
1165        });
1166    }
1167    sized.sort_unstable();
1168    let size_var = |a: ArrayId| -> u32 {
1169        n_fields + u32::try_from(sized.iter().position(|x| *x == a).expect("sized")).unwrap()
1170    };
1171    // 2. Substitute the size variables, then keep the conjuncts connected to one.
1172    let total = n_fields as usize + sized.len();
1173    let subbed: Vec<Expr> = eligible
1174        .iter()
1175        .map(|c| {
1176            replace_sizes(c, &mut |a| {
1177                if arrays[a as usize].active {
1178                    Expr::Var(size_var(a), Ty::signed(32))
1179                } else {
1180                    konst(Ty::signed(32), current[a as usize] as u64)
1181                }
1182            })
1183        })
1184        .collect();
1185    let mut parent: Vec<usize> = (0..total).collect();
1186    let vars_of: Vec<Vec<VarId>> = subbed
1187        .iter()
1188        .map(|c| {
1189            let mut v = Vec::new();
1190            c.for_each_var(&mut |x, _| v.push(x));
1191            v
1192        })
1193        .collect();
1194    for vs in &vars_of {
1195        for w in vs.windows(2) {
1196            let (a, b) = (
1197                find(&mut parent, w[0] as usize),
1198                find(&mut parent, w[1] as usize),
1199            );
1200            parent[a] = b;
1201        }
1202    }
1203    let size_roots: Vec<usize> = (0..sized.len())
1204        .map(|k| find(&mut parent, n_fields as usize + k))
1205        .collect();
1206    let mut keep: Vec<Expr> = Vec::new();
1207    let mut used = vec![false; total];
1208    for k in 0..sized.len() {
1209        used[n_fields as usize + k] = true;
1210    }
1211    for (c, vs) in subbed.iter().zip(&vars_of) {
1212        let Some(&v0) = vs.first() else { continue };
1213        let root = find(&mut parent, v0 as usize);
1214        if size_roots.contains(&root) {
1215            for &v in vs {
1216                used[v as usize] = true;
1217            }
1218            keep.push(c.clone());
1219        }
1220    }
1221    // 3. Compact the variables.
1222    let mut new_id = vec![0u32; total];
1223    let mut tys: Vec<Ty> = Vec::new();
1224    for (v, u) in used.iter().enumerate() {
1225        if *u {
1226            new_id[v] = tys.len() as u32;
1227            tys.push(if v < n_fields as usize {
1228                var_tys[v]
1229            } else {
1230                Ty::signed(32)
1231            });
1232        }
1233    }
1234    let mut keep: Vec<Expr> = keep
1235        .iter()
1236        .map(|c| c.substitute_vars(&mut |v, t| Expr::Var(new_id[v as usize], t)))
1237        .collect();
1238    let svar = |k: usize| Expr::Var(new_id[n_fields as usize + k], Ty::signed(32));
1239    // Blocked tuples.
1240    for tup in blocked {
1241        let mut same: Option<Expr> = None;
1242        for (k, &sz) in tup.iter().enumerate() {
1243            let eq = Expr::Binary(
1244                BinOp::Eq,
1245                Box::new(svar(k)),
1246                Box::new(konst(Ty::signed(32), sz as u64)),
1247                Ty::BIT,
1248            );
1249            same = Some(match same {
1250                None => eq,
1251                Some(a) => Expr::Binary(BinOp::LogAnd, Box::new(a), Box::new(eq), Ty::BIT),
1252            });
1253        }
1254        if let Some(s) = same {
1255            keep.push(Expr::Unary(UnOp::LogNot, Box::new(s), Ty::BIT));
1256        }
1257    }
1258    let assignment = Assignment {
1259        vars: Vec::new(),
1260        states: states.to_vec(),
1261    };
1262    let order: Vec<VarId> = (0..tys.len() as u32).collect();
1263    let tiers: [u64; 3] = [SIZE_TIERS[0], SIZE_TIERS[1], i32::MAX as u64];
1264    for (ti, bound) in tiers.iter().enumerate() {
1265        let mut cs = keep.clone();
1266        for k in 0..sized.len() {
1267            cs.push(Expr::Binary(
1268                BinOp::Ge,
1269                Box::new(svar(k)),
1270                Box::new(konst(Ty::signed(32), 0)),
1271                Ty::BIT,
1272            ));
1273            cs.push(Expr::Binary(
1274                BinOp::Le,
1275                Box::new(svar(k)),
1276                Box::new(konst(Ty::signed(32), *bound)),
1277                Ty::BIT,
1278            ));
1279        }
1280        let mut resolved = Vec::with_capacity(cs.len());
1281        for c in &cs {
1282            resolved.push(guard::resolve(c, &assignment).map_err(|_| SizeError::GuardError)?);
1283        }
1284        let solver = match Solver::new(&tys, states, &resolved) {
1285            Ok(s) => s,
1286            Err(SolveError::Unsatisfiable) => continue,
1287            Err(e) => return Err(SizeError::Solve(e)),
1288        };
1289        let values = match solver.sample(&order, seed) {
1290            Ok(v) => v,
1291            Err(SolveError::Unsatisfiable) => continue,
1292            Err(e) => return Err(SizeError::Solve(e)),
1293        };
1294        if ti == 2 {
1295            return Err(SizeError::TooLarge);
1296        }
1297        let mut sizes = current;
1298        let mut tuple = Vec::with_capacity(sized.len());
1299        for (k, &a) in sized.iter().enumerate() {
1300            let v = &values[new_id[n_fields as usize + k] as usize];
1301            let n = usize::try_from(v).expect("a bounded size");
1302            sizes[a as usize] = n;
1303            tuple.push(n);
1304        }
1305        return Ok(SizeSolution {
1306            sizes,
1307            sized,
1308            tuple,
1309        });
1310    }
1311    Err(SizeError::Unsatisfiable)
1312}
1313
1314#[cfg(test)]
1315mod tests {
1316    use super::*;
1317
1318    fn s32() -> Ty {
1319        Ty::signed(32)
1320    }
1321    fn u(w: u32) -> Ty {
1322        Ty::unsigned(w)
1323    }
1324    fn size(a: ArrayId) -> Expr {
1325        Expr::Array(ArrayNode::Size(a))
1326    }
1327    fn elem(a: ArrayId, i: Expr, t: Ty) -> Expr {
1328        Expr::Array(ArrayNode::Elem {
1329            array: a,
1330            index: Box::new(i),
1331            ty: t,
1332        })
1333    }
1334    fn lp(id: LoopId) -> Expr {
1335        Expr::Array(ArrayNode::Loop(id, s32()))
1336    }
1337    fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
1338        Expr::Binary(op, Box::new(a), Box::new(b), t)
1339    }
1340    fn arr(active: bool, cur: &[u64]) -> ArrayState {
1341        ArrayState {
1342            elem_ty: s32(),
1343            active,
1344            cur: cur.iter().map(|v| Some(BigUint::from(*v))).collect(),
1345            domain: None,
1346            rows: None,
1347            template_of: None,
1348        }
1349    }
1350    fn ev(e: &Expr, vars: &[u64], states: &[Option<BigUint>]) -> bool {
1351        let a = Assignment {
1352            vars: vars.iter().map(|v| BigUint::from(*v)).collect(),
1353            states: states.to_vec(),
1354        };
1355        !eval(e, &a).expect("evaluates").is_zero()
1356    }
1357
1358    /// LRM 18.5.7.1: `foreach (A[j]) A[j] > 2*j` unrolls to one comparison per
1359    /// element, over one fresh variable each.
1360    #[test]
1361    fn foreach_unrolls_over_the_size() {
1362        let body = bin(
1363            BinOp::Gt,
1364            elem(0, lp(0), s32()),
1365            bin(BinOp::Mul, konst(s32(), 2), lp(0), s32()),
1366            Ty::BIT,
1367        );
1368        let c = Expr::Array(ArrayNode::Foreach {
1369            array: 0,
1370            var: 0,
1371            body: Box::new(body),
1372        });
1373        let x = expand(&[c], &[arr(true, &[])], &[3], 0, &[]).expect("expands");
1374        assert_eq!(x.elem_vars, vec![(0, 0), (0, 1), (0, 2)]);
1375        assert_eq!(x.conjuncts.len(), 1);
1376        assert!(ev(&x.conjuncts[0], &[1, 3, 5], &[]));
1377        assert!(!ev(&x.conjuncts[0], &[1, 2, 5], &[]));
1378        assert!(!ev(&x.conjuncts[0], &[0, 3, 5], &[]));
1379    }
1380
1381    /// The ascending-sort example: `if (k < A.size-1) A[k] < A[k+1]` never
1382    /// reads `A[size]` because the constant guard drops that arm.
1383    #[test]
1384    fn a_constant_guard_keeps_the_last_neighbour_unread() {
1385        let pred = bin(
1386            BinOp::Lt,
1387            lp(0),
1388            bin(BinOp::Sub, size(0), konst(s32(), 1), s32()),
1389            Ty::BIT,
1390        );
1391        let then = bin(
1392            BinOp::Lt,
1393            elem(0, lp(0), s32()),
1394            elem(0, bin(BinOp::Add, lp(0), konst(s32(), 1), s32()), s32()),
1395            Ty::BIT,
1396        );
1397        let body = Expr::Guard(
1398            Box::new(pred),
1399            Box::new(then),
1400            Box::new(konst(Ty::BIT, 1)),
1401            Ty::BIT,
1402        );
1403        let c = Expr::Array(ArrayNode::Foreach {
1404            array: 0,
1405            var: 0,
1406            body: Box::new(body),
1407        });
1408        let x = expand(&[c], &[arr(true, &[])], &[4], 0, &[]).expect("expands");
1409        assert_eq!(x.elem_vars.len(), 4);
1410        assert!(ev(&x.conjuncts[0], &[1, 2, 3, 4], &[]));
1411        assert!(!ev(&x.conjuncts[0], &[1, 3, 2, 4], &[]));
1412    }
1413
1414    #[test]
1415    fn an_unguarded_out_of_range_index_is_a_loud_error() {
1416        let c = bin(
1417            BinOp::Eq,
1418            elem(0, konst(s32(), 5), s32()),
1419            konst(s32(), 0),
1420            Ty::BIT,
1421        );
1422        assert_eq!(
1423            expand(&[c], &[arr(true, &[])], &[2], 0, &[]).unwrap_err(),
1424            ExpandError::IndexOutOfRange {
1425                array: 0,
1426                index: 5,
1427                size: 2
1428            }
1429        );
1430    }
1431
1432    /// A pick (a fixed-size array in a class property, XC-059) expands to the
1433    /// element its state index names, counted from the array's lowest index.
1434    #[test]
1435    fn a_pick_selects_the_element_its_index_names() {
1436        let pick = |idx: Expr| {
1437            Expr::Array(ArrayNode::Pick {
1438                index: Box::new(idx),
1439                low: 2,
1440                elems: vec![Expr::Var(0, u(8)), Expr::Var(1, u(8)), Expr::Var(2, u(8))],
1441                ty: u(8),
1442            })
1443        };
1444        let states = [Some(BigUint::from(3u32))];
1445        let c = bin(
1446            BinOp::Eq,
1447            pick(Expr::State(0, s32())),
1448            konst(u(8), 9),
1449            Ty::BIT,
1450        );
1451        let x = expand(&[c], &[], &[], 3, &states).expect("expands");
1452        assert_eq!(x.conjuncts.len(), 1);
1453        assert!(ev(&x.conjuncts[0], &[0, 9, 0], &states));
1454        assert!(!ev(&x.conjuncts[0], &[9, 0, 9], &states));
1455    }
1456
1457    /// Only the chosen element is expanded, so a constant-false guard keeps an
1458    /// out-of-range pick unread; a live one is a loud error naming the index
1459    /// and the size (LRM 18.5.7.1).
1460    #[test]
1461    fn an_out_of_range_pick_is_an_error_only_when_live() {
1462        let pick = Expr::Array(ArrayNode::Pick {
1463            index: Box::new(Expr::State(0, s32())),
1464            low: 0,
1465            elems: vec![Expr::Var(0, u(8)), Expr::Var(1, u(8))],
1466            ty: u(8),
1467        });
1468        let body = bin(BinOp::Eq, pick, konst(u(8), 1), Ty::BIT);
1469        let guarded = Expr::Guard(
1470            Box::new(bin(
1471                BinOp::Lt,
1472                Expr::State(0, s32()),
1473                konst(s32(), 2),
1474                Ty::BIT,
1475            )),
1476            Box::new(body.clone()),
1477            Box::new(konst(Ty::BIT, 1)),
1478            Ty::BIT,
1479        );
1480        let out = [Some(BigUint::from(7u32))];
1481        let x = expand(&[guarded], &[], &[], 2, &out).expect("the guard drops the pick");
1482        assert!(ev(&x.conjuncts[0], &[], &out));
1483        assert_eq!(
1484            expand(std::slice::from_ref(&body), &[], &[], 2, &out).unwrap_err(),
1485            ExpandError::LeafIndexOutOfRange { index: 7, size: 2 }
1486        );
1487        // A negative index (an `int` state of -1) is out of range too.
1488        let neg = [Some((BigUint::from(1u32) << 32) - 1u32)];
1489        assert_eq!(
1490            expand(&[body], &[], &[], 2, &neg).unwrap_err(),
1491            ExpandError::LeafIndexOutOfRange { index: -1, size: 2 }
1492        );
1493    }
1494
1495    /// An inactive array is state: its elements read as the current values
1496    /// and its size as the current length.
1497    #[test]
1498    fn an_inactive_array_is_state() {
1499        let c = bin(
1500            BinOp::Eq,
1501            elem(0, konst(s32(), 1), s32()),
1502            konst(s32(), 7),
1503            Ty::BIT,
1504        );
1505        let x = expand(&[c], &[arr(false, &[3, 7])], &[2], 0, &[]).expect("expands");
1506        assert!(x.elem_vars.is_empty());
1507        assert_eq!(x.extra_states, vec![Some(BigUint::from(7u32))]);
1508        assert!(ev(&x.conjuncts[0], &[], &x.extra_states));
1509    }
1510
1511    #[test]
1512    fn reductions_fold_and_empty_ones_are_the_identity() {
1513        let sum = |op| {
1514            Expr::Array(ArrayNode::Reduce {
1515                op,
1516                array: 0,
1517                var: 0,
1518                index_var: 1,
1519                body: Box::new(Expr::Array(ArrayNode::Loop(0, s32()))),
1520            })
1521        };
1522        let c = bin(BinOp::Eq, sum(ReduceOp::Sum), konst(s32(), 6), Ty::BIT);
1523        let x = expand(&[c], &[arr(true, &[])], &[3], 0, &[]).expect("expands");
1524        assert!(ev(&x.conjuncts[0], &[1, 2, 3], &[]));
1525        assert!(!ev(&x.conjuncts[0], &[1, 2, 4], &[]));
1526        for (op, want) in [
1527            (ReduceOp::Sum, 0u64),
1528            (ReduceOp::Product, 1),
1529            (ReduceOp::Or, 0),
1530            (ReduceOp::Xor, 0),
1531            (ReduceOp::And, 0xFFFF_FFFF),
1532        ] {
1533            let c = bin(BinOp::Eq, sum(op), konst(s32(), want), Ty::BIT);
1534            let x = expand(&[c], &[arr(true, &[])], &[0], 0, &[]).expect("expands");
1535            assert!(ev(&x.conjuncts[0], &[], &[]), "{op:?}");
1536        }
1537    }
1538
1539    /// LRM 18.4: `data.size == len` sizes the array to a drawn `len`.
1540    #[test]
1541    fn a_size_constraint_resizes_the_array() {
1542        let c = bin(
1543            BinOp::Eq,
1544            size(0),
1545            Expr::Resize(Box::new(Expr::Var(0, u(8))), s32()),
1546            Ty::BIT,
1547        );
1548        let bound = bin(BinOp::Lt, Expr::Var(0, u(8)), konst(u(8), 5), Ty::BIT);
1549        let mut seen = std::collections::BTreeSet::new();
1550        let mut seed = 1u64;
1551        for _ in 0..200 {
1552            let s = solve_sizes(
1553                &[c.clone(), bound.clone()],
1554                &[arr(true, &[9, 9])],
1555                &[u(8)],
1556                &[],
1557                &[],
1558                &mut seed,
1559            )
1560            .expect("solves");
1561            assert_eq!(s.sized, vec![0]);
1562            assert!(s.sizes[0] < 5);
1563            seen.insert(s.sizes[0]);
1564        }
1565        assert_eq!(seen.len(), 5, "every size 0..5 is reachable: {seen:?}");
1566    }
1567
1568    #[test]
1569    fn an_unconstrained_size_keeps_the_array() {
1570        let mut seed = 1u64;
1571        let s = solve_sizes(&[], &[arr(true, &[1, 2, 3])], &[], &[], &[], &mut seed).unwrap();
1572        assert_eq!(s.sizes, vec![3]);
1573        assert!(s.sized.is_empty());
1574    }
1575
1576    #[test]
1577    fn an_impossible_size_is_unsatisfiable_and_a_huge_one_is_too_large() {
1578        let mut seed = 1u64;
1579        let lt = |n: u64| bin(BinOp::Lt, size(0), konst(s32(), n), Ty::BIT);
1580        let gt = |n: u64| bin(BinOp::Gt, size(0), konst(s32(), n), Ty::BIT);
1581        let a = [arr(true, &[])];
1582        assert_eq!(
1583            solve_sizes(&[lt(3), gt(5)], &a, &[], &[], &[], &mut seed).unwrap_err(),
1584            SizeError::Unsatisfiable
1585        );
1586        assert_eq!(
1587            solve_sizes(&[gt(10_000_000)], &a, &[], &[], &[], &mut seed).unwrap_err(),
1588            SizeError::TooLarge
1589        );
1590        // Beyond the first tier but within the second.
1591        let s = solve_sizes(&[gt(2000)], &a, &[], &[], &[], &mut seed).unwrap();
1592        assert!(s.sizes[0] > 2000);
1593    }
1594
1595    #[test]
1596    fn blocked_tuples_are_excluded() {
1597        let c = bin(BinOp::Le, size(0), konst(s32(), 1), Ty::BIT);
1598        let a = [arr(true, &[])];
1599        let mut seed = 3u64;
1600        let s = solve_sizes(
1601            std::slice::from_ref(&c),
1602            &a,
1603            &[],
1604            &[],
1605            &[vec![0]],
1606            &mut seed,
1607        )
1608        .unwrap();
1609        assert_eq!(s.sizes, vec![1]);
1610        assert_eq!(
1611            solve_sizes(&[c], &a, &[], &[], &[vec![0], vec![1]], &mut seed).unwrap_err(),
1612            SizeError::Unsatisfiable
1613        );
1614    }
1615
1616    /// A conjunct with an element reference is not part of the size problem,
1617    /// and a disconnected one is left out.
1618    #[test]
1619    fn the_size_problem_holds_only_connected_size_constraints() {
1620        let elem_c = bin(
1621            BinOp::Eq,
1622            elem(0, konst(s32(), 0), s32()),
1623            konst(s32(), 1),
1624            Ty::BIT,
1625        );
1626        let unrelated = bin(BinOp::Eq, Expr::Var(0, u(8)), konst(u(8), 200), Ty::BIT);
1627        let c = bin(BinOp::Eq, size(0), konst(s32(), 2), Ty::BIT);
1628        let mut seed = 1u64;
1629        let s = solve_sizes(
1630            &[elem_c, unrelated, c],
1631            &[arr(true, &[])],
1632            &[u(8)],
1633            &[],
1634            &[],
1635            &mut seed,
1636        )
1637        .unwrap();
1638        assert_eq!(s.sizes, vec![2]);
1639    }
1640}