Skip to main content

vj_sim/constraints/
registry.rs

1//! Run-time dispatch of `obj.randomize()` (plan C3.5; LRM 18.6.1, 18.6.3).
2//!
3//! # What this is
4//!
5//! Compiling a class (`vj-codegen`'s `classes_randomize`) turns its `rand`
6//! fields and constraint blocks into a [`Program`]: the constraint IR plus,
7//! for every random and state variable, *where its value lives* in an object's
8//! flat field buffer. The program is registered per [`ClassTemplateId`] in a
9//! process-wide table (the same shape and lifetime as the other per-template
10//! tables in [`crate::objects`]), and a compiled `obj.randomize()` is one
11//! `sv_rt_obj(Randomize, h, ..)` call that lands in [`randomize`] here.
12//!
13//! # Dispatch is by dynamic class
14//!
15//! [`randomize`] looks the program up under the object's *dynamic* template
16//! ([`objects::dynamic_template`]), so `base_handle.randomize()` on a derived
17//! object solves the derived class's constraints (LRM 18.6.1: `randomize` is
18//! virtual). No vtable slot is involved: the object already carries its own
19//! template id, and a program is registered for every class template, so
20//! "dispatch" is a table lookup, which is virtual in effect and cannot collide
21//! with a user method.
22//!
23//! # Semantics implemented here
24//!
25//! * A `rand_mode(0)` variable is inactive and "treated the same as any other
26//!   state variable" (LRM 18.8): it is fixed at its current value for the
27//!   solve, and a variable that is not rand-active is not forced into its
28//!   enum domain either.
29//! * A state variable (or an inactive random variable) holding an X/Z bit is
30//!   an error ([`Outcome::StateUnknown`], IEEE 1800-2023 18.3), never a silent
31//!   0.
32//! * Success writes every active random variable (2-state value, no unknown
33//!   bits) and stores the advanced PRNG state back to the object.
34//! * Failure of any kind writes **nothing** and does not advance the object's
35//!   PRNG (LRM 18.6.3: "if randomize fails, the constraints are infeasible
36//!   and the random variables retain their previous values"). The solver
37//!   consumes no draws for an unsatisfiable problem, and the seed is only
38//!   stored on success, so the second property holds for every error.
39//!
40//! * A `randc` variable is drawn first, from its own cyclic permutation (LRM
41//!   18.4.2; [`super::randc`]). Its cycle state belongs to the object that
42//!   owns the field (the randomized object for an instance property, the class's
43//!   static-property object for a `static randc`, so every instance shares one
44//!   cycle) and is committed only when the call succeeds. A pick never fails:
45//!   the LRM allows `randc` combined with other constraints to "sometimes cause
46//!   randomize() to fail", but this implementation starts a new cycle instead
47//!   whenever nothing left in the current one is feasible, so a satisfiable
48//!   problem always solves.
49//! * A `static` property is not in the randomized object: the call site passes
50//!   the handle of each static-property object it can reach and a program
51//!   that needs one it was not given fails with [`Outcome::StaticUnavailable`].
52//!
53//! `pre_randomize()`/`post_randomize()` are not run here: they are compiled
54//! subroutines, so the call site runs them around this call
55//! ([`super::walk`], C18). What a call site adds to its class's constraints is a
56//! [`Spec`] (C19, C20): the inline constraints of `with {}` as a fragment
57//! program laid over the object's program by [`joint::overlay`], the values of
58//! the call's local variables as constants, and the variable list of
59//! `randomize(v, ...)` as the complete set of random variables
60//! ([`joint::restrict`]). [`randomize_spec`] is the entry point.
61
62use super::array;
63use super::bdd::BddBuildError;
64use super::dist::{Dist, DistError, SELECTOR_BASE};
65use super::func::{self, Func, FuncError};
66use super::func_order;
67use super::guard;
68use super::ir::{Assignment, ConstraintEvalError, Expr, Ty, eval, mask};
69use super::joint;
70use super::order;
71use super::priority::{self, Soft};
72use super::randc::RandcState;
73use super::real_domain::{self, FloatKind, RealError, RealProblem};
74use super::reduce::{self, BlockItems};
75use super::solve::{Engine, Group, Measure, Plan, SolveError, Weighted};
76use crate::collections;
77use crate::netlist::ClassTemplateId;
78use crate::objects::{self, FieldSlot, Window};
79use num_bigint::BigUint;
80use num_traits::{ToPrimitive, Zero};
81use std::collections::HashMap;
82use std::sync::atomic::{AtomicU32, Ordering};
83use std::sync::{Arc, Mutex, OnceLock};
84
85/// Which heap object a [`FieldSlot`] of a [`Program`] is an offset into.
86#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
87pub enum Home {
88    /// The object being randomized (an instance property).
89    Instance,
90    /// The static-property singleton of one class specialization (LRM 8.9),
91    /// named by an id that is only meaningful within one compile. The
92    /// `randomize()` call site passes the handle of each singleton the
93    /// program needs as an `(id, handle)` pair ([`randomize`]'s `statics`).
94    Static(u32),
95    /// One named heap object: a contained `rand` object of a joint solve
96    /// (Task C17, LRM 18.5.8). Only a transient program that [`super::joint`]
97    /// merged for one call holds this; a registered program never does.
98    Object(u64),
99}
100
101/// A `rand` class-handle member of a [`Program`]: the object it designates is
102/// randomized together with its container when the handle is active and not
103/// null (Task C17, LRM 18.5.8).
104#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105pub struct ProgHandle {
106    /// Where the handle (one word) lives, as an offset into [`Self::home`].
107    pub slot: FieldSlot,
108    /// The object [`Self::slot`] is an offset into.
109    pub home: Home,
110    /// As [`ProgVar::prop_index`]: the handle's `rand_mode` bit, so
111    /// `h.rand_mode(0)` takes the object out of the joint solve (LRM 18.8).
112    pub prop_index: Option<usize>,
113    /// The handle's LSB within the property's value bits: 0 for a `rand` handle
114    /// property, `64 * i` for element `i` of a `rand` fixed array of handles
115    /// (LRM 18.4, 18.5.8: every element's object is randomized with its container).
116    pub bit: u32,
117}
118
119/// A random variable of a [`Program`].
120#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
121pub struct ProgVar {
122    /// Declared type (the solver's 2-state view of it).
123    pub ty: Ty,
124    /// Where its (4-state packed) value lives, as an offset into [`Self::home`].
125    pub slot: FieldSlot,
126    /// The object [`Self::slot`] is an offset into.
127    pub home: Home,
128    /// Index of this property in the template's [`objects::rand_props`]
129    /// order, which is also the index into the object's `rand_mode` vector.
130    /// `None` for a `static` property: it belongs to the class, not to an
131    /// object, so it has no per-object `rand_mode` bit and is always active.
132    pub prop_index: Option<usize>,
133    /// Index into [`Program::conjuncts`] of this variable's implicit enum
134    /// domain constraint, if it has one (dropped while the variable is
135    /// inactive).
136    pub domain_conjunct: Option<usize>,
137    /// Whether the variable is `randc` (LRM 18.4.2): drawn first, from its own
138    /// cyclic permutation, instead of from the joint solution space.
139    pub randc: bool,
140    /// For a member of an unpacked struct or an element of a fixed-size unpacked
141    /// array held in a property (XC-059, LRM 18.4): [`Self::slot`] is then the
142    /// whole property and the variable is this window of it. `None` for a
143    /// variable that is a whole property.
144    pub window: Option<Window>,
145}
146
147impl ProgVar {
148    /// The key of this variable's `randc` cycle in its object
149    /// ([`objects::randc_state`]): its property's word offset, with the window's
150    /// bit position above bit 32 so two leaves of one property have two cycles.
151    fn randc_key(&self) -> usize {
152        randc_key(self.slot, self.window)
153    }
154}
155
156/// The `randc` cycle key of the leaf `window` of property `slot` (see
157/// [`ProgVar::randc_key`]).
158fn randc_key(slot: FieldSlot, window: Option<Window>) -> usize {
159    match window {
160        None => slot.offset,
161        Some(w) => slot.offset | ((w.bit as usize + 1) << 32),
162    }
163}
164
165/// A state variable of a [`Program`] (a non-rand field read by a constraint).
166#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
167pub struct ProgState {
168    /// Declared type.
169    pub ty: Ty,
170    /// Where its (4-state packed) value lives, as an offset into [`Self::home`].
171    pub slot: FieldSlot,
172    /// As [`ProgVar::window`]: the value is this window of the property at the
173    /// end of [`Self::via`] (or of [`Self::slot`] when there is none).
174    pub window: Option<Window>,
175    /// The object [`Self::slot`] is an offset into.
176    pub home: Home,
177    /// For a value read through object handles (`a.x`, `next.next.n`; LRM
178    /// 18.5.12): [`Self::slot`] is then the *root handle* property and these
179    /// are the member slots followed from it, one per `.`, the last holding the
180    /// value of type [`Self::ty`]. Empty for a plain property.
181    pub via: Vec<FieldSlot>,
182    /// The bit of [`Self::slot`] the root handle's word starts at: 0 for a
183    /// handle property, `64 * i` for element `i` of a fixed array of handles
184    /// (`es[i].v`, LRM 18.5.8).
185    pub root_bit: u32,
186    /// The value is the dynamic class template id of the object the handle
187    /// [`Self::slot`] / [`Self::via`] reach names (a virtual call's selector).
188    #[serde(default)]
189    pub class_of: bool,
190}
191
192/// One class template's solver input, resolved to object-field locations.
193#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
194pub struct Program {
195    /// Random variables, indexed by the IR's `VarId`.
196    pub vars: Vec<ProgVar>,
197    /// State variables, indexed by the IR's `StateId`.
198    pub states: Vec<ProgState>,
199    /// One-bit expressions that must all hold.
200    pub conjuncts: Vec<Expr>,
201    /// `solve ... before` edges `(first, later)` over [`Self::vars`] (LRM
202    /// 18.5.9). Acyclic (the builder checks). An edge whose variable is
203    /// inactive (`rand_mode(0)`) is ignored for that call: it is a constant
204    /// then, and there is nothing to order.
205    pub before: Vec<(u32, u32)>,
206    /// The `dist` constraints (LRM 18.5.3). Dist `j`'s hidden selector variable
207    /// appears in [`Self::conjuncts`] as `Var(SELECTOR_BASE + j)`; it is not a
208    /// field and is numbered after the active variables for each call.
209    pub dists: Vec<Dist>,
210    /// The `real`/`shortreal` random variables, indexed by the real
211    /// problem's variable ids (their own numbering, separate from
212    /// [`Self::vars`]).
213    pub real_vars: Vec<ProgRealVar>,
214    /// The constraints over [`Self::real_vars`] (Task C9), solved after the
215    /// integral variables with the same PRNG stream.
216    pub real: RealProblem,
217    /// The dynamic arrays and queues the constraints reach (Task C14), indexed
218    /// by the IR's [`super::ir::ArrayId`].
219    pub arrays: Vec<ProgArray>,
220    /// The functions the constraints call (Task C13), indexed by the IR's
221    /// [`super::ir::FuncId`].
222    pub funcs: Vec<Func>,
223    /// The soft constraints (Task C15, LRM 18.5.13): soft `k`'s discard flag
224    /// appears in [`Self::conjuncts`] as `Var(SOFT_BASE + k)` (see
225    /// [`super::priority`]). Empty for a class with none.
226    pub softs: Vec<Soft>,
227    /// The `rand` class-handle members (Task C17): the objects a joint solve
228    /// reaches from an object of this class ([`super::joint`]).
229    pub rand_handles: Vec<ProgHandle>,
230    /// The named constraint blocks, by ordinal (Task C20): what
231    /// `constraint_mode(0)` takes out of the solve. Empty for a program that
232    /// did not come from a class (a fragment, a scope randomization).
233    pub blocks: Vec<ProgBlock>,
234    /// How many `disable soft`s the program holds: the table
235    /// [`Soft::disabled_by`] indexes and [`BlockItems::disables`] ranges over.
236    pub n_disables: usize,
237    /// The targets of an inline fragment's `disable soft`s (empty for a class
238    /// program), in the order of the last [`Self::n_disables`] entries: the
239    /// overlay discards the base softs they reach (LRM 18.5.13.2, 18.7).
240    pub disable_keys: Vec<super::priority::SoftKey>,
241}
242
243/// A named constraint block of a [`Program`] (LRM 18.9).
244#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
245pub struct ProgBlock {
246    /// The object whose `constraint_mode()` state holds the block's mode: the
247    /// instance for an ordinary block, the class's static-property object for a
248    /// `static` one (its mode is shared by every instance, LRM 18.9).
249    pub home: Home,
250    /// The block's ordinal among the named blocks of the class, base class
251    /// first (a derived override keeps the base block's ordinal): the key into
252    /// [`objects::constraint_mode_of`].
253    pub ordinal: u32,
254    /// The block's conjuncts, a range of [`Program::conjuncts`].
255    pub conjuncts: std::ops::Range<usize>,
256    /// What the block holds besides conjuncts (a `dist`, a `solve ... before`,
257    /// a `real` constraint, a `soft` constraint, a `disable soft`): a call with
258    /// the block switched off leaves those out too ([`reduce`]).
259    pub items: BlockItems,
260}
261
262/// A dynamic array or queue property of a [`Program`] (LRM 18.4, 18.5.7).
263#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
264pub struct ProgArray {
265    /// Element type (the solver's 2-state view of it).
266    pub elem_ty: Ty,
267    /// 64-bit words per stored element (`ceil(2 * width / 64)`, 4-state packed:
268    /// the same layout the collection store and codegen use).
269    pub elem_words: usize,
270    /// Where the collection handle lives, as an offset into [`Self::home`].
271    pub slot: FieldSlot,
272    /// The object [`Self::slot`] is an offset into.
273    pub home: Home,
274    /// Whether the property is `rand`. A non-`rand` array a constraint reads
275    /// is state.
276    pub rand: bool,
277    /// As [`ProgVar::prop_index`]; one `rand_mode` bit covers the whole array.
278    pub prop_index: Option<usize>,
279    /// The enum values every element must be one of, for an array of an enum
280    /// type (LRM 6.19.3: a `rand` enum is limited to its named values).
281    pub elem_domain: Option<Vec<BigUint>>,
282    /// An associative array: its keys (hence its size) are never randomized,
283    /// only the values at the keys it already has (LRM 18.4), so the solved
284    /// values are written back by key instead of resizing a sequence.
285    pub assoc: bool,
286    /// An array of dynamic arrays or queues (`rand int a[][]`, LRM 18.4): the
287    /// property's elements are row handles, and [`Self::elem_ty`] /
288    /// [`Self::elem_words`] describe the leaf elements of the rows.
289    #[serde(default)]
290    pub nested: bool,
291    /// For the row template of the array of arrays with this id
292    /// ([`super::ir::ArrayNode::RowScope`]): the array exists only in the
293    /// constraints, so it has no property ([`Self::slot`] and [`Self::home`] are
294    /// placeholders).
295    #[serde(default)]
296    pub template_of: Option<u32>,
297    /// For an array of FIXED-size rows (`rand int b[][4]`, LRM 7.4.5): the rows'
298    /// element count `N`. Every row is `N` elements stored inline in its outer
299    /// element (one flat 4-state value of `N` leaf elements, lowest index
300    /// first) instead of a row collection handle; zero for dynamic rows.
301    #[serde(default)]
302    pub inner_fixed: u32,
303    /// How many levels of rows lie below the array (0 unless [`Self::nested`]):
304    /// `rand int a[][][]` is 2. Every level but the last holds row handles
305    /// (collections of handles); the last holds the leaf elements, or with
306    /// [`Self::inner_fixed`] the inline fixed-size rows.
307    #[serde(default)]
308    pub row_depth: u32,
309    /// When the collection handle is not the whole property but one leaf of an
310    /// aggregate (a row of `rand int a[3][]`, a `rand` queue member of an
311    /// unpacked struct, LRM 18.4): where it sits in the property's value.
312    #[serde(default)]
313    pub window: Option<Window>,
314}
315
316/// A `real` or `shortreal` random variable of a [`Program`].
317#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
318pub struct ProgRealVar {
319    /// `real` or `shortreal`.
320    pub kind: FloatKind,
321    /// Where its (4-state packed) value lives, as an offset into [`Self::home`]:
322    /// the IEEE bit pattern in the low 64 (32) bits.
323    pub slot: FieldSlot,
324    /// The object [`Self::slot`] is an offset into.
325    pub home: Home,
326    /// As [`ProgVar::prop_index`].
327    pub prop_index: Option<usize>,
328    /// As [`ProgVar::window`].
329    pub window: Option<Window>,
330}
331
332/// The result of one [`randomize`] call; the discriminant is the ABI value
333/// `ObjOp::Randomize` returns.
334#[derive(Clone, Copy, Debug, PartialEq, Eq)]
335#[repr(u64)]
336pub enum Outcome {
337    /// The constraints are unsatisfiable: `randomize()` returns 0, nothing
338    /// was changed (LRM 18.6.3).
339    Unsatisfiable = 0,
340    /// A solution was written; `randomize()` returns 1.
341    Solved = 1,
342    /// The handle is null or stale.
343    DeadHandle = 2,
344    /// The object's class has no usable program (its constraints could not be
345    /// built, or none was registered).
346    NoProgram = 3,
347    /// A constraint reads a variable holding an X/Z bit.
348    StateUnknown = 4,
349    /// The constraint BDD exceeded its node budget (the SAT fallback is a
350    /// later task).
351    TooLarge = 5,
352    /// Any other solver or program failure (a malformed program).
353    Failed = 6,
354    /// The program reads or writes a `static` property of a class whose
355    /// static-property object the call site did not pass (a `randomize()`
356    /// through a base-class handle on an object of a derived class that
357    /// declares its own static `rand`/`randc` variable or constraint state).
358    StaticUnavailable = 7,
359    /// A `dist` weight evaluated to a negative number, or a weight or bound
360    /// divided by zero (IEEE 1800-2023 18.5.3).
361    BadDistWeight = 8,
362    /// A constraint guard evaluated to `ERROR` (IEEE 1800-2023 18.5.12): a
363    /// null handle or X/Z value read where nothing short-circuits it.
364    GuardError = 9,
365    /// A constraint outside any guard reads a member through a null or deleted
366    /// object handle (IEEE 1800-2023 18.5.12: "randomize() fails").
367    NullHandle = 10,
368    /// A real constraint evaluated to NaN or divided by zero (Task C9).
369    RealDomain = 11,
370    /// The size constraints of a dynamic array or queue can only be met by a
371    /// size beyond the supported maximum (65536 elements).
372    ArraySizeTooLarge = 12,
373    /// A constraint reads an array element outside `0..size` (IEEE 1800-2023
374    /// 18.5.7.1) where no guard excludes it.
375    ArrayIndexOutOfRange = 13,
376    /// Every array size satisfying the size constraints alone made the full
377    /// problem unsatisfiable, and the bounded search for another one gave up.
378    ArraySizeSearch = 14,
379    /// A function called from a constraint failed (division by zero, a runaway
380    /// loop, recursion past the supported depth; IEEE 1800-2023 18.5.11).
381    FunctionFailed = 15,
382    /// The implicit order the random arguments of function calls impose is
383    /// circular (IEEE 1800-2023 18.5.11).
384    ImplicitOrderCycle = 16,
385    /// A constraint compares the results of function calls over several random
386    /// variables, and no draw tried satisfied it (the bounded search gave up).
387    FunctionSearch = 17,
388    /// The objects of a joint solve (a `rand` object member, IEEE 1800-2023
389    /// 18.5.8) use something the joint solver does not combine yet: `real`
390    /// random variables, or a `static` random variable two members of the tree share.
391    GlobalUnsupported = 18,
392    /// The constraint problem exceeded the BDD node budget and the SAT fallback
393    /// ([`super::sat`]) did not finish within its time limit.
394    SatTimeout = 19,
395    /// The constraint problem exceeded the BDD node budget and needs something
396    /// the SAT fallback cannot sample faithfully (a `dist` weighted by a count
397    /// of more than 512 models, a `dist` over a variable wider than 32 bits or
398    /// with more than 1024 feasible values per range, a `randc` variable wider
399    /// than 10 bits).
400    SatUnsupported = 20,
401}
402
403impl Outcome {
404    /// The ABI value.
405    pub fn code(self) -> u64 {
406        self as u64
407    }
408
409    /// The outcome with ABI value `code`, if any.
410    pub fn from_code(code: u64) -> Option<Outcome> {
411        [
412            Outcome::Unsatisfiable,
413            Outcome::Solved,
414            Outcome::DeadHandle,
415            Outcome::NoProgram,
416            Outcome::StateUnknown,
417            Outcome::TooLarge,
418            Outcome::Failed,
419            Outcome::StaticUnavailable,
420            Outcome::BadDistWeight,
421            Outcome::GuardError,
422            Outcome::NullHandle,
423            Outcome::RealDomain,
424            Outcome::ArraySizeTooLarge,
425            Outcome::ArrayIndexOutOfRange,
426            Outcome::ArraySizeSearch,
427            Outcome::FunctionFailed,
428            Outcome::ImplicitOrderCycle,
429            Outcome::FunctionSearch,
430            Outcome::GlobalUnsupported,
431            Outcome::SatTimeout,
432            Outcome::SatUnsupported,
433        ]
434        .into_iter()
435        .find(|o| o.code() == code)
436    }
437
438    /// The diagnostic a compiled call site prints for this outcome, if it is
439    /// an error rather than an ordinary result (an unsatisfiable problem is
440    /// not an error: `randomize()` just returns 0, LRM 18.6.3).
441    pub fn error_message(self) -> Option<&'static str> {
442        match self {
443            Outcome::Unsatisfiable | Outcome::Solved => None,
444            Outcome::DeadHandle => Some("randomize() on a null or deleted object"),
445            Outcome::NoProgram => Some(
446                "randomize(): the constraints of this object's class could not be compiled \
447                 (see the class's constraint blocks)",
448            ),
449            Outcome::StateUnknown => Some(
450                "randomize(): a constraint reads a variable with an X or Z bit; a 4-state \
451                 value in a constraint is an error (IEEE 1800-2023 18.3)",
452            ),
453            Outcome::TooLarge => Some(
454                "randomize(): the constraint problem is too large for both the BDD solver \
455                 (node budget exceeded) and its SAT fallback (clause limit exceeded)",
456            ),
457            Outcome::SatTimeout => Some(
458                "randomize(): the constraint problem is too large for the BDD solver and \
459                 the SAT fallback did not find a solution within its time limit",
460            ),
461            Outcome::SatUnsupported => Some(
462                "randomize(): the constraint problem is too large for the BDD solver and \
463                 uses a `dist` over an expression with more than 512 feasible assignments, a `dist` \
464                 over a variable wider than 32 bits or with more than 1024 feasible values per \
465                 range, or a `randc` variable wider than 10 bits, which the SAT fallback does \
466                 not support",
467            ),
468            Outcome::Failed => Some("randomize(): internal solver failure on a malformed program"),
469            Outcome::StaticUnavailable => Some(
470                "randomize(): the object's class declares a static property used by its \
471                 constraints or `rand`/`randc` variables that this call site cannot reach \
472                 (it is declared in a class derived from the handle's static type); call \
473                 randomize() through a handle of that class",
474            ),
475            Outcome::BadDistWeight => Some(
476                "randomize(): a `dist` weight is negative, or a weight or range bound \
477                 divides by zero (IEEE 1800-2023 18.5.3)",
478            ),
479            Outcome::GuardError => Some(
480                "randomize(): the guard of an implication or `if` constraint evaluated to \
481                 ERROR (a null object handle, or an X/Z value, read where no operand \
482                 decides it; IEEE 1800-2023 18.5.12)",
483            ),
484            Outcome::NullHandle => Some(
485                "randomize(): a constraint reads a member through a null or deleted object \
486                 handle (IEEE 1800-2023 18.5.12)",
487            ),
488            Outcome::RealDomain => Some(
489                "randomize(): a constraint on a real variable evaluates to NaN or divides by \
490                 zero, so it has no well-defined set of solutions",
491            ),
492            Outcome::ArraySizeTooLarge => Some(
493                "randomize(): the constraints on the size of a dynamic array or queue can \
494                 only be met by a size above 65536 elements, which is not supported",
495            ),
496            Outcome::ArrayIndexOutOfRange => Some(
497                "randomize(): a constraint reads an array element outside the array's \
498                 bounds (guard it with an `if` on the size, IEEE 1800-2023 18.5.7.1)",
499            ),
500            Outcome::ArraySizeSearch => Some(
501                "randomize(): no array size tried satisfied both the size constraints and \
502                 the element constraints (the bounded search over sizes gave up)",
503            ),
504            Outcome::FunctionFailed => Some(
505                "randomize(): a function called from a constraint failed (division by zero, \
506                 a runaway loop or recursion that is too deep)",
507            ),
508            Outcome::ImplicitOrderCycle => Some(
509                "randomize(): the random arguments of function calls in the constraints \
510                 impose a circular solve order (IEEE 1800-2023 18.5.11)",
511            ),
512            Outcome::FunctionSearch => Some(
513                "randomize(): no draw satisfied a constraint that compares the results of \
514                 function calls over several random variables (the bounded search gave up)",
515            ),
516            Outcome::GlobalUnsupported => Some(
517                "randomize(): the object tree being randomized together (a `rand` object \
518                 member, IEEE 1800-2023 18.5.8) has `real` random variables, or a `static` \
519                 random variable that two objects of the tree share, which the joint solver \
520                 does not combine yet",
521            ),
522        }
523    }
524}
525
526type ProgramTable = HashMap<ClassTemplateId, Arc<Result<Program, String>>>;
527
528fn table() -> &'static Mutex<ProgramTable> {
529    static T: OnceLock<Mutex<ProgramTable>> = OnceLock::new();
530    T.get_or_init(|| Mutex::new(HashMap::new()))
531}
532
533/// Registers (replacing any earlier registration) `template`'s program, or
534/// the reason it could not be built. A failure here must not fail the compile
535/// by itself: a class whose constraints this build cannot yet handle is fine
536/// as long as nothing calls `randomize()` on it; the compiler records the
537/// failure and reports it at a call site that could reach the class.
538pub fn register_program(template: ClassTemplateId, program: Result<Program, String>) {
539    table()
540        .lock()
541        .unwrap_or_else(|e| e.into_inner())
542        .insert(template, Arc::new(program));
543}
544
545/// `template`'s registered program, or the reason there is none.
546pub fn program(template: ClassTemplateId) -> Option<Arc<Result<Program, String>>> {
547    table()
548        .lock()
549        .unwrap_or_else(|e| e.into_inner())
550        .get(&template)
551        .cloned()
552}
553
554/// What a design image carries of the solver: the program of each class
555/// template and every call site's [`Spec`], by the ids baked into the machine
556/// code (see [`snapshot`], [`preload`]).
557#[derive(serde::Serialize, serde::Deserialize)]
558struct Snapshot {
559    /// `(template id, program or the reason there is none)`, ascending by id.
560    programs: Vec<(u32, Result<Program, String>)>,
561    /// `(spec id, spec)`, ascending by id.
562    specs: Vec<(u32, Spec)>,
563}
564
565/// The solver state `obj.randomize()` and `std::randomize()` call sites of the
566/// design just compiled depend on, as bytes: the program registered for each of
567/// `templates` and every spec registered in this process. Empty when there is
568/// neither (a design that never randomizes carries nothing). Deterministic:
569/// ascending ids, and `postcard` has no hash-order or padding.
570pub fn snapshot(templates: &[ClassTemplateId]) -> Vec<u8> {
571    let mut ids: Vec<u32> = templates.iter().map(|t| t.0).collect();
572    ids.sort_unstable();
573    ids.dedup();
574    let programs: Vec<(u32, Result<Program, String>)> = ids
575        .into_iter()
576        .filter_map(|id| program(ClassTemplateId(id)).map(|p| (id, Result::clone(&p))))
577        .collect();
578    let specs: Vec<(u32, Spec)> = all_specs()
579        .into_iter()
580        .map(|(id, s)| (id, Spec::clone(&s)))
581        .collect();
582    if programs.is_empty() && specs.is_empty() {
583        return Vec::new();
584    }
585    postcard::to_allocvec(&Snapshot { programs, specs })
586        .expect("a constraint program is plain data and always serializes")
587}
588
589/// Registers what [`snapshot`] captured, under the same ids. Empty bytes
590/// register nothing.
591pub fn preload(bytes: &[u8]) -> Result<(), String> {
592    if bytes.is_empty() {
593        return Ok(());
594    }
595    let snap: Snapshot =
596        postcard::from_bytes(bytes).map_err(|e| format!("randomize section: {e}"))?;
597    for (id, p) in snap.programs {
598        register_program(ClassTemplateId(id), p);
599    }
600    for (id, s) in snap.specs {
601        register_spec_at(id, s);
602    }
603    Ok(())
604}
605
606/// The 4-state packed field `words` of a `width`-bit value as `(val, unk)`.
607pub(super) fn unpack(words: &[u64], width: u32) -> (BigUint, BigUint) {
608    let mut n = BigUint::zero();
609    for (i, w) in words.iter().enumerate() {
610        n |= BigUint::from(*w) << (64 * i);
611    }
612    let m = mask(width);
613    (&n & &m, (n >> width) & m)
614}
615
616/// A 2-state `val` as `words` packed words (all unknown bits zero), or `None`
617/// if it does not fit.
618fn pack(val: &BigUint, words: usize) -> Option<Vec<u64>> {
619    let mut d = val.to_u64_digits();
620    if d.len() > words {
621        return None;
622    }
623    d.resize(words, 0);
624    Some(d)
625}
626
627/// The current 2-state value of the field `slot` (or its leaf `window`) of
628/// width `ty`; `Ok(None)` if it has an X/Z bit, `Err` if `h` is dead.
629fn read(
630    h: u64,
631    slot: FieldSlot,
632    window: Option<Window>,
633    ty: Ty,
634) -> Result<Option<BigUint>, Outcome> {
635    let (val, unk) = read_unknown(h, slot, window, ty.width)?;
636    Ok(if unk.is_zero() { Some(val) } else { None })
637}
638
639/// The `(value, unknown bits)` of the field `slot` (or its leaf `window`) as a
640/// `width`-bit value; `Err` if `h` is dead.
641fn read_unknown(
642    h: u64,
643    slot: FieldSlot,
644    window: Option<Window>,
645    width: u32,
646) -> Result<(BigUint, BigUint), Outcome> {
647    match window {
648        None => {
649            let mut buf = vec![0u64; slot.words];
650            if !objects::field_get(h, slot, &mut buf) {
651                return Err(Outcome::DeadHandle);
652            }
653            Ok(unpack(&buf, width))
654        }
655        Some(w) => {
656            let n = (w.width as usize).div_ceil(64);
657            let (mut val, mut unk) = (vec![0u64; n], vec![0u64; n]);
658            if !objects::field_get_window(h, slot, w, &mut val, &mut unk) {
659                return Err(Outcome::DeadHandle);
660            }
661            let m = mask(width);
662            let to_big = |ws: &[u64]| {
663                ws.iter().enumerate().fold(BigUint::zero(), |a, (i, x)| {
664                    a | (BigUint::from(*x) << (64 * i))
665                })
666            };
667            Ok((to_big(&val) & &m, to_big(&unk) & &m))
668        }
669    }
670}
671
672/// What reading a state variable found.
673enum Reading {
674    /// The value, or `None` if it has an X/Z bit.
675    Value(Option<BigUint>),
676    /// An object handle on the way to it was null (or its object deleted).
677    NullHandle,
678}
679
680/// Reads state `s`, owned by object `owner`, following its handle chain
681/// ([`ProgState::via`]). A null handle on the chain is a [`Reading::NullHandle`],
682/// an X/Z handle an unreadable value, and a dead `owner` itself an error.
683fn read_state(owner: u64, s: &ProgState) -> Result<Reading, Outcome> {
684    let (mut obj, mut slot) = (owner, s.slot);
685    for (i, hop) in s.via.iter().enumerate() {
686        let mut buf = vec![0u64; slot.words];
687        let bit = if i == 0 { s.root_bit } else { 0 };
688        if !objects::field_get(obj, slot, &mut buf) {
689            return if i == 0 {
690                Err(Outcome::DeadHandle)
691            } else {
692                Ok(Reading::NullHandle)
693            };
694        }
695        // A handle is one word of the value half; element `bit / 64` of a fixed
696        // array of handles has its unknown bits at the same offset of the upper
697        // half (as `joint::read_handle`).
698        let w = (bit / 64) as usize;
699        let half = slot.words / 2;
700        let (Some(&v), Some(&u)) = (buf.get(w), buf.get(half + w)) else {
701            return Err(Outcome::Failed);
702        };
703        let (val, unk) = (BigUint::from(v), BigUint::from(u));
704        if !unk.is_zero() {
705            return Ok(Reading::Value(None));
706        }
707        match val.to_u64() {
708            Some(0) => return Ok(Reading::NullHandle),
709            Some(next) => obj = next,
710            None => return Err(Outcome::Failed),
711        }
712        slot = *hop;
713    }
714    if s.class_of {
715        let (val, unk) = match read_unknown(obj, slot, None, 64) {
716            Ok(r) => r,
717            Err(_) if s.via.is_empty() => return Err(Outcome::DeadHandle),
718            Err(_) => return Ok(Reading::NullHandle),
719        };
720        if !unk.is_zero() {
721            return Ok(Reading::Value(None));
722        }
723        return match val.to_u64() {
724            Some(0) => Ok(Reading::NullHandle),
725            Some(h) => Ok(match objects::dynamic_template(h) {
726                Some(t) => Reading::Value(Some(BigUint::from(t.0))),
727                None => Reading::NullHandle,
728            }),
729            None => Err(Outcome::Failed),
730        };
731    }
732    match read_unknown(obj, slot, s.window, s.ty.width) {
733        Ok((val, unk)) => Ok(Reading::Value(if unk.is_zero() { Some(val) } else { None })),
734        Err(_) if s.via.is_empty() => Err(Outcome::DeadHandle),
735        Err(_) => Ok(Reading::NullHandle),
736    }
737}
738
739/// The heap object `home` names for a call on `h`, given the static-property
740/// objects the call site passed.
741pub(super) fn owner(home: Home, h: u64, statics: &[(u32, u64)]) -> Result<u64, Outcome> {
742    match home {
743        Home::Instance => Ok(h),
744        Home::Object(o) => Ok(o),
745        Home::Static(id) => statics
746            .iter()
747            .find(|(i, s)| *i == id && *s != 0)
748            .map(|&(_, s)| s)
749            .ok_or(Outcome::StaticUnavailable),
750    }
751}
752
753/// Whether the unreadable state `id` is so because of a null handle.
754fn unreadable_outcome(id: u32, null_state: &[bool]) -> Outcome {
755    if null_state.get(id as usize).copied().unwrap_or(false) {
756        Outcome::NullHandle
757    } else {
758        Outcome::StateUnknown
759    }
760}
761
762/// The solver's failure `e` as the call's outcome.
763fn outcome_of(e: SolveError, null_state: &[bool]) -> Outcome {
764    match e {
765        SolveError::Unsatisfiable => Outcome::Unsatisfiable,
766        SolveError::Build(BddBuildError::StateUnknown(id)) => unreadable_outcome(id, null_state),
767        SolveError::Build(BddBuildError::TooLarge { .. } | BddBuildError::CnfTooLarge { .. }) => {
768            Outcome::TooLarge
769        }
770        SolveError::SatTimeout => Outcome::SatTimeout,
771        SolveError::SatUnsupported { .. } => Outcome::SatUnsupported,
772        SolveError::Build(_)
773        | SolveError::BadOrder { .. }
774        | SolveError::BadPlan { .. }
775        | SolveError::SatModelRejected
776        | SolveError::SatSolverFailure { .. } => Outcome::Failed,
777    }
778}
779
780/// A `dist` weight failure `e` as the call's outcome.
781fn outcome_of_dist(e: DistError, null_state: &[bool]) -> Outcome {
782    match e {
783        DistError::Eval(ConstraintEvalError::StateUnknown(id)) => {
784            unreadable_outcome(id, null_state)
785        }
786        DistError::Eval(ConstraintEvalError::DivideByZero) | DistError::NegativeWeight => {
787            Outcome::BadDistWeight
788        }
789        DistError::Eval(
790            ConstraintEvalError::UnboundVar(_)
791            | ConstraintEvalError::UnboundState(_)
792            | ConstraintEvalError::Unexpanded,
793        ) => Outcome::Failed,
794    }
795}
796
797/// A real solver failure `e` as the call's outcome.
798fn outcome_of_real(e: RealError, null_state: &[bool]) -> Outcome {
799    match e {
800        RealError::Unsatisfiable => Outcome::Unsatisfiable,
801        RealError::GuardError => Outcome::GuardError,
802        RealError::StateUnreadable(id) => unreadable_outcome(id, null_state),
803        RealError::NonFinite(_) => Outcome::RealDomain,
804        RealError::BadWeight => Outcome::BadDistWeight,
805        RealError::Shape(_) => Outcome::Failed,
806    }
807}
808
809/// One solved value to store: a whole property, or one leaf `window` of it.
810struct Write {
811    owner: u64,
812    slot: FieldSlot,
813    window: Option<Window>,
814    /// The value, packed for the property (`slot.words`) or the leaf
815    /// (`ceil(width/64)`).
816    words: Vec<u64>,
817}
818
819impl Write {
820    /// The write of `val` to the field `slot` (or its leaf `window`) of `owner`;
821    /// `None` when `val` does not fit.
822    fn new(owner: u64, slot: FieldSlot, window: Option<Window>, val: &BigUint) -> Option<Write> {
823        let n = match window {
824            None => slot.words,
825            Some(w) => (w.width as usize).div_ceil(64),
826        };
827        Some(Write {
828            owner,
829            slot,
830            window,
831            words: pack(val, n)?,
832        })
833    }
834
835    /// Stores the value; `false` when the object is dead.
836    fn apply(&self) -> bool {
837        match self.window {
838            None => objects::field_set(self.owner, self.slot, &self.words),
839            Some(w) => objects::field_set_window(self.owner, self.slot, w, &self.words),
840        }
841    }
842}
843
844/// Solves the real variables of `program` (Task C9) and returns the field
845/// writes for the active ones; nothing is written here.
846/// An inactive (`rand_mode(0)`) real variable is a constant holding its field's
847/// current value, and an X/Z bit in it is a [`Outcome::StateUnknown`].
848#[allow(clippy::too_many_arguments)]
849fn run_real(
850    program: &Program,
851    h: u64,
852    rand_mode: &[bool],
853    statics: &[(u32, u64)],
854    states: &[Option<BigUint>],
855    null_state: &[bool],
856    ints: Option<(&[Option<u32>], &[BigUint])>,
857    seed: &mut u64,
858) -> Result<Vec<Write>, Outcome> {
859    if program.real_vars.is_empty() {
860        return Ok(Vec::new());
861    }
862    if program.real_vars.len() != program.real.kinds.len() {
863        return Err(Outcome::Failed);
864    }
865    let mut owners = Vec::with_capacity(program.real_vars.len());
866    let mut known = Vec::with_capacity(program.real_vars.len());
867    for v in &program.real_vars {
868        let o = owner(v.home, h, statics)?;
869        let active = match v.prop_index {
870            Some(i) => rand_mode.get(i).copied().ok_or(Outcome::Failed)?,
871            None => true,
872        };
873        known.push(if active {
874            None
875        } else {
876            match read(o, v.slot, v.window, Ty::unsigned(v.kind.width()))? {
877                Some(bits) => Some(v.kind.from_bits(&bits)),
878                None => return Err(Outcome::StateUnknown),
879            }
880        });
881        owners.push(o);
882    }
883    // A real condition that reads an integral random variable (LRM 18.5.13)
884    // reads the value the integral solve drew for it, as a state: an active
885    // variable's drawn value, an inactive one's state (module docs of
886    // [`super::build::real`]).
887    let (lowered, ext);
888    let (problem, states) = match ints {
889        Some((var_new, values)) if program.real.reads_int_vars() => {
890            let mut e = states.to_vec();
891            let mut ix = Vec::with_capacity(var_new.len());
892            let mut inactive = program.states.len();
893            for d in var_new {
894                match d {
895                    Some(d) => {
896                        ix.push(e.len() as u32);
897                        e.push(Some(
898                            values.get(*d as usize).ok_or(Outcome::Failed)?.clone(),
899                        ));
900                    }
901                    None => {
902                        ix.push(inactive as u32);
903                        inactive += 1;
904                    }
905                }
906            }
907            lowered = program
908                .real
909                .with_int_vars(|v, t| Expr::State(ix[v as usize], t));
910            ext = e;
911            (&lowered, &ext[..])
912        }
913        _ => (&program.real, states),
914    };
915    let solved = real_domain::solve(problem, &known, states, seed)
916        .map_err(|e| outcome_of_real(e, null_state))?;
917    let mut writes = Vec::new();
918    for ((v, o), x) in program.real_vars.iter().zip(owners).zip(solved) {
919        let Some(x) = x else { continue };
920        match Write::new(o, v.slot, v.window, &BigUint::from(v.kind.to_bits(x))) {
921            Some(w) => writes.push(w),
922            None => return Err(Outcome::Failed),
923        }
924    }
925    Ok(writes)
926}
927
928/// Everything about one `randomize()` call that does not depend on the array
929/// sizes it will choose.
930struct Call<'a> {
931    program: &'a Program,
932    h: u64,
933    rand_mode: &'a [bool],
934    statics: &'a [(u32, u64)],
935    var_owner: Vec<u64>,
936    /// Dense number of each program variable that is active this call.
937    var_new: Vec<Option<u32>>,
938    /// Types of the active variables, in dense order.
939    var_tys: Vec<Ty>,
940    /// Program index of each active variable, in dense order.
941    active_ids: Vec<usize>,
942    /// Each program array as this call sees it.
943    arrays: Vec<array::ArrayState>,
944    array_owner: Vec<u64>,
945    /// The program's states, then the inactive variables read as states.
946    states: Vec<Option<BigUint>>,
947    /// Which unreadable states are so because of a null handle (not X/Z).
948    null_state: Vec<bool>,
949    /// Set when the last attempt's integral part was solved and its real part
950    /// was not (see [`solve_expanded`]).
951    real_unsat: std::cell::Cell<bool>,
952}
953
954/// What one successful attempt drew.
955struct Solution {
956    /// A value per active variable, then per element variable.
957    values: Vec<BigUint>,
958    randc_keys: Vec<(u64, usize)>,
959    randc_work: Vec<RandcState>,
960    real_writes: Vec<Write>,
961}
962
963/// Reads program array `a` (owned by `owner`) as the call sees it.
964fn read_array(a: &ProgArray, owner: u64, active: bool) -> Result<array::ArrayState, Outcome> {
965    if let Some(outer) = a.template_of {
966        // A row template exists only in the constraints.
967        return Ok(array::ArrayState {
968            elem_ty: a.elem_ty,
969            active: false,
970            cur: Vec::new(),
971            domain: None,
972            rows: None,
973            template_of: Some(outer),
974        });
975    }
976    let (handle, unk) = read_unknown(owner, a.slot, a.window, 64)?;
977    if !unk.is_zero() {
978        return Err(Outcome::Failed);
979    }
980    let handle = handle.to_u64().ok_or(Outcome::Failed)?;
981    let leaves = |h: u64| -> Vec<Option<BigUint>> {
982        collections::elements(h)
983            .iter()
984            .map(|words| {
985                let (val, unk) = unpack(words, a.elem_ty.width);
986                if unk.is_zero() { Some(val) } else { None }
987            })
988            .collect()
989    };
990    if a.nested {
991        return read_rows(a, handle, a.row_depth.max(1), active, &leaves);
992    }
993    Ok(array::ArrayState::flat(a.elem_ty, active, leaves(handle))
994        .with_domain(a.elem_domain.clone()))
995}
996
997/// Reads the rows of the array of arrays `a` held in `handle`, `levels` levels
998/// of rows deep (LRM 1800-2023 7.5, 7.10; 18.5.8): each element of the
999/// collection is the handle of a row, which is a leaf collection when `levels`
1000/// is 1 and a collection of rows otherwise.
1001fn read_rows(
1002    a: &ProgArray,
1003    handle: u64,
1004    levels: u32,
1005    active: bool,
1006    leaves: &dyn Fn(u64) -> Vec<Option<BigUint>>,
1007) -> Result<array::ArrayState, Outcome> {
1008    let mut cur_rows = Vec::new();
1009    let mut deeper = Vec::new();
1010    for words in collections::elements(handle) {
1011        if levels == 1 && a.inner_fixed > 0 {
1012            // The row is stored inline: `inner_fixed` leaf elements in one
1013            // flat 4-state value (value bits, then unknown bits).
1014            let w = a.elem_ty.width;
1015            let (val, unk) = unpack(&words, w * a.inner_fixed);
1016            let m = mask(w);
1017            cur_rows.push(
1018                (0..a.inner_fixed)
1019                    .map(|e| {
1020                        let sh = u64::from(e * w);
1021                        ((&unk >> sh) & &m).is_zero().then(|| (&val >> sh) & &m)
1022                    })
1023                    .collect(),
1024            );
1025            continue;
1026        }
1027        let (row, unk) = unpack(&words, 64);
1028        if !unk.is_zero() {
1029            return Err(Outcome::Failed);
1030        }
1031        let row = row.to_u64().ok_or(Outcome::Failed)?;
1032        if levels == 1 {
1033            cur_rows.push(leaves(row));
1034        } else {
1035            deeper.push(read_rows(a, row, levels - 1, active, leaves)?);
1036        }
1037    }
1038    let n = cur_rows.len().max(deeper.len());
1039    Ok(array::ArrayState {
1040        elem_ty: a.elem_ty,
1041        active,
1042        cur: vec![Some(BigUint::from(0u8)); n],
1043        domain: a.elem_domain.clone(),
1044        rows: Some(array::RowsState::new(levels, cur_rows, deeper)),
1045        template_of: None,
1046    })
1047}
1048
1049/// The call's outcome for a failed function call `e`.
1050fn outcome_of_func(e: FuncError, null_state: &[bool]) -> Outcome {
1051    match e {
1052        FuncError::Eval(ConstraintEvalError::StateUnknown(id)) => {
1053            unreadable_outcome(id, null_state)
1054        }
1055        FuncError::Eval(ConstraintEvalError::DivideByZero)
1056        | FuncError::TooDeep { .. }
1057        | FuncError::StepLimit { .. } => Outcome::FunctionFailed,
1058        FuncError::Eval(
1059            ConstraintEvalError::UnboundVar(_)
1060            | ConstraintEvalError::UnboundState(_)
1061            | ConstraintEvalError::Unexpanded,
1062        )
1063        | FuncError::Malformed(_) => Outcome::Failed,
1064    }
1065}
1066
1067/// The call's outcome for an expansion failure `e`.
1068fn outcome_of_expand(e: array::ExpandError, null_state: &[bool]) -> Outcome {
1069    match e {
1070        array::ExpandError::IndexOutOfRange { .. }
1071        | array::ExpandError::LeafIndexOutOfRange { .. } => Outcome::ArrayIndexOutOfRange,
1072        array::ExpandError::GuardError => Outcome::GuardError,
1073        array::ExpandError::StateUnknown(id) => unreadable_outcome(id, null_state),
1074        array::ExpandError::Func(e) => outcome_of_func(e, null_state),
1075        array::ExpandError::NonConstantIndex
1076        | array::ExpandError::DistRandomBound
1077        | array::ExpandError::Malformed(_) => Outcome::Failed,
1078    }
1079}
1080
1081/// The call's outcome for a size-solve failure `e`.
1082fn outcome_of_size(e: array::SizeError, null_state: &[bool]) -> Outcome {
1083    match e {
1084        array::SizeError::Unsatisfiable => Outcome::Unsatisfiable,
1085        array::SizeError::TooLarge => Outcome::ArraySizeTooLarge,
1086        array::SizeError::GuardError => Outcome::GuardError,
1087        array::SizeError::Solve(e) => outcome_of(e, null_state),
1088    }
1089}
1090
1091/// How many draws a stage with a check (a constraint comparing function results
1092/// over its own random variables) may try before the search is given up.
1093const MAX_CHECK_TRIES: usize = 4096;
1094
1095/// Whether every check holds for the draw `drawn` of the stage's variables: the
1096/// calls are evaluated on the drawn argument values.
1097fn checks_hold(
1098    checks: &[Expr],
1099    drawn: &[BigUint],
1100    funcs: &[Func],
1101    states: &[Option<BigUint>],
1102    null_state: &[bool],
1103) -> Result<bool, Outcome> {
1104    let asg = Assignment {
1105        vars: drawn.to_vec(),
1106        states: states.to_vec(),
1107    };
1108    for c in checks {
1109        let c = c.substitute_vars(&mut |v, t| match drawn.get(v as usize) {
1110            Some(val) => Expr::Const(t, val.clone()),
1111            None => Expr::Var(v, t),
1112        });
1113        let c = func::fold_ground_calls(&c, funcs, states)
1114            .map_err(|e| outcome_of_func(e, null_state))?;
1115        let v = eval(&c, &asg).map_err(|e| outcome_of_func(FuncError::Eval(e), null_state))?;
1116        if v.is_zero() {
1117            return Ok(false);
1118        }
1119    }
1120    Ok(true)
1121}
1122
1123/// Solves the scalar problem `x` (the expansion of the program's constraints
1124/// for one choice of array sizes). [`Outcome::Unsatisfiable`] is an ordinary
1125/// result the caller may retry with other sizes.
1126///
1127/// The real variables are solved after the integral ones (LRM 18.5.13). When a
1128/// real condition reads an integral random variable the integral draw may leave
1129/// the real part with no solution (`x < a` with `a == 0` and `x >= 0.0`), so
1130/// the integral part is redrawn, up to [`REAL_JOINT_TRIES`] times, before the
1131/// call is [`Outcome::Unsatisfiable`]: a joint solution is found whenever the
1132/// draws land on one (every such program is sampled by rejection, so a joint
1133/// solution that a tiny fraction of the integral draws allow may be missed).
1134fn solve_expanded(
1135    call: &Call<'_>,
1136    x: &array::Expanded,
1137    seed: &mut u64,
1138) -> Result<Solution, Outcome> {
1139    let joint = call.program.real.reads_int_vars();
1140    let tries = if joint { REAL_JOINT_TRIES } else { 1 };
1141    for _ in 1..tries {
1142        call.real_unsat.set(false);
1143        match solve_expanded_once(call, x, seed) {
1144            Err(Outcome::Unsatisfiable) if call.real_unsat.get() => {}
1145            r => return r,
1146        }
1147    }
1148    solve_expanded_once(call, x, seed)
1149}
1150
1151/// How many integral draws a joint integral/real solve makes before giving up.
1152const REAL_JOINT_TRIES: usize = 4096;
1153
1154fn solve_expanded_once(
1155    call: &Call<'_>,
1156    x: &array::Expanded,
1157    seed: &mut u64,
1158) -> Result<Solution, Outcome> {
1159    let program = call.program;
1160    let n_active = call.var_tys.len();
1161    let n_real = n_active + x.elem_vars.len();
1162    let mut states = call.states.clone();
1163    states.extend(x.extra_states.iter().cloned());
1164    let mut null_state = call.null_state.clone();
1165    null_state.resize(states.len(), false);
1166    let mut var_tys = call.var_tys.clone();
1167    var_tys.extend(x.elem_tys.iter().copied());
1168
1169    // The dists' hidden selector variables are numbered after every real
1170    // variable, the elements included (module docs of `super::dist`).
1171    let conjuncts: Vec<Expr> = x
1172        .conjuncts
1173        .iter()
1174        .map(|c| {
1175            c.substitute_vars(&mut |v, t| {
1176                if v >= SELECTOR_BASE {
1177                    Expr::Var(n_real as u32 + (v - SELECTOR_BASE), t)
1178                } else {
1179                    Expr::Var(v, t)
1180                }
1181            })
1182        })
1183        .collect();
1184    let assignment = Assignment {
1185        vars: Vec::new(),
1186        states: states.clone(),
1187    };
1188
1189    // Function calls (Task C13, LRM 18.5.11): the random arguments of a call
1190    // are solved first, so the variables are solved in stages (module docs of
1191    // [`super::func_order`]); a program without a call over random variables
1192    // is one stage, solved exactly as before.
1193    let has_calls = conjuncts.iter().any(func::has_call);
1194    let stage_of: Vec<u32> = if has_calls {
1195        func_order::stages(n_real, &conjuncts).map_err(|_| Outcome::ImplicitOrderCycle)?
1196    } else {
1197        vec![0; n_real]
1198    };
1199    let n_stages = stage_of.iter().max().map_or(1, |m| *m as usize + 1);
1200    // A constraint is solved with the latest stage of the variables it holds;
1201    // a selector variable is solved with its constraint.
1202    let mut conj_stage: Vec<usize> = Vec::with_capacity(conjuncts.len());
1203    // The program's dists, then the instances of its `foreach` templates the
1204    // expansion made (selector slots in that order; a template's own slot is
1205    // never planned).
1206    let n_prog_dists = program.dists.len();
1207    let all_dists: Vec<&Dist> = program.dists.iter().chain(&x.extra_dists).collect();
1208    let mut dist_stage: Vec<usize> = vec![0; all_dists.len()];
1209    for c in &conjuncts {
1210        let mut s = 0usize;
1211        c.for_each_var(&mut |v, _| {
1212            if let Some(&st) = stage_of.get(v as usize) {
1213                s = s.max(st as usize);
1214            }
1215        });
1216        c.for_each_var(&mut |v, _| {
1217            if let Some(j) = (v as usize).checked_sub(n_real)
1218                && let Some(d) = dist_stage.get_mut(j)
1219            {
1220                *d = s;
1221            }
1222        });
1223        conj_stage.push(s);
1224    }
1225
1226    // Ordering between the active variables only, renumbered like them.
1227    let mut edges: Vec<(u32, u32)> = Vec::with_capacity(program.before.len());
1228    for &(a, b) in &program.before {
1229        let (Some(&na), Some(&nb)) = (call.var_new.get(a as usize), call.var_new.get(b as usize))
1230        else {
1231            return Err(Outcome::Failed);
1232        };
1233        if let (Some(na), Some(nb)) = (na, nb) {
1234            edges.push((na, nb));
1235        }
1236    }
1237
1238    // The active `randc` variables are drawn first, each from its own cycle
1239    // (LRM 18.4.2), so they leave the ordered groups. Their cycle states are
1240    // worked on as copies and committed only if the whole call succeeds.
1241    let mut randc_dense: Vec<u32> = Vec::new();
1242    let mut randc_keys: Vec<(u64, usize)> = Vec::new();
1243    let mut randc_work: Vec<RandcState> = Vec::new();
1244    for (dense, &i) in call.active_ids.iter().enumerate() {
1245        let v = &program.vars[i];
1246        if !v.randc {
1247            continue;
1248        }
1249        let Some(st) = objects::randc_state(call.var_owner[i], v.randc_key()) else {
1250            return Err(Outcome::DeadHandle);
1251        };
1252        randc_dense.push(dense as u32);
1253        randc_keys.push((call.var_owner[i], v.randc_key()));
1254        randc_work.push(st);
1255    }
1256    // Every `dist`'s weights, evaluated once, and its selector variable.
1257    let mut dist_weights = Vec::with_capacity(all_dists.len());
1258    for d in &all_dists {
1259        if d.subject.is_some() {
1260            dist_weights.push(Vec::new());
1261        } else {
1262            match d.weights(&states) {
1263                Ok(w) => dist_weights.push(w),
1264                Err(e) => return Err(outcome_of_dist(e, &null_state)),
1265            }
1266        }
1267        var_tys.push(d.selector_ty());
1268    }
1269
1270    let mut values: Vec<BigUint> = vec![BigUint::zero(); n_real];
1271    let mut known: Vec<Option<BigUint>> = vec![None; n_real];
1272    for stage in 0..n_stages {
1273        let in_stage = |v: u32| {
1274            stage_of
1275                .get(v as usize)
1276                .is_some_and(|s| *s as usize == stage)
1277        };
1278        // This stage's constraints: the earlier stages' variables are fixed,
1279        // so they are constants, and a call over them is evaluated. A call
1280        // that is left is over this stage's own variables (one constraint
1281        // comparing results of calls): it is a check on each draw.
1282        let mut plain: Vec<Expr> = Vec::new();
1283        let mut checks: Vec<Expr> = Vec::new();
1284        for (c, _) in conjuncts
1285            .iter()
1286            .zip(&conj_stage)
1287            .filter(|(_, s)| **s == stage)
1288        {
1289            let mut c = c.clone();
1290            if has_calls {
1291                if stage > 0 {
1292                    c = c.substitute_vars(&mut |v, t| match known.get(v as usize) {
1293                        Some(Some(val)) => Expr::Const(t, val.clone()),
1294                        _ => Expr::Var(v, t),
1295                    });
1296                }
1297                c = func::fold_ground_calls(&c, &program.funcs, &states)
1298                    .map_err(|e| outcome_of_func(e, &null_state))?;
1299            }
1300            if func::has_call(&c) {
1301                checks.push(c);
1302                continue;
1303            }
1304            // Resolve the guards of implication / `if` constraints against
1305            // the state values (LRM 18.5.12); an ERROR guard fails the call.
1306            match guard::resolve(&c, &assignment) {
1307                Ok(r) => plain.push(r),
1308                Err(_) => return Err(Outcome::GuardError),
1309            }
1310        }
1311
1312        let stage_edges: Vec<(u32, u32)> = edges
1313            .iter()
1314            .copied()
1315            .filter(|&(a, b)| in_stage(a) && in_stage(b))
1316            .collect();
1317        let Ok(groups) = order::groups(n_real, &stage_edges) else {
1318            return Err(Outcome::Failed);
1319        };
1320        // The stage's `randc` variables, as indices into the call's lists.
1321        let stage_randc: Vec<usize> = (0..randc_dense.len())
1322            .filter(|&k| in_stage(randc_dense[k]))
1323            .collect();
1324        let stage_randc_dense: Vec<u32> = stage_randc.iter().map(|&k| randc_dense[k]).collect();
1325        // One weighted selector per `dist`, drawn with the first group that
1326        // holds a variable of its subject (so the subject is still undrawn), or
1327        // with the last group when the subject has no active variable.
1328        let mut plan_groups: Vec<Group> = groups
1329            .into_iter()
1330            .map(|g| Group {
1331                vars: g
1332                    .into_iter()
1333                    .filter(|v| in_stage(*v) && !randc_dense.contains(v))
1334                    .collect(),
1335                weighted: Vec::new(),
1336            })
1337            .collect();
1338        let stage_dists: Vec<usize> = (0..all_dists.len())
1339            .filter(|&j| dist_stage[j] == stage && all_dists[j].subject.is_none())
1340            .collect();
1341        if !stage_dists.is_empty() && plan_groups.is_empty() {
1342            plan_groups.push(Group::default());
1343        }
1344        for &j in &stage_dists {
1345            let d = all_dists[j];
1346            // An instance's variables are the expansion's own dense ids.
1347            let dense = |v: u32| {
1348                if j >= n_prog_dists {
1349                    Some(v)
1350                } else {
1351                    call.var_new.get(v as usize).copied().flatten()
1352                }
1353            };
1354            let subject: Vec<u32> = d
1355                .vars
1356                .iter()
1357                .filter_map(|&v| dense(v))
1358                .filter(|&v| in_stage(v))
1359                .collect();
1360            let gi = plan_groups
1361                .iter()
1362                .position(|g| g.vars.iter().any(|v| subject.contains(v)))
1363                .unwrap_or(plan_groups.len() - 1);
1364            let measure = match (
1365                d.plain_var.and_then(dense).filter(|&v| in_stage(v)),
1366                subject.is_empty(),
1367            ) {
1368                (Some(s), _) => Measure::Distinct(s),
1369                (None, false) => Measure::Models,
1370                (None, true) => Measure::Flat,
1371            };
1372            plan_groups[gi].weighted.push(Weighted {
1373                var: (n_real + j) as u32,
1374                weights: dist_weights[j].clone(),
1375                measure,
1376            });
1377        }
1378        let plan = Plan {
1379            groups: plan_groups
1380                .into_iter()
1381                .filter(|g| !g.vars.is_empty() || !g.weighted.is_empty())
1382                .collect(),
1383            randc: stage_randc_dense,
1384        };
1385
1386        let solver =
1387            Engine::new(&var_tys, &states, &plain).map_err(|e| outcome_of(e, &null_state))?;
1388        let mut tries = 0;
1389        let drawn = loop {
1390            let mut work: Vec<RandcState> =
1391                stage_randc.iter().map(|&k| randc_work[k].clone()).collect();
1392            let drawn = solver
1393                .sample_plan(&plan, &mut work, seed)
1394                .map_err(|e| outcome_of(e, &null_state))?;
1395            if checks_hold(&checks, &drawn, &program.funcs, &states, &null_state)? {
1396                for (&k, st) in stage_randc.iter().zip(work) {
1397                    randc_work[k] = st;
1398                }
1399                break drawn;
1400            }
1401            tries += 1;
1402            if tries >= MAX_CHECK_TRIES {
1403                return Err(Outcome::FunctionSearch);
1404            }
1405        };
1406        for v in 0..n_real {
1407            if in_stage(v as u32) {
1408                values[v] = drawn[v].clone();
1409                known[v] = Some(drawn[v].clone());
1410            }
1411        }
1412    }
1413
1414    // The real variables are solved after the integral ones, from the same
1415    // PRNG stream (a class with none draws nothing here).
1416    let real_writes = run_real(
1417        program,
1418        call.h,
1419        call.rand_mode,
1420        call.statics,
1421        &states,
1422        &null_state,
1423        Some((&call.var_new, &values)),
1424        seed,
1425    )
1426    .inspect_err(|o| call.real_unsat.set(*o == Outcome::Unsatisfiable))?;
1427    Ok(Solution {
1428        values,
1429        randc_keys,
1430        randc_work,
1431        real_writes,
1432    })
1433}
1434
1435/// What [`solve_all`] returns: the solution, the element variables, and the
1436/// size of every array.
1437type Solved = (Solution, Vec<(u32, usize)>, Vec<usize>, Vec<(u32, u32)>);
1438
1439/// Solves the call's constraints `conjuncts` (no soft wrappers left): the
1440/// array sizes first, then the elements and the scalars for those sizes, trying
1441/// other sizes when the sizes drawn alone made the whole problem
1442/// unsatisfiable.
1443fn solve_all(call: &Call<'_>, conjuncts: &[Expr], seed: &mut u64) -> Result<Solved, Outcome> {
1444    if call.arrays.iter().any(|a| a.rows.is_some()) {
1445        return solve_all_rows(call, conjuncts, seed);
1446    }
1447    let n_active = call.var_tys.len() as u32;
1448    let mut blocked: Vec<Vec<usize>> = Vec::new();
1449    loop {
1450        let chosen = array::solve_sizes(
1451            conjuncts,
1452            &call.arrays,
1453            &call.var_tys,
1454            &call.states,
1455            &blocked,
1456            seed,
1457        )
1458        .map_err(|e| outcome_of_size(e, &call.null_state))?;
1459        let x = array::expand_with(
1460            conjuncts,
1461            &call.arrays,
1462            &chosen.sizes,
1463            n_active,
1464            &call.states,
1465            &call.program.funcs,
1466            &call.program.dists,
1467        )
1468        .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1469        match solve_expanded(call, &x, seed) {
1470            Ok(s) => return Ok((s, x.elem_vars, chosen.sizes, Vec::new())),
1471            // The sizes were drawn without the element constraints, so they
1472            // may be what made the whole problem unsatisfiable: block them.
1473            Err(Outcome::Unsatisfiable) if !chosen.sized.is_empty() => {
1474                blocked.push(chosen.tuple);
1475                if blocked.len() >= array::MAX_SIZE_RETRIES {
1476                    return Err(Outcome::ArraySizeSearch);
1477                }
1478            }
1479            Err(o) => return Err(o),
1480        }
1481    }
1482}
1483
1484/// [`solve_all`] for a program with an array of dynamic arrays or queues
1485/// (`rand int a[][]`, LRM 18.4): the sizes are solved in two stages, the outer
1486/// array's first, then those of the rows it has ([`array::lower_rows`]), and
1487/// every row is its own array of the expansion. A row-size tuple the
1488/// constraints cannot satisfy is blocked like an outer one.
1489fn solve_all_rows(call: &Call<'_>, conjuncts: &[Expr], seed: &mut u64) -> Result<Solved, Outcome> {
1490    let mut tries = 0usize;
1491    rows_stage(call, conjuncts, &call.arrays, &[], seed, &mut tries)
1492}
1493
1494/// One stage of [`solve_all_rows`]: sizes the arrays of `arrays` that
1495/// constraints size, then either binds the rows of the arrays of arrays whose
1496/// rows are not bound yet (a row that is itself an array of arrays comes out
1497/// unbound and is sized by the next stage: `int a[][][]`) or, with every row
1498/// bound, expands and solves. A stage whose rows cannot be satisfied blocks
1499/// the size tuple it chose and tries another; `bases` are the row bases earlier
1500/// stages bound.
1501fn rows_stage(
1502    call: &Call<'_>,
1503    conjuncts: &[Expr],
1504    arrays: &[array::ArrayState],
1505    bases: &[(u32, u32)],
1506    seed: &mut u64,
1507    tries: &mut usize,
1508) -> Result<Solved, Outcome> {
1509    let n_active = call.var_tys.len() as u32;
1510    let mut blocked: Vec<Vec<usize>> = Vec::new();
1511    let unbound = arrays
1512        .iter()
1513        .any(|a| a.rows.as_ref().is_some_and(|r| !r.lowered));
1514    loop {
1515        *tries += 1;
1516        if *tries >= array::MAX_SIZE_RETRIES {
1517            return Err(Outcome::ArraySizeSearch);
1518        }
1519        let sz = array::solve_sizes(
1520            conjuncts,
1521            arrays,
1522            &call.var_tys,
1523            &call.states,
1524            &blocked,
1525            seed,
1526        )
1527        // No sizes fit (the stage above blocks the sizes it chose and retries).
1528        .map_err(|e| outcome_of_size(e, &call.null_state))?;
1529        let res = if unbound {
1530            let low = array::lower_rows(conjuncts, arrays, &sz.sizes, &call.states)
1531                .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1532            let mut all = bases.to_vec();
1533            all.extend_from_slice(&low.bases);
1534            rows_stage(call, &low.conjuncts, &low.arrays, &all, seed, tries)
1535        } else {
1536            let x = array::expand_with(
1537                conjuncts,
1538                arrays,
1539                &sz.sizes,
1540                n_active,
1541                &call.states,
1542                &call.program.funcs,
1543                &call.program.dists,
1544            )
1545            .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1546            solve_expanded(call, &x, seed)
1547                .map(|s| (s, x.elem_vars, sz.sizes.clone(), bases.to_vec()))
1548        };
1549        match res {
1550            Err(Outcome::Unsatisfiable) if !sz.sized.is_empty() => blocked.push(sz.tuple),
1551            other => return other,
1552        }
1553    }
1554}
1555
1556/// Solves `program` for the object `h` and, on success, writes the result
1557/// (module docs). `rand_mode[i]` is whether property `i` is active; `statics`
1558/// are the `(id, handle)` pairs of the static-property objects the call site
1559/// reaches.
1560fn run(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1561    // The constraint blocks `constraint_mode(0)` switched off (LRM 18.9) are
1562    // not part of this call, whatever they hold. A static block whose object
1563    // the call site did not pass cannot have been switched off through this
1564    // call's handle, so it stays on.
1565    let mut off: Vec<&ProgBlock> = Vec::new();
1566    for b in &program.blocks {
1567        let Ok(o) = owner(b.home, h, statics) else {
1568            continue;
1569        };
1570        match objects::constraint_mode_of(o, b.ordinal) {
1571            Some(true) => {}
1572            Some(false) => off.push(b),
1573            None => return Outcome::DeadHandle,
1574        }
1575    }
1576    if off.is_empty() {
1577        return run_blocks_on(program, h, rand_mode, statics);
1578    }
1579    match reduce::without_blocks(program, &off) {
1580        Ok(p) => run_blocks_on(&p, h, rand_mode, statics),
1581        Err(o) => o,
1582    }
1583}
1584
1585/// [`run`] for a program none of whose blocks is switched off.
1586fn run_blocks_on(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1587    let Some(seed0) = objects::prng_seed(h) else {
1588        return Outcome::DeadHandle;
1589    };
1590    let mut var_owner = Vec::with_capacity(program.vars.len());
1591    for v in &program.vars {
1592        match owner(v.home, h, statics) {
1593            Ok(o) => var_owner.push(o),
1594            Err(o) => return o,
1595        }
1596    }
1597    let mut state_owner = Vec::with_capacity(program.states.len());
1598    for s in &program.states {
1599        match owner(s.home, h, statics) {
1600            Ok(o) => state_owner.push(o),
1601            Err(o) => return o,
1602        }
1603    }
1604    let mut array_owner = Vec::with_capacity(program.arrays.len());
1605    for a in &program.arrays {
1606        match owner(a.home, h, statics) {
1607            Ok(o) => array_owner.push(o),
1608            Err(o) => return o,
1609        }
1610    }
1611    let active = |v: &ProgVar| match v.prop_index {
1612        Some(i) => rand_mode.get(i).copied(),
1613        None => Some(true),
1614    };
1615    // Dense renumbering of the active variables; inactive ones become extra
1616    // state variables after the program's own, read like any other state.
1617    let mut var_new: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1618    let mut var_tys: Vec<Ty> = Vec::new();
1619    let mut active_ids: Vec<usize> = Vec::new();
1620    let mut inactive_state: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1621    let mut inactive_ids: Vec<usize> = Vec::new();
1622    for (i, v) in program.vars.iter().enumerate() {
1623        match active(v) {
1624            None => return Outcome::Failed,
1625            Some(true) => {
1626                var_new.push(Some(var_tys.len() as u32));
1627                inactive_state.push(None);
1628                var_tys.push(v.ty);
1629                active_ids.push(i);
1630            }
1631            Some(false) => {
1632                var_new.push(None);
1633                inactive_state.push(Some((program.states.len() + inactive_ids.len()) as u32));
1634                inactive_ids.push(i);
1635            }
1636        }
1637    }
1638
1639    let mut states: Vec<Option<BigUint>> = Vec::with_capacity(program.states.len());
1640    let mut null_state: Vec<bool> = Vec::with_capacity(program.states.len());
1641    for (s, &o) in program.states.iter().zip(&state_owner) {
1642        match read_state(o, s) {
1643            Ok(Reading::Value(v)) => {
1644                states.push(v);
1645                null_state.push(false);
1646            }
1647            Ok(Reading::NullHandle) => {
1648                states.push(None);
1649                null_state.push(true);
1650            }
1651            Err(o) => return o,
1652        }
1653    }
1654    for &i in &inactive_ids {
1655        let v = &program.vars[i];
1656        match read(var_owner[i], v.slot, v.window, v.ty) {
1657            Ok(x) => states.push(x),
1658            Err(o) => return o,
1659        }
1660        null_state.push(false);
1661    }
1662    let mut arrays = Vec::with_capacity(program.arrays.len());
1663    for (a, &o) in program.arrays.iter().zip(&array_owner) {
1664        let on = a.rand
1665            && match a.prop_index {
1666                Some(i) => match rand_mode.get(i) {
1667                    Some(&b) => b,
1668                    None => return Outcome::Failed,
1669                },
1670                None => true,
1671            };
1672        match read_array(a, o, on) {
1673            Ok(s) => arrays.push(s),
1674            Err(o) => return o,
1675        }
1676    }
1677    // Tell each array of arrays which array stands for its rows.
1678    // A template's `template_of` names the array it is the row template of; a
1679    // deeper level's names the template above it, so the chain leads to the
1680    // array that holds the rows.
1681    for t in 0..arrays.len() {
1682        let Some(mut o) = arrays[t].template_of else {
1683            continue;
1684        };
1685        let mut level = 0;
1686        while let Some(up) = arrays.get(o as usize).and_then(|a| a.template_of) {
1687            o = up;
1688            level += 1;
1689        }
1690        if let Some(rows) = arrays.get_mut(o as usize).and_then(|a| a.rows.as_mut()) {
1691            rows.set_template(level, t as u32);
1692        }
1693    }
1694
1695    // The conjuncts left out of this call: the enum-domain constraint of an
1696    // inactive variable (LRM 18.8: it is state, not a random variable).
1697    let mut dropped = vec![false; program.conjuncts.len()];
1698    for &i in &inactive_ids {
1699        if let Some(d) = program.vars[i].domain_conjunct {
1700            match dropped.get_mut(d) {
1701                Some(slot) => *slot = true,
1702                None => return Outcome::Failed,
1703            }
1704        }
1705    }
1706    let call = Call {
1707        program,
1708        h,
1709        rand_mode,
1710        statics,
1711        var_owner,
1712        var_new,
1713        var_tys,
1714        active_ids,
1715        arrays,
1716        array_owner,
1717        states,
1718        null_state,
1719        real_unsat: std::cell::Cell::new(false),
1720    };
1721
1722    // The constraints one choice of discarded soft constraints leaves: the
1723    // soft wrappers resolved (module docs of [`super::priority`]), the field
1724    // variables renumbered densely (the dists' selector variables are
1725    // renumbered once the element variables exist), and every call whose
1726    // arguments are all fixed (state, constants) evaluated now (LRM 18.5.11:
1727    // calls are evaluated before solving), so a size constraint or an index
1728    // may read its result.
1729    let has_softs = !program.softs.is_empty();
1730    let prepare = |discard: &[bool]| -> Result<Vec<Expr>, Outcome> {
1731        let mut out = Vec::with_capacity(program.conjuncts.len());
1732        for (i, c) in program.conjuncts.iter().enumerate() {
1733            if dropped[i] {
1734                continue;
1735            }
1736            let resolved;
1737            let c = if has_softs {
1738                resolved = priority::resolve_softs(c, discard).map_err(|_| Outcome::Failed)?;
1739                &resolved
1740            } else {
1741                c
1742            };
1743            let c = c.substitute_vars(&mut |v, t| {
1744                if v >= SELECTOR_BASE {
1745                    return Expr::Var(v, t);
1746                }
1747                match (call.var_new[v as usize], inactive_state[v as usize]) {
1748                    (Some(n), _) => Expr::Var(n, t),
1749                    (None, Some(s)) => Expr::State(s, t),
1750                    (None, None) => unreachable!("every variable is active or inactive"),
1751                }
1752            });
1753            out.push(if program.funcs.is_empty() {
1754                c
1755            } else {
1756                func::fold_ground_calls(&c, &program.funcs, &call.states)
1757                    .map_err(|e| outcome_of_func(e, &call.null_state))?
1758            });
1759        }
1760        Ok(out)
1761    };
1762
1763    let mut seed = seed0;
1764    let solved = if has_softs {
1765        // Whether the problem has a solution is what a soft constraint trades
1766        // against; every other outcome is the call's.
1767        priority::solve_relaxing(
1768            &program.softs,
1769            &mut seed,
1770            |discard, seed| solve_all(&call, &prepare(discard)?, seed),
1771            |o| matches!(o, Outcome::Unsatisfiable | Outcome::ArraySizeSearch),
1772        )
1773    } else {
1774        prepare(&[]).and_then(|c| solve_all(&call, &c, &mut seed))
1775    };
1776    let (solution, elem_vars, sizes, row_bases) = match solved {
1777        Ok(s) => s,
1778        Err(o) => return o,
1779    };
1780
1781    // Pack everything first so a layout mismatch writes nothing at all.
1782    let mut writes = solution.real_writes;
1783    writes.reserve(call.active_ids.len());
1784    for (k, &i) in call.active_ids.iter().enumerate() {
1785        let v = &program.vars[i];
1786        match Write::new(call.var_owner[i], v.slot, v.window, &solution.values[k]) {
1787            Some(w) => writes.push(w),
1788            None => return Outcome::Failed,
1789        }
1790    }
1791    // Each active array's new contents, element words flat.
1792    let mut new_arrays: Vec<(usize, Vec<u64>)> = Vec::new();
1793    for (ai, a) in call.arrays.iter().enumerate() {
1794        if !a.active {
1795            continue;
1796        }
1797        let ew = program.arrays[ai].elem_words;
1798        if program.arrays[ai].nested {
1799            // One new row collection per row (and per row of a row, for an
1800            // array nested deeper); the outer holds their handles.
1801            let mut out = RowWriter {
1802                pa: &program.arrays[ai],
1803                elem_vars: &elem_vars,
1804                values: &solution.values[call.active_ids.len()..],
1805                bases: &row_bases,
1806                sizes: &sizes,
1807                made: Vec::new(),
1808            };
1809            match out.elements(ai as u32, program.arrays[ai].row_depth.max(1)) {
1810                Some(flat) => new_arrays.push((ai, flat)),
1811                None => {
1812                    out.made.iter().for_each(|&h| collections::release(h));
1813                    return Outcome::Failed;
1814                }
1815            }
1816            continue;
1817        }
1818        let mut flat = Vec::with_capacity(sizes[ai] * ew);
1819        for (e, &(arr, _)) in elem_vars.iter().enumerate() {
1820            if arr as usize != ai {
1821                continue;
1822            }
1823            match pack(&solution.values[call.active_ids.len() + e], ew) {
1824                Some(w) => flat.extend(w),
1825                None => return Outcome::Failed,
1826            }
1827        }
1828        new_arrays.push((ai, flat));
1829    }
1830    for w in writes {
1831        if !w.apply() {
1832            return Outcome::DeadHandle;
1833        }
1834    }
1835    for (ai, flat) in new_arrays {
1836        let pa = &program.arrays[ai];
1837        let o = call.array_owner[ai];
1838        let (old_h, _) = match read_unknown(o, pa.slot, pa.window, 64) {
1839            Ok(x) => x,
1840            Err(o) => return o,
1841        };
1842        let new = if pa.nested {
1843            // The outer's elements are the rows themselves (inline) or their
1844            // handles built above; the old rows are not owned by the outer and
1845            // stay alive.
1846            let shape = collections::Shape {
1847                elem_words: row_elem_words(pa, pa.row_depth.max(1)),
1848                assoc: false,
1849                key: collections::KeyKind::Int {
1850                    width: 32,
1851                    signed: false,
1852                },
1853                obj: false,
1854                nested: collections::Nested::NONE,
1855            };
1856            collections::from_words(&shape, &flat)
1857        } else if pa.assoc {
1858            // Same keys, new values: an associative array is never resized.
1859            match collections::map_with_values(old_h.to_u64().unwrap_or(0), pa.elem_words, &flat) {
1860                Some(h) => h,
1861                None => return Outcome::Failed,
1862            }
1863        } else {
1864            let shape = collections::Shape {
1865                elem_words: pa.elem_words,
1866                assoc: false,
1867                key: collections::KeyKind::Int {
1868                    width: 32,
1869                    signed: false,
1870                },
1871                obj: false,
1872                nested: collections::Nested::NONE,
1873            };
1874            collections::from_words(&shape, &flat)
1875        };
1876        let Some(w) = Write::new(o, pa.slot, pa.window, &BigUint::from(new)) else {
1877            collections::release(new);
1878            return Outcome::Failed;
1879        };
1880        if !w.apply() {
1881            collections::release(new);
1882            return Outcome::DeadHandle;
1883        }
1884        collections::release(old_h.to_u64().unwrap_or(0));
1885    }
1886    for ((o, offset), st) in solution.randc_keys.into_iter().zip(solution.randc_work) {
1887        if !objects::set_randc_states(o, vec![(offset, st)]) {
1888            return Outcome::DeadHandle;
1889        }
1890    }
1891    objects::set_prng_seed(h, seed);
1892    Outcome::Solved
1893}
1894
1895/// The words of one element of a collection whose elements are rows `levels`
1896/// levels above the leaves: a row handle (2 words) or, for the innermost level
1897/// of an array whose last dimension is fixed, the row's elements inline
1898/// (`int a[][4]`, LRM 1800-2023 7.4, 7.5).
1899fn row_elem_words(pa: &ProgArray, levels: u32) -> usize {
1900    if levels == 1 && pa.inner_fixed > 0 {
1901        (2 * pa.inner_fixed as usize * pa.elem_ty.width as usize)
1902            .div_ceil(64)
1903            .max(1)
1904    } else {
1905        2
1906    }
1907}
1908
1909/// Builds the new collections of one array of arrays from a solution.
1910struct RowWriter<'a> {
1911    pa: &'a ProgArray,
1912    elem_vars: &'a [(u32, usize)],
1913    /// The value of each element variable.
1914    values: &'a [BigUint],
1915    /// `(array of arrays, the array standing for its row 0)` of every stage.
1916    bases: &'a [(u32, u32)],
1917    sizes: &'a [usize],
1918    /// The collections made so far (released if the write fails).
1919    made: Vec<u64>,
1920}
1921
1922impl RowWriter<'_> {
1923    fn base(&self, arr: u32) -> Option<u32> {
1924        self.bases.iter().find(|&&(o, _)| o == arr).map(|&(_, b)| b)
1925    }
1926
1927    fn shape(elem_words: usize) -> collections::Shape {
1928        collections::Shape {
1929            elem_words,
1930            assoc: false,
1931            key: collections::KeyKind::Int {
1932                width: 32,
1933                signed: false,
1934            },
1935            obj: false,
1936            nested: collections::Nested::NONE,
1937        }
1938    }
1939
1940    /// The element words of the collection standing for `arr`, whose rows
1941    /// are `levels` levels above the leaves.
1942    fn elements(&mut self, arr: u32, levels: u32) -> Option<Vec<u64>> {
1943        let base = self.base(arr)?;
1944        let mut out = Vec::new();
1945        for k in 0..*self.sizes.get(arr as usize)? {
1946            let row = base + k as u32;
1947            if levels > 1 {
1948                let words = self.elements(row, levels - 1)?;
1949                let h = collections::from_words(
1950                    &Self::shape(row_elem_words(self.pa, levels - 1)),
1951                    &words,
1952                );
1953                self.made.push(h);
1954                out.extend(pack(&BigUint::from(h), 2)?);
1955            } else if self.pa.inner_fixed > 0 {
1956                // The row is stored inline: `inner_fixed` leaf elements in one
1957                // flat value, lowest index first. The constraints pin every
1958                // row to that many.
1959                let n = self.pa.inner_fixed as usize;
1960                let w = self.pa.elem_ty.width as usize;
1961                let mut val = BigUint::zero();
1962                let mut count = 0usize;
1963                for (e, &(a, _)) in self.elem_vars.iter().enumerate() {
1964                    if a == row {
1965                        val |= &self.values[e] << (count * w);
1966                        count += 1;
1967                    }
1968                }
1969                if count != n {
1970                    return None;
1971                }
1972                out.extend(pack(&val, row_elem_words(self.pa, 1))?);
1973            } else {
1974                let ew = self.pa.elem_words;
1975                let mut flat = Vec::new();
1976                for (e, &(a, _)) in self.elem_vars.iter().enumerate() {
1977                    if a == row {
1978                        flat.extend(pack(&self.values[e], ew)?);
1979                    }
1980                }
1981                let h = collections::from_words(&Self::shape(ew), &flat);
1982                self.made.push(h);
1983                out.extend(pack(&BigUint::from(h), 2)?);
1984            }
1985        }
1986        Some(out)
1987    }
1988}
1989
1990/// One local variable an inline constraint reads (LRM 18.7): its value is
1991/// taken when the call is made and is a constant of the solve.
1992#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1993pub struct Capture {
1994    /// The fragment state that stands for the local ([`Spec::frag`]).
1995    pub state: u32,
1996    /// Its bit width.
1997    pub width: u32,
1998}
1999
2000impl Capture {
2001    /// How many 64-bit words the call site passes for it: the 4-state packed
2002    /// value, as everywhere in the object store.
2003    pub fn words(&self) -> usize {
2004        (2 * self.width as usize).div_ceil(64).max(1)
2005    }
2006}
2007
2008/// Where one property a `randomize(v, ...)` argument list names lives (LRM
2009/// 18.11), in the same terms a [`Program`] locates its variables.
2010#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2011pub struct Target {
2012    /// The object the property is a field of.
2013    pub home: Home,
2014    /// The property.
2015    pub slot: FieldSlot,
2016    /// The leaf of an unpacked aggregate property it names, or `None` for the
2017    /// whole property (every leaf of it, for an aggregate).
2018    pub window: Option<Window>,
2019}
2020
2021/// What one `randomize(...)` call site adds to its class's constraints: the
2022/// inline constraints of `with {}` (C19), the argument list of `randomize(v,
2023/// ...)` / `randomize(null)` (C20). Built by the compiler once per call site and
2024/// registered ([`register_spec`]); the call passes only its id and the values of
2025/// the locals it captured.
2026#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2027pub struct Spec {
2028    /// The inline constraints, as a [`Program`] over the receiver's *static*
2029    /// class: its variables, arrays and states name properties by where they
2030    /// live, so [`joint::overlay`] lays it over the program of whatever dynamic
2031    /// class the receiver turns out to be. No constraint blocks of the class
2032    /// are in it.
2033    pub frag: Program,
2034    /// The locals the fragment reads, in the order their words follow the
2035    /// static-property pairs in the call's buffer.
2036    pub captures: Vec<Capture>,
2037    /// `Some(list)`: the complete set of random variables of the call (LRM
2038    /// 18.11); everything else is state. `Some(vec![])` is `randomize(null)`,
2039    /// a checker. `None`: the object's own `rand` variables, as `rand_mode`
2040    /// leaves them.
2041    pub only: Option<Vec<Target>>,
2042}
2043
2044impl Spec {
2045    /// How many words of captured values a call to this spec passes.
2046    pub fn capture_words(&self) -> usize {
2047        self.captures.iter().map(Capture::words).sum()
2048    }
2049
2050    /// For a scope randomize ([`randomize_scope`]): how many words the solved
2051    /// variables occupy, the extent of the scratch area [`Self::frag`] lays them
2052    /// out in.
2053    pub fn scope_words(&self) -> usize {
2054        let ints = self.frag.vars.iter().map(|v| v.slot.offset + v.slot.words);
2055        let reals = self
2056            .frag
2057            .real_vars
2058            .iter()
2059            .map(|v| v.slot.offset + v.slot.words);
2060        // a queue / dynamic array of the call keeps its handle in one word
2061        let arrays = self
2062            .frag
2063            .arrays
2064            .iter()
2065            .map(|v| v.slot.offset + v.slot.words);
2066        ints.chain(reals).chain(arrays).max().unwrap_or(0)
2067    }
2068}
2069
2070type SpecTable = HashMap<u32, Arc<Spec>>;
2071
2072fn specs() -> &'static Mutex<SpecTable> {
2073    static T: OnceLock<Mutex<SpecTable>> = OnceLock::new();
2074    T.get_or_init(|| Mutex::new(HashMap::new()))
2075}
2076
2077/// The next id [`register_spec`] hands out.
2078static NEXT_SPEC: AtomicU32 = AtomicU32::new(1);
2079
2080/// Registers `spec` and returns its id (`1..`; `0` is "no spec", a plain
2081/// `obj.randomize()`).
2082pub fn register_spec(spec: Spec) -> u32 {
2083    let id = NEXT_SPEC.fetch_add(1, Ordering::Relaxed);
2084    specs()
2085        .lock()
2086        .unwrap_or_else(|e| e.into_inner())
2087        .insert(id, Arc::new(spec));
2088    id
2089}
2090
2091/// Registers `spec` under the id it had in the process that built a design
2092/// image ([`wire::preload`]): the id is baked into the machine code of the
2093/// call site, so the runner must not mint its own. Later [`register_spec`]
2094/// calls continue above it.
2095pub(super) fn register_spec_at(id: u32, spec: Spec) {
2096    NEXT_SPEC.fetch_max(id + 1, Ordering::Relaxed);
2097    specs()
2098        .lock()
2099        .unwrap_or_else(|e| e.into_inner())
2100        .insert(id, Arc::new(spec));
2101}
2102
2103/// Every registered spec, ascending by id (a hash table has no order of its
2104/// own, and a design image must encode deterministically).
2105pub(super) fn all_specs() -> Vec<(u32, Arc<Spec>)> {
2106    let mut v: Vec<_> = specs()
2107        .lock()
2108        .unwrap_or_else(|e| e.into_inner())
2109        .iter()
2110        .map(|(&id, s)| (id, Arc::clone(s)))
2111        .collect();
2112    v.sort_by_key(|(id, _)| *id);
2113    v
2114}
2115
2116/// The spec registered under `id`.
2117pub fn spec(id: u32) -> Option<Arc<Spec>> {
2118    specs()
2119        .lock()
2120        .unwrap_or_else(|e| e.into_inner())
2121        .get(&id)
2122        .cloned()
2123}
2124
2125/// How many specs this process has registered: a design whose `randomize()`
2126/// call sites registered one carries solver state that exists only here.
2127pub fn spec_count() -> usize {
2128    specs().lock().unwrap_or_else(|e| e.into_inner()).len()
2129}
2130
2131static OBJECT_SITES: AtomicU32 = AtomicU32::new(0);
2132
2133/// Records that an `obj.randomize()` call site was lowered: it reads the class
2134/// programs registered with [`register_program`], which exist only in the
2135/// process that lowered the design.
2136pub fn note_object_site() {
2137    OBJECT_SITES.fetch_add(1, Ordering::Relaxed);
2138}
2139
2140/// How many `obj.randomize()` call sites this process has lowered
2141/// ([`note_object_site`]).
2142pub fn object_site_count() -> u32 {
2143    OBJECT_SITES.load(Ordering::Relaxed)
2144}
2145
2146/// How many words of captured values a call of spec `id` passes (`0` for no spec
2147/// or an unknown id).
2148pub fn spec_capture_words(id: u32) -> usize {
2149    spec(id).map_or(0, |s| s.capture_words())
2150}
2151
2152/// The captured values `words` (packed per [`Capture::words`]) as the constants
2153/// of the fragment's states. An unknown (X/Z) bit in a captured local is a
2154/// [`Outcome::StateUnknown`]: a 4-state value in a constraint is an error (LRM
2155/// 18.3), and the local was named by the constraint.
2156fn capture_consts(spec: &Spec, words: &[u64]) -> Result<Vec<Option<BigUint>>, Outcome> {
2157    let mut consts: Vec<Option<BigUint>> = vec![None; spec.frag.states.len()];
2158    let mut at = 0;
2159    for c in &spec.captures {
2160        let n = c.words();
2161        let chunk = words.get(at..at + n).ok_or(Outcome::Failed)?;
2162        at += n;
2163        let (val, unk) = unpack(chunk, c.width);
2164        if !unk.is_zero() {
2165            return Err(Outcome::StateUnknown);
2166        }
2167        *consts.get_mut(c.state as usize).ok_or(Outcome::Failed)? = Some(val);
2168    }
2169    Ok(consts)
2170}
2171
2172/// `obj.constraint_mode(on)` with no block named (LRM 18.9): every non-`static`
2173/// constraint block of `h`'s dynamic class. The `static` blocks live in the
2174/// class's static-property object, one per class, so the call site addresses
2175/// those itself. `Outcome::DeadHandle` for a null or stale `h`,
2176/// `Outcome::NoProgram` for a class whose program could not be built.
2177pub fn set_all_constraint_modes(h: u64, on: bool) -> Outcome {
2178    let Some(template) = objects::dynamic_template(h) else {
2179        return Outcome::DeadHandle;
2180    };
2181    let Some(entry) = program(template) else {
2182        return Outcome::NoProgram;
2183    };
2184    let Ok(prog) = entry.as_ref() else {
2185        return Outcome::NoProgram;
2186    };
2187    for b in prog.blocks.iter().filter(|b| b.home == Home::Instance) {
2188        if !objects::set_constraint_mode_of(h, b.ordinal, on) {
2189            return Outcome::DeadHandle;
2190        }
2191    }
2192    Outcome::Solved
2193}
2194
2195/// `obj.randomize()` for the object `h` (module docs); `statics` are the
2196/// `(id, handle)` pairs of the static-property objects the call site reaches
2197/// ([`Home::Static`]).
2198pub fn randomize(h: u64, statics: &[(u32, u64)]) -> Outcome {
2199    randomize_spec(h, statics, 0, &[])
2200}
2201
2202/// `obj.randomize(...)` with the additions of spec `spec_id` (`0`: none);
2203/// `captures` are the packed values of the locals it captured
2204/// ([`Spec::captures`]).
2205pub fn randomize_spec(h: u64, statics: &[(u32, u64)], spec_id: u32, captures: &[u64]) -> Outcome {
2206    let Some(template) = objects::dynamic_template(h) else {
2207        return Outcome::DeadHandle;
2208    };
2209    let Some(base) = program(template) else {
2210        return Outcome::NoProgram;
2211    };
2212    let Ok(base_prog) = base.as_ref() else {
2213        return Outcome::NoProgram;
2214    };
2215    let Some(meta) = objects::rand_meta(h) else {
2216        return Outcome::DeadHandle;
2217    };
2218    let mut rand_mode = meta.rand_mode;
2219    let entry = if spec_id == 0 {
2220        base.clone()
2221    } else {
2222        let Some(spec) = spec(spec_id) else {
2223            return Outcome::Failed;
2224        };
2225        let consts = match capture_consts(&spec, captures) {
2226            Ok(c) => c,
2227            Err(o) => return o,
2228        };
2229        let mut p = match joint::overlay(base_prog, &spec.frag, &consts) {
2230            Ok(p) => p,
2231            Err(o) => return o,
2232        };
2233        if let Some(only) = &spec.only
2234            && let Err(o) = joint::restrict(&mut p, &mut rand_mode, only)
2235        {
2236            return o;
2237        }
2238        Arc::new(Ok(p))
2239    };
2240    let Ok(prog) = entry.as_ref() else {
2241        return Outcome::NoProgram;
2242    };
2243    // An element switched off alone with `obj.a[i].rand_mode(0)` is state.
2244    let entry = match joint::apply_leaf_modes(prog, &mut rand_mode, &meta.leaf_off, &meta.leaf_on) {
2245        Some(p) => Arc::new(Ok(p)),
2246        None => entry,
2247    };
2248    let Ok(prog) = entry.as_ref() else {
2249        return Outcome::NoProgram;
2250    };
2251    // A `static` variable switched off with `rand_mode(0)` (through any
2252    // instance, LRM 18.8) is state for this call.
2253    let entry = match joint::apply_static_modes(prog, &mut rand_mode, statics) {
2254        Some(p) => Arc::new(Ok(p)),
2255        None => entry,
2256    };
2257    let Ok(prog) = entry.as_ref() else {
2258        return Outcome::NoProgram;
2259    };
2260    // The objects randomized together with `h` (LRM 18.5.8): `h` alone unless
2261    // it holds an active, non-null `rand` object member.
2262    let closure = match joint::closure(h, &entry, &rand_mode, statics) {
2263        Ok(c) => c,
2264        Err(o) => return o,
2265    };
2266    if closure.members.len() == 1 {
2267        // A handle that leads back to `h` (`me.x`) reads one of its own
2268        // random variables; only the merge turns that read into the variable.
2269        match joint::self_alias(&closure, prog, statics) {
2270            Ok(joint::SelfAlias::None) => return run(prog, h, &rand_mode, statics),
2271            Ok(joint::SelfAlias::Integral) => {}
2272            Err(o) => return o,
2273        }
2274    }
2275    match joint::merge(&closure, statics) {
2276        Ok(m) => run(&m.program, h, &m.rand_mode, statics),
2277        Err(o) => o,
2278    }
2279}
2280
2281/// The template of the scratch object a scope randomize solves in: it has no
2282/// class, no `rand` properties and no program, only field storage for the
2283/// variables (see [`randomize_scope`]).
2284const SCOPE_TEMPLATE: ClassTemplateId = ClassTemplateId(u32::MAX - 1);
2285
2286/// `std::randomize(v, ...)` (LRM 1800-2023 18.12): solves the spec `spec_id`
2287/// registered for the call site. Its variables belong to no object: the spec's
2288/// fragment places variable `i` at words of a scratch area, which is solved in
2289/// a temporary object and copied to `out` only when the solve succeeds (18.6.1:
2290/// a failed call changes nothing). `captures` are the packed values of the
2291/// locals the constraint block reads, as for [`randomize_spec`]; `seed` is the
2292/// state of the generator the draw uses (the calling process's, LRM 18.13).
2293/// With no variable the call is a checker (18.12): nothing is drawn or
2294/// written, and [`Outcome::Solved`] means every constraint held.
2295pub fn randomize_scope(spec_id: u32, captures: &[u64], out: &mut [u64], seed: u64) -> Outcome {
2296    let Some(spec) = spec(spec_id) else {
2297        return Outcome::Failed;
2298    };
2299    // a call site whose buffers do not match its spec is a compiler bug
2300    if out.len() != spec.scope_words() || captures.len() != spec.capture_words() {
2301        return Outcome::Failed;
2302    }
2303    let consts = match capture_consts(&spec, captures) {
2304        Ok(c) => c,
2305        Err(o) => return o,
2306    };
2307    let prog = match joint::overlay(&Program::default(), &spec.frag, &consts) {
2308        Ok(p) => p,
2309        Err(o) => return o,
2310    };
2311    let h = objects::alloc(SCOPE_TEMPLATE, out.len());
2312    objects::set_prng_seed(h, seed);
2313    let slot = FieldSlot {
2314        offset: 0,
2315        words: out.len(),
2316    };
2317    // The caller's current values go in first: an aggregate's non-random
2318    // members (a `string`) are not solved and must come back unchanged.
2319    if !out.is_empty() && !objects::field_set(h, slot, out) {
2320        objects::release(h);
2321        return Outcome::Failed;
2322    }
2323    let outcome = run(&prog, h, &[], &[]);
2324    if outcome == Outcome::Solved && !out.is_empty() && !objects::field_get(h, slot, out) {
2325        objects::release(h);
2326        return Outcome::Failed;
2327    }
2328    objects::release(h);
2329    outcome
2330}
2331
2332#[cfg(test)]
2333mod tests {
2334    use super::*;
2335    use crate::constraints::ir::BinOp;
2336    use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
2337
2338    pub(super) fn u(w: u32) -> Ty {
2339        Ty::unsigned(w)
2340    }
2341    pub(super) fn var(i: u32, t: Ty) -> Expr {
2342        Expr::Var(i, t)
2343    }
2344    pub(super) fn k(t: Ty, v: u64) -> Expr {
2345        Expr::constant(t, v)
2346    }
2347    pub(super) fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
2348        Expr::Binary(op, Box::new(a), Box::new(b), t)
2349    }
2350
2351    /// `randomize` on a call site that reaches no static-property object.
2352    fn randomize(h: u64) -> Outcome {
2353        super::randomize(h, &[])
2354    }
2355
2356    /// Each test uses its own template id: the registries are process-wide
2357    /// and `cargo test` is concurrent. A template has `n` 8-bit rand fields
2358    /// (`x0`, `x1`, ...) at words `0..n`, then one 8-bit state field `s` at
2359    /// word `n`.
2360    pub(super) fn setup(id: u32, n: usize, conjuncts: Vec<Expr>) -> (ClassTemplateId, u64) {
2361        setup_with_softs(id, n, conjuncts, Vec::new())
2362    }
2363
2364    fn setup_with_softs(
2365        id: u32,
2366        n: usize,
2367        conjuncts: Vec<Expr>,
2368        softs: Vec<Soft>,
2369    ) -> (ClassTemplateId, u64) {
2370        let t = ClassTemplateId(id);
2371        let vars = (0..n)
2372            .map(|i| ProgVar {
2373                ty: u(8),
2374                slot: FieldSlot {
2375                    offset: i,
2376                    words: 1,
2377                },
2378                home: Home::Instance,
2379                prop_index: Some(i),
2380                domain_conjunct: None,
2381                randc: false,
2382                window: None,
2383            })
2384            .collect();
2385        register_rand_props(
2386            t,
2387            (0..n)
2388                .map(|i| RandProp {
2389                    slot: FieldSlot {
2390                        offset: i,
2391                        words: 1,
2392                    },
2393                    kind: RandKind::Rand,
2394                })
2395                .collect(),
2396        );
2397        register_program(
2398            t,
2399            Ok(Program {
2400                vars,
2401                states: vec![ProgState {
2402                    class_of: false,
2403                    root_bit: 0,
2404                    ty: u(8),
2405                    slot: FieldSlot {
2406                        offset: n,
2407                        words: 1,
2408                    },
2409                    window: None,
2410                    home: Home::Instance,
2411                    via: Vec::new(),
2412                }],
2413                conjuncts,
2414                before: Vec::new(),
2415                dists: Vec::new(),
2416                softs,
2417                ..Program::default()
2418            }),
2419        );
2420        let h = alloc(t, n + 1);
2421        objects::set_prng_seed(h, 0x1234_5678);
2422        (t, h)
2423    }
2424
2425    /// A soft constraint on `x0` at priority `seq` (later is higher).
2426    fn soft_on_x0(seq: u32) -> Soft {
2427        Soft {
2428            priority: priority::Priority {
2429                object: 0,
2430                level: 0,
2431                seq,
2432            },
2433            dists: Vec::new(),
2434            keys: vec![priority::SoftKey::Var(0)],
2435            disabled: false,
2436            disabled_by: Vec::new(),
2437        }
2438    }
2439
2440    #[test]
2441    fn soft_constraints_hold_unless_a_hard_or_higher_soft_conflicts() {
2442        // soft x0 == 5 (low), soft x0 == 6 (high): the higher one wins.
2443        let eq = |v: u64| bin(BinOp::Eq, var(0, u(8)), k(u(8), v), Ty::BIT);
2444        let c = vec![priority::wrap_soft(0, eq(5)), priority::wrap_soft(1, eq(6))];
2445        let (_, h) = setup_with_softs(0x7000_0f01, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2446        for _ in 0..20 {
2447            assert_eq!(randomize(h), Outcome::Solved);
2448            assert_eq!(get(h, 0), 6);
2449        }
2450        release(h);
2451
2452        // A hard x0 in [1, 3] discards both; the result is still random over the hard range.
2453        let c = vec![
2454            bin(BinOp::Ge, var(0, u(8)), k(u(8), 1), Ty::BIT),
2455            bin(BinOp::Le, var(0, u(8)), k(u(8), 3), Ty::BIT),
2456            priority::wrap_soft(0, eq(5)),
2457            priority::wrap_soft(1, eq(6)),
2458        ];
2459        let (_, h) = setup_with_softs(0x7000_0f02, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2460        let mut seen = std::collections::BTreeSet::new();
2461        for _ in 0..100 {
2462            assert_eq!(randomize(h), Outcome::Solved);
2463            seen.insert(get(h, 0));
2464        }
2465        assert_eq!(seen.len(), 3, "{seen:?}");
2466        release(h);
2467
2468        // A hard contradiction stays a failure whatever the softs say.
2469        let c = vec![
2470            bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2471            bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2472            priority::wrap_soft(0, eq(4)),
2473        ];
2474        let (_, h) = setup_with_softs(0x7000_0f03, 1, c, vec![soft_on_x0(0)]);
2475        set(h, 0, 0x42);
2476        assert_eq!(randomize(h), Outcome::Unsatisfiable);
2477        assert_eq!(get(h, 0), 0x42, "a failed randomize writes nothing");
2478        release(h);
2479    }
2480
2481    pub(super) fn get(h: u64, off: usize) -> u64 {
2482        let mut b = [0u64];
2483        assert!(objects::field_get(
2484            h,
2485            FieldSlot {
2486                offset: off,
2487                words: 1
2488            },
2489            &mut b
2490        ));
2491        b[0]
2492    }
2493    fn set(h: u64, off: usize, v: u64) {
2494        assert!(objects::field_set(
2495            h,
2496            FieldSlot {
2497                offset: off,
2498                words: 1
2499            },
2500            &[v]
2501        ));
2502    }
2503
2504    #[test]
2505    fn solves_writes_two_state_values_and_advances_the_seed() {
2506        // x0 in [10, 12].
2507        let c = vec![
2508            bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2509            bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2510        ];
2511        let (_, h) = setup(0x7000_0001, 1, c);
2512        let before = objects::prng_seed(h);
2513        let mut seen = std::collections::BTreeSet::new();
2514        for _ in 0..200 {
2515            assert_eq!(randomize(h), Outcome::Solved);
2516            let v = get(h, 0);
2517            assert!((10..=12).contains(&v), "{v}");
2518            seen.insert(v);
2519        }
2520        assert_eq!(seen.len(), 3, "all three legal values are reachable");
2521        assert_ne!(objects::prng_seed(h), before);
2522        release(h);
2523    }
2524
2525    #[test]
2526    fn failure_writes_nothing_and_consumes_no_draws() {
2527        // x0 > 5 && x0 < 3 is unsatisfiable.
2528        let c = vec![
2529            bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2530            bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2531        ];
2532        let (_, h) = setup(0x7000_0002, 2, c);
2533        set(h, 0, 0x77);
2534        set(h, 1, 0x88);
2535        let seed = objects::prng_seed(h);
2536        assert_eq!(randomize(h), Outcome::Unsatisfiable);
2537        assert_eq!((get(h, 0), get(h, 1)), (0x77, 0x88), "values unchanged");
2538        assert_eq!(objects::prng_seed(h), seed, "stream not perturbed");
2539        release(h);
2540    }
2541
2542    #[test]
2543    fn determinism_by_seed() {
2544        let (_, a) = setup(0x7000_0003, 2, vec![]);
2545        let b = alloc(ClassTemplateId(0x7000_0003), 3);
2546        objects::set_prng_seed(b, 0x1234_5678);
2547        for _ in 0..5 {
2548            assert_eq!(randomize(a), Outcome::Solved);
2549            assert_eq!(randomize(b), Outcome::Solved);
2550            assert_eq!((get(a, 0), get(a, 1)), (get(b, 0), get(b, 1)));
2551        }
2552        objects::set_prng_seed(b, 99);
2553        let mut diff = false;
2554        for _ in 0..8 {
2555            randomize(a);
2556            randomize(b);
2557            diff |= get(a, 0) != get(b, 0);
2558        }
2559        assert!(diff, "different seeds give different streams");
2560        release(a);
2561        release(b);
2562    }
2563
2564    #[test]
2565    fn rand_mode_off_variable_is_state_not_solved() {
2566        // x0 + x1 == 100 with x0 inactive at 30: x1 must be 70, x0 untouched.
2567        let c = vec![bin(
2568            BinOp::Eq,
2569            bin(BinOp::Add, var(0, u(8)), var(1, u(8)), u(8)),
2570            k(u(8), 100),
2571            Ty::BIT,
2572        )];
2573        let (_, h) = setup(0x7000_0004, 2, c);
2574        set(h, 0, 30);
2575        let mut m = objects::rand_meta(h).unwrap();
2576        m.rand_mode[0] = false;
2577        objects::set_rand_meta(h, m);
2578        for _ in 0..20 {
2579            assert_eq!(randomize(h), Outcome::Solved);
2580            assert_eq!((get(h, 0), get(h, 1)), (30, 70));
2581        }
2582        // An inactive variable pinned outside the constraint makes the
2583        // problem unsatisfiable: with nothing left to solve it is a checker.
2584        let c2 = vec![bin(BinOp::Lt, var(0, u(8)), k(u(8), 10), Ty::BIT)];
2585        let (_, h2) = setup(0x7000_0005, 1, c2);
2586        set(h2, 0, 200);
2587        let mut m = objects::rand_meta(h2).unwrap();
2588        m.rand_mode[0] = false;
2589        objects::set_rand_meta(h2, m);
2590        assert_eq!(randomize(h2), Outcome::Unsatisfiable, "acts as a checker");
2591        assert_eq!(get(h2, 0), 200);
2592        release(h);
2593        release(h2);
2594    }
2595
2596    #[test]
2597    fn all_variables_inactive_checks_the_constraints() {
2598        let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 5), Ty::BIT)];
2599        let (_, h) = setup(0x7000_0006, 1, c);
2600        let mut m = objects::rand_meta(h).unwrap();
2601        m.rand_mode[0] = false;
2602        objects::set_rand_meta(h, m);
2603        set(h, 0, 5);
2604        assert_eq!(randomize(h), Outcome::Solved);
2605        set(h, 0, 6);
2606        assert_eq!(randomize(h), Outcome::Unsatisfiable);
2607        release(h);
2608    }
2609
2610    /// XC-059: one property (word 0, a 24-bit value then its 24 unknown bits)
2611    /// holding three byte leaves: `lo` (bit 0) and `hi` (bit 16) are solved
2612    /// (`lo` in 10..=12, `hi == lo + 1`) or read as state, and the middle byte
2613    /// (X-filled) is never touched.
2614    fn windowed(id: u32, randc_hi: bool) -> (ClassTemplateId, u64) {
2615        let t = ClassTemplateId(id);
2616        let slot = FieldSlot {
2617            offset: 0,
2618            words: 1,
2619        };
2620        register_rand_props(
2621            t,
2622            vec![RandProp {
2623                slot,
2624                kind: RandKind::Rand,
2625            }],
2626        );
2627        let leaf = |bit| {
2628            Some(Window {
2629                bit,
2630                width: 8,
2631                field_width: 24,
2632            })
2633        };
2634        let c = vec![
2635            bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2636            bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2637            bin(
2638                BinOp::Eq,
2639                var(1, u(8)),
2640                bin(BinOp::Add, var(0, u(8)), k(u(8), 1), u(8)),
2641                Ty::BIT,
2642            ),
2643        ];
2644        let mk = |bit, randc| ProgVar {
2645            ty: u(8),
2646            slot,
2647            home: Home::Instance,
2648            prop_index: Some(0),
2649            domain_conjunct: None,
2650            randc,
2651            window: leaf(bit),
2652        };
2653        register_program(
2654            t,
2655            Ok(Program {
2656                vars: vec![mk(0, false), mk(16, randc_hi)],
2657                conjuncts: c,
2658                ..Program::default()
2659            }),
2660        );
2661        let h = alloc(t, 2);
2662        // The middle byte is 0x5a with all eight of its bits unknown.
2663        set(h, 0, (0x5au64 << 8) | (0xffu64 << (24 + 8)));
2664        set(h, 1, 0xdead);
2665        (t, h)
2666    }
2667
2668    #[test]
2669    fn leaf_windows_of_one_property_are_solved_and_written_in_place() {
2670        let (_, h) = windowed(0x7000_0f10, false);
2671        let mut seen = std::collections::BTreeSet::new();
2672        for _ in 0..60 {
2673            assert_eq!(randomize(h), Outcome::Solved);
2674            let w = get(h, 0);
2675            let (lo, mid, hi) = (w & 0xff, (w >> 8) & 0xff, (w >> 16) & 0xff);
2676            assert!((10..=12).contains(&lo), "{lo}");
2677            assert_eq!(hi, lo + 1);
2678            assert_eq!(mid, 0x5a, "the neighbouring leaf keeps its value");
2679            assert_eq!(w >> 24, 0xff << 8, "the neighbour's unknown bits survive");
2680            assert_eq!(get(h, 1), 0xdead, "the next property is untouched");
2681            seen.insert(lo);
2682        }
2683        assert_eq!(seen.len(), 3, "all three legal values are reachable");
2684        release(h);
2685    }
2686
2687    /// An inactive leaf (its property's `rand_mode` off) is state read through
2688    /// its window: it pins the other leaf; one holding X is a loud error.
2689    #[test]
2690    fn an_inactive_leaf_is_read_through_its_window() {
2691        let (_, h) = windowed(0x7000_0f11, false);
2692        let mut m = objects::rand_meta(h).unwrap();
2693        m.rand_mode[0] = false;
2694        objects::set_rand_meta(h, m);
2695        // lo = 11 (known), hi = 12: the constraints hold, nothing is written.
2696        set(h, 0, 11 | (0x5 << 8) | (12 << 16));
2697        assert_eq!(randomize(h), Outcome::Solved);
2698        assert_eq!(get(h, 0), 11 | (0x5 << 8) | (12 << 16));
2699        // hi = 13 breaks `hi == lo + 1`: the call checks, and fails.
2700        set(h, 0, 11 | (13 << 16));
2701        assert_eq!(randomize(h), Outcome::Unsatisfiable);
2702        // An X bit in `hi` (unknown bit 16 of the 24-bit value) is an error;
2703        // an X bit in the unread middle byte is not.
2704        set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 16)));
2705        assert_eq!(randomize(h), Outcome::StateUnknown);
2706        set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 8)));
2707        assert_eq!(randomize(h), Outcome::Solved);
2708        release(h);
2709    }
2710
2711    /// Two leaves of one property are two `randc` cycles, keyed apart.
2712    #[test]
2713    fn randc_cycles_of_two_leaves_of_one_property_are_independent() {
2714        let (_, h) = windowed(0x7000_0f12, true);
2715        assert_eq!(randomize(h), Outcome::Solved);
2716        let hi_key = randc_key(
2717            FieldSlot {
2718                offset: 0,
2719                words: 1,
2720            },
2721            Some(Window {
2722                bit: 16,
2723                width: 8,
2724                field_width: 24,
2725            }),
2726        );
2727        assert_ne!(hi_key, 0, "a leaf's key differs from its property's");
2728        assert!(objects::randc_state(h, hi_key).unwrap().live);
2729        assert!(!objects::randc_state(h, 0).unwrap().live);
2730        // `hi == lo + 1` over lo in 10..=12 leaves three values: one cycle.
2731        let mut cycle = vec![get(h, 0) >> 16 & 0xff];
2732        for _ in 0..2 {
2733            assert_eq!(randomize(h), Outcome::Solved);
2734            cycle.push(get(h, 0) >> 16 & 0xff);
2735        }
2736        cycle.sort_unstable();
2737        assert_eq!(cycle, vec![11, 12, 13]);
2738        release(h);
2739    }
2740
2741    #[test]
2742    fn enum_domain_dropped_only_while_inactive() {
2743        // x0 in {1, 3} (the domain conjunct, index 0).
2744        let dom = bin(
2745            BinOp::LogOr,
2746            bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2747            bin(BinOp::Eq, var(0, u(8)), k(u(8), 3), Ty::BIT),
2748            Ty::BIT,
2749        );
2750        let (t, h) = setup(0x7000_0007, 1, vec![dom]);
2751        let mut p = program(t).unwrap().as_ref().clone().unwrap();
2752        p.vars[0].domain_conjunct = Some(0);
2753        register_program(t, Ok(p));
2754        for _ in 0..20 {
2755            assert_eq!(randomize(h), Outcome::Solved);
2756            assert!(matches!(get(h, 0), 1 | 3));
2757        }
2758        // Inactive: the current (non-domain) value is kept and no error.
2759        set(h, 0, 2);
2760        let mut m = objects::rand_meta(h).unwrap();
2761        m.rand_mode[0] = false;
2762        objects::set_rand_meta(h, m);
2763        assert_eq!(randomize(h), Outcome::Solved);
2764        assert_eq!(get(h, 0), 2);
2765        release(h);
2766    }
2767
2768    #[test]
2769    fn x_state_is_a_loud_error_and_changes_nothing() {
2770        // x0 == s, with s holding an X bit (unk bit 8 set in the packed word).
2771        let c = vec![bin(BinOp::Eq, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT)];
2772        let (_, h) = setup(0x7000_0008, 1, c);
2773        set(h, 0, 9);
2774        set(h, 1, 1 << 8);
2775        let seed = objects::prng_seed(h);
2776        assert_eq!(randomize(h), Outcome::StateUnknown);
2777        assert_eq!(get(h, 0), 9);
2778        assert_eq!(objects::prng_seed(h), seed);
2779        // A known state works.
2780        set(h, 1, 0x42);
2781        assert_eq!(randomize(h), Outcome::Solved);
2782        assert_eq!(get(h, 0), 0x42);
2783        release(h);
2784    }
2785
2786    #[test]
2787    fn unreferenced_x_state_is_not_an_error() {
2788        let (_, h) = setup(0x7000_0009, 1, vec![]);
2789        set(h, 1, 1 << 8);
2790        assert_eq!(randomize(h), Outcome::Solved);
2791        release(h);
2792    }
2793
2794    #[test]
2795    fn dead_handle_missing_program_and_failed_program() {
2796        assert_eq!(randomize(0), Outcome::DeadHandle);
2797        let t = ClassTemplateId(0x7000_000a);
2798        let h = alloc(t, 1);
2799        assert_eq!(randomize(h), Outcome::NoProgram, "nothing registered");
2800        register_program(t, Err("unsupported thing".into()));
2801        assert_eq!(randomize(h), Outcome::NoProgram, "build failure");
2802        assert_eq!(
2803            program(t).unwrap().as_ref().clone(),
2804            Err("unsupported thing".to_string())
2805        );
2806        release(h);
2807        assert_eq!(randomize(h), Outcome::DeadHandle, "stale handle");
2808    }
2809
2810    #[test]
2811    fn four_state_field_round_trip_packs_value_low_unknown_high() {
2812        // 40-bit field: 80 packed bits over two words.
2813        let (v, x) = unpack(&[0xde_adbe_ef12, 0], 40);
2814        assert_eq!(v, BigUint::from(0xde_adbe_ef12u64));
2815        assert!(x.is_zero());
2816        let (_, x) = unpack(&[0, 1 << 8], 40);
2817        assert!(!x.is_zero(), "bit 72 of the packed value is unknown bit 32");
2818        assert_eq!(pack(&BigUint::from(7u8), 2), Some(vec![7, 0]));
2819        assert_eq!(pack(&(BigUint::from(1u8) << 70), 1), None);
2820    }
2821
2822    /// `x0 < 2 && (x0 == 1 -> x1 == 0)` over two 8-bit variables with the
2823    /// given `before` edges: 257 models, only one of them with `x0 == 1`.
2824    fn skewed(id: u32, before: Vec<(u32, u32)>) -> (ClassTemplateId, u64) {
2825        let c = vec![
2826            bin(BinOp::Lt, var(0, u(8)), k(u(8), 2), Ty::BIT),
2827            bin(
2828                BinOp::LogImpl,
2829                bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2830                bin(BinOp::Eq, var(1, u(8)), k(u(8), 0), Ty::BIT),
2831                Ty::BIT,
2832            ),
2833        ];
2834        let (t, h) = setup(id, 2, c);
2835        let mut p = program(t).unwrap().as_ref().clone().unwrap();
2836        p.before = before;
2837        register_program(t, Ok(p));
2838        (t, h)
2839    }
2840
2841    fn count_x0_is_one(h: u64, n: u32) -> u32 {
2842        let mut ones = 0;
2843        for _ in 0..n {
2844            assert_eq!(randomize(h), Outcome::Solved);
2845            ones += u32::from(get(h, 0) == 1);
2846        }
2847        ones
2848    }
2849
2850    #[test]
2851    fn solve_before_orders_the_solve_but_not_the_solution_space() {
2852        // Unordered: P(x0 == 1) = 1/257. `solve x0 before x1`: 1/2 (LRM
2853        // 18.5.9 Table 18-2). `solve x1 before x0`: x1 is solved first and
2854        // x1 == 0 is the only value compatible with x0 == 1, but x0 == 0 is
2855        // compatible with every x1, so P(x0 == 1) = P(x1 == 0) * 1/2 ~ 1/512.
2856        let (_, h) = skewed(0x7000_0020, vec![]);
2857        let ones = count_x0_is_one(h, 3000);
2858        assert!(ones < 40, "unordered: {ones}/3000");
2859        let (_, h) = skewed(0x7000_0021, vec![(0, 1)]);
2860        let ones = count_x0_is_one(h, 3000);
2861        assert!((1350..1650).contains(&ones), "x0 before x1: {ones}/3000");
2862        let (_, h) = skewed(0x7000_0022, vec![(1, 0)]);
2863        let ones = count_x0_is_one(h, 3000);
2864        assert!(ones < 40, "x1 before x0: {ones}/3000");
2865    }
2866
2867    #[test]
2868    fn an_edge_through_an_inactive_variable_is_ignored() {
2869        let (_, h) = skewed(0x7000_0023, vec![(0, 1)]);
2870        set(h, 0, 1);
2871        let mut m = objects::rand_meta(h).unwrap();
2872        m.rand_mode[0] = false;
2873        objects::set_rand_meta(h, m);
2874        for _ in 0..20 {
2875            assert_eq!(randomize(h), Outcome::Solved);
2876            assert_eq!(
2877                (get(h, 0), get(h, 1)),
2878                (1, 0),
2879                "x0 is state, x1 forced to 0"
2880            );
2881        }
2882    }
2883
2884    #[test]
2885    fn a_circular_or_out_of_range_order_is_a_failed_program_not_a_guess() {
2886        let (_, h) = skewed(0x7000_0024, vec![(0, 1), (1, 0)]);
2887        let seed = objects::prng_seed(h);
2888        assert_eq!(randomize(h), Outcome::Failed);
2889        assert_eq!(objects::prng_seed(h), seed);
2890        let (_, h) = skewed(0x7000_0025, vec![(0, 9)]);
2891        assert_eq!(randomize(h), Outcome::Failed);
2892    }
2893
2894    #[test]
2895    fn outcome_codes_round_trip_and_every_error_has_a_message() {
2896        for c in 0..=20u64 {
2897            let o = Outcome::from_code(c).unwrap();
2898            assert_eq!(o.code(), c);
2899            assert_eq!(o.error_message().is_some(), c >= 2, "{o:?}");
2900        }
2901        assert_eq!(Outcome::from_code(21), None);
2902    }
2903
2904    #[test]
2905    fn every_sat_fallback_failure_has_its_own_outcome() {
2906        use crate::constraints::bdd::BddBuildError;
2907        let none: &[bool] = &[];
2908        for (e, want) in [
2909            (
2910                SolveError::Build(BddBuildError::TooLarge {
2911                    budget: 1,
2912                    during: "x",
2913                }),
2914                Outcome::TooLarge,
2915            ),
2916            (
2917                SolveError::Build(BddBuildError::CnfTooLarge {
2918                    limit: 1,
2919                    during: "x",
2920                }),
2921                Outcome::TooLarge,
2922            ),
2923            (SolveError::SatTimeout, Outcome::SatTimeout),
2924            (
2925                SolveError::SatUnsupported { what: "x" },
2926                Outcome::SatUnsupported,
2927            ),
2928            (SolveError::SatModelRejected, Outcome::Failed),
2929            (SolveError::SatSolverFailure { what: "x" }, Outcome::Failed),
2930        ] {
2931            assert_eq!(outcome_of(e.clone(), none), want, "{e}");
2932        }
2933        // The messages say which limit was hit.
2934        assert!(
2935            Outcome::TooLarge
2936                .error_message()
2937                .unwrap()
2938                .contains("SAT fallback")
2939        );
2940        assert!(
2941            Outcome::SatTimeout
2942                .error_message()
2943                .unwrap()
2944                .contains("time limit")
2945        );
2946        assert!(
2947            Outcome::SatUnsupported
2948                .error_message()
2949                .unwrap()
2950                .contains("does not support")
2951        );
2952    }
2953
2954    /// `x0 dist { 0 := s, 1 := 1 }` over one 8-bit variable, `s` the state
2955    /// field read as a signed 8-bit weight.
2956    fn dist_program(id: u32) -> (ClassTemplateId, u64) {
2957        use crate::constraints::dist::{Dist, Item, SELECTOR_BASE, Span};
2958        let x = var(0, u(8));
2959        let item = |v: u64, w: Expr| Item {
2960            member: bin(BinOp::Eq, x.clone(), k(u(8), v), Ty::BIT),
2961            ty: u(8),
2962            span: Span::Point(k(u(8), v)),
2963            weight: w,
2964            per_range: false,
2965        };
2966        let d = Dist {
2967            subject_ty: u(8),
2968            plain_var: Some(0),
2969            vars: vec![0],
2970            items: vec![
2971                item(
2972                    0,
2973                    Expr::Resize(Box::new(Expr::State(0, u(8))), Ty::signed(8)),
2974                ),
2975                item(1, k(Ty::signed(8), 1)),
2976            ],
2977            default: None,
2978            subject: None,
2979        };
2980        let (t, h) = setup(id, 1, vec![d.conjunct(SELECTOR_BASE)]);
2981        let mut p = program(t).unwrap().as_ref().clone().unwrap();
2982        p.dists = vec![d];
2983        register_program(t, Ok(p));
2984        (t, h)
2985    }
2986
2987    #[test]
2988    fn a_dist_weight_is_read_from_the_state_at_each_call() {
2989        let (_, h) = dist_program(0x7000_0040);
2990        // LRM 18.5.3: `0 := 3, 1 := 1` is 3:1.
2991        set(h, 1, 3);
2992        let mut zeros = 0;
2993        for _ in 0..2000 {
2994            assert_eq!(randomize(h), Outcome::Solved);
2995            let x = get(h, 0);
2996            assert!(x <= 1, "x = {x}");
2997            zeros += u32::from(x == 0);
2998        }
2999        assert!(
3000            (1400..1600).contains(&zeros),
3001            "x == 0: {zeros}/2000 (want 1500)"
3002        );
3003        // A weight of 0 excludes its value; the weight is re-read.
3004        set(h, 1, 0);
3005        for _ in 0..50 {
3006            assert_eq!(randomize(h), Outcome::Solved);
3007            assert_eq!(get(h, 0), 1);
3008        }
3009    }
3010
3011    #[test]
3012    fn a_negative_dist_weight_fails_the_call_and_changes_nothing() {
3013        let (_, h) = dist_program(0x7000_0041);
3014        set(h, 1, 3);
3015        assert_eq!(randomize(h), Outcome::Solved);
3016        let (before, seed) = (get(h, 0), objects::prng_seed(h));
3017        set(h, 1, 200); // -56 as a signed byte
3018        assert_eq!(randomize(h), Outcome::BadDistWeight);
3019        assert_eq!((get(h, 0), objects::prng_seed(h)), (before, seed));
3020        // An X state weight is the usual state-unknown error.
3021        set(h, 1, 1 << 8);
3022        assert_eq!(randomize(h), Outcome::StateUnknown);
3023    }
3024
3025    #[test]
3026    fn a_dist_on_an_inactive_variable_is_a_membership_test_on_its_value() {
3027        let (_, h) = dist_program(0x7000_0042);
3028        set(h, 1, 3);
3029        let mut m = objects::rand_meta(h).unwrap();
3030        m.rand_mode[0] = false;
3031        objects::set_rand_meta(h, m);
3032        set(h, 0, 1);
3033        assert_eq!(randomize(h), Outcome::Solved);
3034        assert_eq!(get(h, 0), 1);
3035        // 7 is in no item, and a value of weight 0 is excluded too.
3036        set(h, 0, 7);
3037        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3038        set(h, 0, 0);
3039        set(h, 1, 0);
3040        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3041    }
3042
3043    /// LRM 18.5.3: `x dist {[0:3] :/ 8, default :/ 8}` over a byte: the range
3044    /// weighs 8 (2 per value) and the other 252 values share the default's 8,
3045    /// so x <= 3 half of the time and every other draw is one of 4..=255.
3046    #[test]
3047    fn a_dist_default_spreads_its_weight_over_the_values_no_item_names() {
3048        use crate::constraints::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
3049        let x = var(0, u(8));
3050        let d = Dist {
3051            subject_ty: u(8),
3052            plain_var: Some(0),
3053            vars: vec![0],
3054            items: vec![Item {
3055                member: bin(BinOp::Le, x.clone(), k(u(8), 3), Ty::BIT),
3056                ty: u(8),
3057                span: Span::Range {
3058                    lo: Some(k(u(8), 0)),
3059                    hi: Some(k(u(8), 3)),
3060                },
3061                weight: k(u(32), 8),
3062                per_range: true,
3063            }],
3064            default: Some(DefaultWeight {
3065                weight: k(u(32), 8),
3066                per_range: true,
3067            }),
3068            subject: None,
3069        };
3070        let (t, h) = setup(0x7000_0043, 1, vec![d.conjunct(SELECTOR_BASE)]);
3071        let mut p = program(t).unwrap().as_ref().clone().unwrap();
3072        p.dists = vec![d];
3073        register_program(t, Ok(p));
3074        let (mut low, mut distinct) = (0u32, std::collections::BTreeSet::new());
3075        for _ in 0..4000 {
3076            assert_eq!(randomize(h), Outcome::Solved);
3077            let v = get(h, 0);
3078            if v <= 3 {
3079                low += 1;
3080            } else {
3081                distinct.insert(v);
3082            }
3083        }
3084        assert!(
3085            (1850..2150).contains(&low),
3086            "x <= 3: {low}/4000 (want 2000)"
3087        );
3088        assert!(
3089            distinct.len() > 100,
3090            "the default values are spread: {}",
3091            distinct.len()
3092        );
3093    }
3094
3095    /// `randc x0 < limit` (and `x0 >= s`, `s` the state field) with an
3096    /// optional `rand x1 > x0` over a two-variable template.
3097    fn randc_program(id: u32, limit: u64) -> (ClassTemplateId, u64) {
3098        let c = vec![
3099            bin(BinOp::Lt, var(0, u(8)), k(u(8), limit), Ty::BIT),
3100            bin(BinOp::Ge, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT),
3101            bin(BinOp::Gt, var(1, u(8)), var(0, u(8)), Ty::BIT),
3102        ];
3103        let (t, h) = setup(id, 2, c);
3104        let mut p = program(t).unwrap().as_ref().clone().unwrap();
3105        p.vars[0].randc = true;
3106        register_program(t, Ok(p));
3107        (t, h)
3108    }
3109
3110    fn draw_cycle(h: u64, n: usize) -> Vec<u64> {
3111        (0..n)
3112            .map(|_| {
3113                assert_eq!(randomize(h), Outcome::Solved);
3114                get(h, 0)
3115            })
3116            .collect()
3117    }
3118
3119    #[test]
3120    fn randc_visits_every_legal_value_once_per_cycle() {
3121        // LRM 18.4.2: every window of `limit` consecutive calls (cycles are
3122        // aligned: the engine starts one at the first call) is a permutation.
3123        let (_, h) = randc_program(0x7000_0030, 6);
3124        let mut orders = std::collections::BTreeSet::new();
3125        for _ in 0..40 {
3126            let mut cyc = draw_cycle(h, 6);
3127            orders.insert(cyc.clone());
3128            cyc.sort_unstable();
3129            assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
3130        }
3131        assert!(orders.len() > 10, "cycles are shuffled, not a fixed order");
3132        release(h);
3133    }
3134
3135    #[test]
3136    fn randc_state_is_committed_only_when_the_call_succeeds() {
3137        let (_, h) = randc_program(0x7000_0031, 4);
3138        let first = draw_cycle(h, 2);
3139        // s = 200 makes `x0 >= s` unsatisfiable: the call fails and the
3140        // cycle must not move (LRM 18.6.3: a failed call changes nothing).
3141        let saved = objects::randc_state(h, 0).unwrap();
3142        set(h, 2, 200);
3143        let seed = objects::prng_seed(h);
3144        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3145        assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
3146        assert_eq!(objects::prng_seed(h), seed);
3147        set(h, 2, 0);
3148        let rest = draw_cycle(h, 2);
3149        let mut all = [first, rest].concat();
3150        all.sort_unstable();
3151        assert_eq!(all, vec![0, 1, 2, 3], "the interrupted cycle completes");
3152        release(h);
3153    }
3154
3155    #[test]
3156    fn randc_constraint_change_starts_a_new_cycle() {
3157        // LRM 18.4.2: a changed constraint set restarts the permutation.
3158        let (_, h) = randc_program(0x7000_0032, 8);
3159        draw_cycle(h, 3);
3160        set(h, 2, 6); // x0 in {6, 7} now
3161        let next = draw_cycle(h, 2);
3162        let mut sorted = next.clone();
3163        sorted.sort_unstable();
3164        assert_eq!(sorted, vec![6, 7], "{next:?}: a fresh cycle of the new set");
3165        release(h);
3166    }
3167
3168    #[test]
3169    fn randc_interacts_with_rand_without_failing() {
3170        // x1 > x0 with x0 == 5 leaves x1 only 6..=255; every cycle of x0
3171        // still completes and every x1 satisfies the constraint.
3172        let (_, h) = randc_program(0x7000_0033, 6);
3173        for _ in 0..10 {
3174            let mut cyc = Vec::new();
3175            for _ in 0..6 {
3176                assert_eq!(randomize(h), Outcome::Solved);
3177                assert!(get(h, 1) > get(h, 0));
3178                cyc.push(get(h, 0));
3179            }
3180            cyc.sort_unstable();
3181            assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
3182        }
3183        release(h);
3184    }
3185
3186    #[test]
3187    fn an_inactive_randc_variable_is_state_and_keeps_its_cycle() {
3188        let (_, h) = randc_program(0x7000_0034, 4);
3189        draw_cycle(h, 2);
3190        let saved = objects::randc_state(h, 0).unwrap();
3191        set(h, 0, 2);
3192        let mut m = objects::rand_meta(h).unwrap();
3193        m.rand_mode[0] = false;
3194        objects::set_rand_meta(h, m);
3195        for _ in 0..5 {
3196            assert_eq!(randomize(h), Outcome::Solved);
3197            assert_eq!(get(h, 0), 2, "not redrawn");
3198            assert!(get(h, 1) > 2);
3199        }
3200        assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
3201        release(h);
3202    }
3203
3204    /// Instance `x0` made a `static randc` living at word 0 of a separate
3205    /// static-property object (`Home::Static(7)`), shared by every instance.
3206    fn static_randc(id: u32) -> (u64, u64, u64) {
3207        let (t, a) = randc_program(id, 4);
3208        let mut p = program(t).unwrap().as_ref().clone().unwrap();
3209        p.vars[0].home = Home::Static(7);
3210        p.vars[0].prop_index = None;
3211        register_program(t, Ok(p));
3212        let b = alloc(t, 3);
3213        objects::set_prng_seed(b, 0x9999);
3214        let st = alloc(ClassTemplateId(id + 0x100), 1);
3215        (a, b, st)
3216    }
3217
3218    #[test]
3219    fn a_static_randc_cycles_across_all_instances() {
3220        // LRM 18.4.2/8.9: one variable, one cycle, however many objects
3221        // randomize it. Alternating two objects still sees each value once
3222        // per four calls.
3223        let (a, b, st) = static_randc(0x7000_0035);
3224        let pairs = [(7u32, st)];
3225        for _ in 0..10 {
3226            let mut cyc = Vec::new();
3227            for i in 0..4 {
3228                let h = if i % 2 == 0 { a } else { b };
3229                assert_eq!(super::randomize(h, &pairs), Outcome::Solved);
3230                let mut w = [0u64];
3231                assert!(objects::field_get(
3232                    st,
3233                    FieldSlot {
3234                        offset: 0,
3235                        words: 1
3236                    },
3237                    &mut w
3238                ));
3239                cyc.push(w[0]);
3240                assert_eq!(get(h, 0), 0, "the instance's own word is untouched");
3241            }
3242            cyc.sort_unstable();
3243            assert_eq!(cyc, vec![0, 1, 2, 3]);
3244        }
3245        assert_eq!(
3246            objects::randc_state(a, 0).unwrap(),
3247            RandcState::default(),
3248            "the cycle lives in the static object, not the instance"
3249        );
3250        assert_ne!(objects::randc_state(st, 0).unwrap(), RandcState::default());
3251        release(a);
3252        release(b);
3253        release(st);
3254    }
3255
3256    #[test]
3257    fn instance_randc_cycles_are_per_object() {
3258        // The contrast: an instance randc keeps one cycle per object.
3259        let (_, a) = randc_program(0x7000_0036, 4);
3260        let b = alloc(ClassTemplateId(0x7000_0036), 3);
3261        objects::set_prng_seed(b, 0x4242);
3262        for _ in 0..6 {
3263            let mut ca = Vec::new();
3264            let mut cb = Vec::new();
3265            for _ in 0..4 {
3266                assert_eq!(randomize(a), Outcome::Solved);
3267                ca.push(get(a, 0));
3268                assert_eq!(randomize(b), Outcome::Solved);
3269                cb.push(get(b, 0));
3270            }
3271            ca.sort_unstable();
3272            cb.sort_unstable();
3273            assert_eq!((ca, cb), (vec![0, 1, 2, 3], vec![0, 1, 2, 3]));
3274        }
3275        release(a);
3276        release(b);
3277    }
3278
3279    #[test]
3280    fn a_missing_static_object_is_a_loud_error_that_changes_nothing() {
3281        let (a, _, st) = static_randc(0x7000_0037);
3282        let seed = objects::prng_seed(a);
3283        assert_eq!(super::randomize(a, &[]), Outcome::StaticUnavailable);
3284        assert_eq!(super::randomize(a, &[(8, st)]), Outcome::StaticUnavailable);
3285        assert_eq!(objects::prng_seed(a), seed);
3286        assert_eq!(super::randomize(a, &[(7, st)]), Outcome::Solved);
3287        release(a);
3288        release(st);
3289    }
3290
3291    // ---- LRM 18.5.12 guards over object handles ---------------------------
3292    //
3293    // `class D { int x; }  class C { rand int x, y; D a, b; ... }` with the
3294    // template's words: x = 0, y = 1, handle a = 2..4, handle b = 4..6. The
3295    // state variables are `a.x` (0) and `b.x` (1), each read through a handle.
3296
3297    const HANDLE_A: usize = 2;
3298    const HANDLE_B: usize = 4;
3299
3300    fn handle_slot(offset: usize) -> FieldSlot {
3301        FieldSlot { offset, words: 2 }
3302    }
3303
3304    /// A `D` object whose `x` is `x`.
3305    fn d_object(id: u32, x: u64) -> u64 {
3306        let d = alloc(ClassTemplateId(id), 1);
3307        set(d, 0, x);
3308        d
3309    }
3310
3311    fn set_handle(h: u64, offset: usize, target: u64) {
3312        assert!(objects::field_set(h, handle_slot(offset), &[target, 0]));
3313    }
3314
3315    /// The program of the module comment's class `C` with `conjuncts`, and one
3316    /// object of it whose handles `a` and `b` point at `a_x` / `b_x` (`None`
3317    /// = null).
3318    fn guard_class(id: u32, conjuncts: Vec<Expr>, a_x: Option<u64>, b_x: Option<u64>) -> u64 {
3319        let t = ClassTemplateId(id);
3320        let rand_slot = |i: usize| FieldSlot {
3321            offset: i,
3322            words: 1,
3323        };
3324        register_rand_props(
3325            t,
3326            (0..2)
3327                .map(|i| RandProp {
3328                    slot: rand_slot(i),
3329                    kind: RandKind::Rand,
3330                })
3331                .collect(),
3332        );
3333        let state = |root: usize| ProgState {
3334            class_of: false,
3335            root_bit: 0,
3336            ty: u(8),
3337            slot: handle_slot(root),
3338            window: None,
3339            home: Home::Instance,
3340            via: vec![rand_slot(0)],
3341        };
3342        register_program(
3343            t,
3344            Ok(Program {
3345                vars: (0..2)
3346                    .map(|i| ProgVar {
3347                        ty: u(8),
3348                        slot: rand_slot(i),
3349                        home: Home::Instance,
3350                        prop_index: Some(i),
3351                        domain_conjunct: None,
3352                        randc: false,
3353                        window: None,
3354                    })
3355                    .collect(),
3356                states: vec![state(HANDLE_A), state(HANDLE_B)],
3357                conjuncts,
3358                before: Vec::new(),
3359                dists: Vec::new(),
3360                ..Program::default()
3361            }),
3362        );
3363        let h = alloc(t, 6);
3364        objects::set_prng_seed(h, 0x5eed_0018);
3365        if let Some(x) = a_x {
3366            set_handle(h, HANDLE_A, d_object(id + 0x100, x));
3367        }
3368        if let Some(x) = b_x {
3369            set_handle(h, HANDLE_B, d_object(id + 0x200, x));
3370        }
3371        h
3372    }
3373
3374    fn x() -> Expr {
3375        var(0, u(8))
3376    }
3377    fn y() -> Expr {
3378        var(1, u(8))
3379    }
3380    fn a_x() -> Expr {
3381        Expr::State(0, u(8))
3382    }
3383    fn b_x() -> Expr {
3384        Expr::State(1, u(8))
3385    }
3386    fn lt_xy() -> Expr {
3387        bin(BinOp::Lt, x(), y(), Ty::BIT)
3388    }
3389    fn a_gt_b() -> Expr {
3390        bin(BinOp::Gt, a_x(), b_x(), Ty::BIT)
3391    }
3392    fn a_is_5() -> Expr {
3393        bin(BinOp::Eq, a_x(), k(u(8), 5), Ty::BIT)
3394    }
3395    fn or(l: Expr, r: Expr) -> Expr {
3396        bin(BinOp::LogOr, l, r, Ty::BIT)
3397    }
3398    fn and(l: Expr, r: Expr) -> Expr {
3399        bin(BinOp::LogAnd, l, r, Ty::BIT)
3400    }
3401    /// `pred -> x + y == 10`, as the builder emits it.
3402    fn guard_sum(pred: Expr) -> Vec<Expr> {
3403        vec![Expr::Guard(
3404            Box::new(pred),
3405            Box::new(bin(
3406                BinOp::Eq,
3407                bin(BinOp::Add, x(), y(), u(8)),
3408                k(u(8), 10),
3409                Ty::BIT,
3410            )),
3411            Box::new(k(Ty::BIT, 1)),
3412            Ty::BIT,
3413        )]
3414    }
3415
3416    /// 400 draws of `h`: how many have `x + y == 10` (mod 256) and how many
3417    /// have `x < y`; and whether `(x < y) -> (x + y == 10)` held on every one.
3418    fn sum_draws(h: u64) -> (u32, u32, bool) {
3419        let (mut sums, mut lts, mut implied) = (0, 0, true);
3420        for _ in 0..400 {
3421            assert_eq!(randomize(h), Outcome::Solved);
3422            let (x, y) = (get(h, 0) as u8, get(h, 1) as u8);
3423            let sum = x.wrapping_add(y) == 10;
3424            sums += u32::from(sum);
3425            lts += u32::from(x < y);
3426            implied &= x >= y || sum;
3427        }
3428        (sums, lts, implied)
3429    }
3430
3431    /// Every draw satisfies the body, whatever the guard says about `x < y`.
3432    fn assert_body_unconditional(h: u64) {
3433        let (sums, _, _) = sum_draws(h);
3434        assert_eq!(sums, 400, "an unconditional `x + y == 10`");
3435    }
3436
3437    /// The body only binds when `x < y`: it holds then, and a draw with
3438    /// `x >= y` is not forced to satisfy it.
3439    fn assert_body_conditional(h: u64) {
3440        let (sums, lts, implied) = sum_draws(h);
3441        assert!(implied, "(x < y) -> (x + y == 10) always holds");
3442        assert!(lts < 400 && sums < 400, "not unconditional: {sums}/{lts}");
3443    }
3444
3445    /// No constraint at all: the body never binds.
3446    fn assert_eliminated(h: u64) {
3447        let (sums, lts, _) = sum_draws(h);
3448        assert!(sums < 20, "x + y == 10 is a 1/256 accident: {sums}");
3449        assert!((120..280).contains(&lts), "x and y are free: {lts}/400");
3450    }
3451
3452    fn assert_error(h: u64) {
3453        let (seed, x0) = (objects::prng_seed(h), get(h, 0));
3454        assert_eq!(randomize(h), Outcome::GuardError);
3455        assert_eq!((objects::prng_seed(h), get(h, 0)), (seed, x0), "no change");
3456    }
3457
3458    /// LRM 18.5.12 example 1: `(x < y || a.x > b.x || a.x == 5) -> x + y == 10`.
3459    #[test]
3460    fn lrm_example_1_or_guard() {
3461        let c = || guard_sum(or(or(lt_xy(), a_gt_b()), a_is_5()));
3462        // Case 1: a non-null (a.x == 5), b null: `a.x == 5` is TRUE, so the
3463        // constraint is unconditional.
3464        assert_body_unconditional(guard_class(0x7100_0001, c(), Some(5), None));
3465        // Case 2: a null: a.x is ERROR in two operands, TRUE decides nothing.
3466        assert_error(guard_class(0x7100_0002, c(), None, Some(1)));
3467        // Case 3: a.x == 10, b.x == 20: only `x < y` is random.
3468        assert_body_conditional(guard_class(0x7100_0003, c(), Some(10), Some(20)));
3469    }
3470
3471    /// LRM 18.5.12 example 2: `(x < y && a.x > b.x && a.x == 5) -> x + y == 10`.
3472    #[test]
3473    fn lrm_example_2_and_guard() {
3474        let c = || guard_sum(and(and(lt_xy(), a_gt_b()), a_is_5()));
3475        // Case 1: a.x == 6, b null: `a.x == 5` is FALSE, which dominates the
3476        // ERROR of `a.x > b.x`: the constraint is eliminated, with no error.
3477        assert_eliminated(guard_class(0x7100_0011, c(), Some(6), None));
3478        // Case 2: a null: ERROR.
3479        assert_error(guard_class(0x7100_0012, c(), None, Some(1)));
3480        // Case 3: a.x == 5, b.x == 2: conditional on `x < y`.
3481        assert_body_conditional(guard_class(0x7100_0013, c(), Some(5), Some(2)));
3482    }
3483
3484    /// LRM 18.5.12 example 3: `(x < y && (a.x > b.x || a.x == 5)) -> ...`.
3485    #[test]
3486    fn lrm_example_3_nested_guard() {
3487        let c = || guard_sum(and(lt_xy(), or(a_gt_b(), a_is_5())));
3488        // Case 1: b null, a.x == 5: `ERROR || TRUE` is TRUE; conditional.
3489        assert_body_conditional(guard_class(0x7100_0021, c(), Some(5), None));
3490        // Case 2: b null, a.x == 8: `ERROR || FALSE` is ERROR.
3491        assert_error(guard_class(0x7100_0022, c(), Some(8), None));
3492        // Case 3: a null: ERROR.
3493        assert_error(guard_class(0x7100_0023, c(), None, Some(1)));
3494        // Case 4: a.x == 5, b.x == 2: conditional.
3495        assert_body_conditional(guard_class(0x7100_0024, c(), Some(5), Some(2)));
3496    }
3497
3498    /// The else arm of a guard is taken when the predicate is FALSE, and an
3499    /// erroring guard inside the arm that is not taken raises nothing.
3500    #[test]
3501    fn a_false_guard_selects_the_else_arm_and_never_examines_the_other() {
3502        // if (a.x == 5) { if (b.x == 1) x == 9; } else y == 3;
3503        let inner = Expr::Guard(
3504            Box::new(bin(BinOp::Eq, b_x(), k(u(8), 1), Ty::BIT)),
3505            Box::new(bin(BinOp::Eq, x(), k(u(8), 9), Ty::BIT)),
3506            Box::new(k(Ty::BIT, 1)),
3507            Ty::BIT,
3508        );
3509        let c = vec![Expr::Guard(
3510            Box::new(a_is_5()),
3511            Box::new(inner),
3512            Box::new(bin(BinOp::Eq, y(), k(u(8), 3), Ty::BIT)),
3513            Ty::BIT,
3514        )];
3515        // a.x == 6: else arm; the null `b` in the then arm is never read.
3516        let h = guard_class(0x7100_0031, c.clone(), Some(6), None);
3517        for _ in 0..20 {
3518            assert_eq!(randomize(h), Outcome::Solved);
3519            assert_eq!(get(h, 1), 3);
3520        }
3521        // a.x == 5, b null: the then arm runs, and its own guard is ERROR.
3522        assert_error(guard_class(0x7100_0032, c.clone(), Some(5), None));
3523        // a.x == 5, b.x == 1: x == 9.
3524        let h = guard_class(0x7100_0033, c, Some(5), Some(1));
3525        assert_eq!(randomize(h), Outcome::Solved);
3526        assert_eq!(get(h, 0), 9);
3527    }
3528
3529    /// A read through a null handle outside any guard is not an X: it has its
3530    /// own outcome, and an X/Z handle or value keeps `StateUnknown`.
3531    #[test]
3532    fn a_null_handle_outside_a_guard_fails_the_call_with_its_own_outcome() {
3533        let c = vec![bin(BinOp::Eq, x(), a_x(), Ty::BIT)];
3534        let h = guard_class(0x7100_0041, c.clone(), None, None);
3535        let seed = objects::prng_seed(h);
3536        assert_eq!(randomize(h), Outcome::NullHandle);
3537        assert_eq!(objects::prng_seed(h), seed);
3538        // The same constraint with a live handle solves.
3539        let h = guard_class(0x7100_0042, c.clone(), Some(77), None);
3540        assert_eq!(randomize(h), Outcome::Solved);
3541        assert_eq!(get(h, 0), 77);
3542        // A deleted target is as good as null.
3543        let h = guard_class(0x7100_0043, c.clone(), Some(77), None);
3544        let mut buf = [0u64; 2];
3545        assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3546        release(buf[0]);
3547        assert_eq!(randomize(h), Outcome::NullHandle);
3548        // An X bit in the handle is StateUnknown, not a null.
3549        let h = guard_class(0x7100_0044, c, Some(77), None);
3550        assert!(objects::field_set(h, handle_slot(HANDLE_A), &[1, 1]));
3551        assert_eq!(randomize(h), Outcome::StateUnknown);
3552    }
3553
3554    /// A guard whose operand reads an X/Z value is an ERROR too (the value is
3555    /// not null, but it is not a value either).
3556    #[test]
3557    fn an_unknown_value_in_a_guard_is_an_error() {
3558        let h = guard_class(0x7100_0051, guard_sum(a_is_5()), Some(5), None);
3559        // Poison a.x's X bits: the D object is the one `a` designates.
3560        let mut buf = [0u64; 2];
3561        assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3562        assert!(objects::field_set(
3563            buf[0],
3564            FieldSlot {
3565                offset: 0,
3566                words: 1
3567            },
3568            &[5 | (1 << 8)]
3569        ));
3570        assert_error(h);
3571    }
3572
3573    /// A handle chain longer than one hop: `n.n.v` through three objects, the
3574    /// middle link of which is null in the second call.
3575    #[test]
3576    fn a_two_hop_handle_chain_reads_the_far_member_and_fails_on_a_null_link() {
3577        // Node = { handle n (words 0..2), int v (word 2) }.
3578        let node = |id: u32, v: u64| {
3579            let o = alloc(ClassTemplateId(id), 3);
3580            set(o, 2, v);
3581            o
3582        };
3583        let t = ClassTemplateId(0x7100_0061);
3584        register_rand_props(
3585            t,
3586            vec![RandProp {
3587                slot: FieldSlot {
3588                    offset: 0,
3589                    words: 1,
3590                },
3591                kind: RandKind::Rand,
3592            }],
3593        );
3594        let root = |offset| ProgState {
3595            class_of: false,
3596            root_bit: 0,
3597            ty: u(8),
3598            slot: handle_slot(offset),
3599            window: None,
3600            home: Home::Instance,
3601            via: vec![
3602                handle_slot(0),
3603                FieldSlot {
3604                    offset: 2,
3605                    words: 1,
3606                },
3607            ],
3608        };
3609        // x == a.n.v: `a` is the root handle at words 1..3.
3610        register_program(
3611            t,
3612            Ok(Program {
3613                vars: vec![ProgVar {
3614                    ty: u(8),
3615                    slot: FieldSlot {
3616                        offset: 0,
3617                        words: 1,
3618                    },
3619                    home: Home::Instance,
3620                    prop_index: Some(0),
3621                    domain_conjunct: None,
3622                    randc: false,
3623                    window: None,
3624                }],
3625                states: vec![root(1)],
3626                conjuncts: vec![bin(BinOp::Eq, x(), Expr::State(0, u(8)), Ty::BIT)],
3627                before: Vec::new(),
3628                dists: Vec::new(),
3629                ..Program::default()
3630            }),
3631        );
3632        let h = alloc(t, 3);
3633        objects::set_prng_seed(h, 1);
3634        let (n0, n1) = (node(0x7100_0062, 1), node(0x7100_0063, 42));
3635        set_handle(h, 1, n0);
3636        set_handle(n0, 0, n1);
3637        assert_eq!(randomize(h), Outcome::Solved);
3638        assert_eq!(get(h, 0), 42);
3639        set_handle(n0, 0, 0);
3640        assert_eq!(randomize(h), Outcome::NullHandle);
3641    }
3642
3643    // ---- Task C9: real variables ----
3644
3645    use crate::constraints::real_domain::{
3646        CmpOp, RealCond, RealDist, RealDistItem, RealExpr, RealSpan,
3647    };
3648
3649    fn rslot(offset: usize) -> FieldSlot {
3650        FieldSlot { offset, words: 2 }
3651    }
3652    fn rk(x: f64) -> RealExpr {
3653        RealExpr::Const(real_domain::Real::new(x), FloatKind::F64)
3654    }
3655    fn rcmp(op: CmpOp, a: RealExpr, b: RealExpr) -> RealCond {
3656        RealCond::Cmp(op, a, b)
3657    }
3658    fn rget(h: u64, off: usize) -> f64 {
3659        let mut b = [0u64; 2];
3660        assert!(objects::field_get(h, rslot(off), &mut b));
3661        assert_eq!(b[1], 0, "a solved real has no unknown bits");
3662        f64::from_bits(b[0])
3663    }
3664    fn rset_bits(h: u64, off: usize, bits: u64, unk: u64) {
3665        assert!(objects::field_set(h, rslot(off), &[bits, unk]));
3666    }
3667
3668    /// A template with the real rand variable `r` at word 0 (property 0), the
3669    /// 8-bit integral rand variable `x` at word 2 (property 1) and a real state
3670    /// `s` at word 3 (the program's state 0).
3671    fn setup_real(id: u32, real: RealProblem, conjuncts: Vec<Expr>) -> u64 {
3672        let t = ClassTemplateId(id);
3673        register_rand_props(
3674            t,
3675            vec![
3676                RandProp {
3677                    slot: rslot(0),
3678                    kind: RandKind::Rand,
3679                },
3680                RandProp {
3681                    slot: FieldSlot {
3682                        offset: 2,
3683                        words: 1,
3684                    },
3685                    kind: RandKind::Rand,
3686                },
3687            ],
3688        );
3689        register_program(
3690            t,
3691            Ok(Program {
3692                vars: vec![ProgVar {
3693                    ty: u(8),
3694                    slot: FieldSlot {
3695                        offset: 2,
3696                        words: 1,
3697                    },
3698                    home: Home::Instance,
3699                    prop_index: Some(1),
3700                    domain_conjunct: None,
3701                    randc: false,
3702                    window: None,
3703                }],
3704                states: vec![ProgState {
3705                    class_of: false,
3706                    root_bit: 0,
3707                    ty: u(64),
3708                    slot: rslot(3),
3709                    window: None,
3710                    home: Home::Instance,
3711                    via: Vec::new(),
3712                }],
3713                conjuncts,
3714                real_vars: vec![ProgRealVar {
3715                    kind: FloatKind::F64,
3716                    slot: rslot(0),
3717                    home: Home::Instance,
3718                    prop_index: Some(0),
3719                    window: None,
3720                }],
3721                real,
3722                ..Program::default()
3723            }),
3724        );
3725        let h = alloc(t, 5);
3726        objects::set_prng_seed(h, 0x5eed_0c09);
3727        h
3728    }
3729
3730    fn unit_interval() -> RealProblem {
3731        RealProblem {
3732            kinds: vec![FloatKind::F64],
3733            conds: vec![
3734                rcmp(CmpOp::Ge, RealExpr::Var(0), rk(1.0)),
3735                rcmp(CmpOp::Lt, RealExpr::Var(0), rk(2.0)),
3736            ],
3737            ..RealProblem::default()
3738        }
3739    }
3740
3741    #[test]
3742    fn real_variable_is_solved_with_the_integral_ones_and_the_seed_advances() {
3743        // 1.0 <= r < 2.0 alongside x == 7.
3744        let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 7), Ty::BIT)];
3745        let h = setup_real(0x7000_0c01, unit_interval(), c);
3746        let before = objects::prng_seed(h);
3747        let mut bins = [0u32; 4];
3748        for _ in 0..400 {
3749            assert_eq!(randomize(h), Outcome::Solved);
3750            let r = rget(h, 0);
3751            assert!((1.0..2.0).contains(&r), "{r}");
3752            assert_eq!(get(h, 2), 7);
3753            bins[((r - 1.0) * 4.0) as usize] += 1;
3754        }
3755        assert!(
3756            bins.iter().all(|&n| n > 60),
3757            "uniform over the interval: {bins:?}"
3758        );
3759        assert_ne!(objects::prng_seed(h), before);
3760        release(h);
3761    }
3762
3763    #[test]
3764    fn unsatisfiable_real_constraint_writes_nothing_and_keeps_the_seed() {
3765        let mut p = unit_interval();
3766        p.conds.push(rcmp(CmpOp::Gt, RealExpr::Var(0), rk(5.0)));
3767        let h = setup_real(0x7000_0c02, p, Vec::new());
3768        rset_bits(h, 0, 3.25f64.to_bits(), 0);
3769        set(h, 2, 99);
3770        let seed = objects::prng_seed(h);
3771        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3772        assert_eq!(rget(h, 0), 3.25);
3773        assert_eq!(get(h, 2), 99, "the integral variable is untouched too");
3774        assert_eq!(objects::prng_seed(h), seed);
3775        release(h);
3776    }
3777
3778    #[test]
3779    fn inactive_real_variable_is_a_constant_and_is_not_written() {
3780        let h = setup_real(0x7000_0c03, unit_interval(), Vec::new());
3781        let mut m = objects::rand_meta(h).unwrap();
3782        m.rand_mode[0] = false;
3783        objects::set_rand_meta(h, m);
3784        rset_bits(h, 0, 1.5f64.to_bits(), 0);
3785        for _ in 0..10 {
3786            assert_eq!(randomize(h), Outcome::Solved);
3787            assert_eq!(rget(h, 0), 1.5);
3788        }
3789        // Outside the constraint it acts as a checker.
3790        rset_bits(h, 0, 2.0f64.to_bits(), 0);
3791        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3792        assert_eq!(rget(h, 0), 2.0);
3793        // An X bit in it is an error, never a silent 0.0.
3794        rset_bits(h, 0, 0, 1);
3795        assert_eq!(randomize(h), Outcome::StateUnknown);
3796        release(h);
3797    }
3798
3799    #[test]
3800    fn real_state_variable_bounds_the_solution_and_division_by_zero_is_a_loud_error() {
3801        // r < s, with s a real state at word 3.
3802        let p = RealProblem {
3803            kinds: vec![FloatKind::F64],
3804            conds: vec![
3805                rcmp(CmpOp::Ge, RealExpr::Var(0), rk(0.0)),
3806                rcmp(
3807                    CmpOp::Lt,
3808                    RealExpr::Var(0),
3809                    RealExpr::State(0, FloatKind::F64),
3810                ),
3811            ],
3812            ..RealProblem::default()
3813        };
3814        let h = setup_real(0x7000_0c04, p, Vec::new());
3815        rset_bits(h, 3, 0.5f64.to_bits(), 0);
3816        for _ in 0..50 {
3817            assert_eq!(randomize(h), Outcome::Solved);
3818            assert!((0.0..0.5).contains(&rget(h, 0)));
3819        }
3820        // Every comparison with a NaN is false (IEEE 754), so `r < NaN` has no
3821        // solution: that is an ordinary unsatisfiable problem, not an error.
3822        rset_bits(h, 3, f64::NAN.to_bits(), 0);
3823        assert_eq!(randomize(h), Outcome::Unsatisfiable);
3824        rset_bits(h, 3, 0, 1);
3825        assert_eq!(randomize(h), Outcome::StateUnknown);
3826        release(h);
3827
3828        // A division by a state that is 0.0 has no value: a loud error.
3829        let p = RealProblem {
3830            kinds: vec![FloatKind::F64],
3831            conds: vec![rcmp(
3832                CmpOp::Lt,
3833                RealExpr::Div(
3834                    Box::new(RealExpr::Var(0)),
3835                    Box::new(RealExpr::State(0, FloatKind::F64)),
3836                ),
3837                rk(1.0),
3838            )],
3839            ..RealProblem::default()
3840        };
3841        let h = setup_real(0x7000_0c08, p, Vec::new());
3842        rset_bits(h, 3, 2.0f64.to_bits(), 0);
3843        assert_eq!(randomize(h), Outcome::Solved);
3844        assert!(rget(h, 0) < 2.0);
3845        rset_bits(h, 3, 0.0f64.to_bits(), 0);
3846        assert_eq!(randomize(h), Outcome::RealDomain);
3847        release(h);
3848    }
3849
3850    #[test]
3851    fn real_dist_follows_its_weights() {
3852        // r dist { 1.0 := 3, [10.0 : 20.0] :/ 1 }: the point has weight 3 and the
3853        // range a total of 1.
3854        let p = RealProblem {
3855            kinds: vec![FloatKind::F64],
3856            dists: vec![RealDist {
3857                var: 0,
3858                items: vec![
3859                    RealDistItem {
3860                        span: RealSpan::Point(rk(1.0)),
3861                        weight: k(u(32), 3),
3862                    },
3863                    RealDistItem {
3864                        span: RealSpan::Range {
3865                            lo: Some(rk(10.0)),
3866                            hi: Some(rk(20.0)),
3867                        },
3868                        weight: k(u(32), 1),
3869                    },
3870                ],
3871                default: None,
3872            }],
3873            ..RealProblem::default()
3874        };
3875        let h = setup_real(0x7000_0c05, p, Vec::new());
3876        let (mut point, mut range) = (0u32, 0u32);
3877        for _ in 0..2000 {
3878            assert_eq!(randomize(h), Outcome::Solved);
3879            let r = rget(h, 0);
3880            if r == 1.0 {
3881                point += 1;
3882            } else {
3883                assert!((10.0..=20.0).contains(&r), "{r}");
3884                range += 1;
3885            }
3886        }
3887        // Expected 1500 / 500; 5 sigma is about 100.
3888        assert!((1400..=1600).contains(&point), "{point} / {range}");
3889        release(h);
3890    }
3891
3892    #[test]
3893    fn the_integral_solution_is_drawn_before_the_real_one() {
3894        // From the same seed the integral variable gets the same value with or
3895        // without a real problem next to it, and an all-inactive real problem
3896        // draws nothing at all.
3897        let c = vec![bin(BinOp::Ge, var(0, u(8)), k(u(8), 3), Ty::BIT)];
3898        let (_, plain) = setup(0x7000_0c06, 1, c.clone());
3899        let h = setup_real(0x7000_0c07, unit_interval(), c);
3900        objects::set_prng_seed(h, objects::prng_seed(plain).unwrap());
3901        let seed = objects::prng_seed(plain).unwrap();
3902        assert_eq!(randomize(plain), Outcome::Solved);
3903        assert_eq!(randomize(h), Outcome::Solved);
3904        // `setup` puts its variable at word 0, `setup_real` at word 2.
3905        assert_eq!(get(plain, 0), get(h, 2));
3906        assert_ne!(
3907            objects::prng_seed(h),
3908            objects::prng_seed(plain),
3909            "real draws follow"
3910        );
3911        // All real variables inactive: the stream ends where the integral part left it.
3912        let mut m = objects::rand_meta(h).unwrap();
3913        m.rand_mode[0] = false;
3914        objects::set_rand_meta(h, m);
3915        rset_bits(h, 0, 1.5f64.to_bits(), 0);
3916        objects::set_prng_seed(h, seed);
3917        assert_eq!(randomize(h), Outcome::Solved);
3918        assert_eq!(objects::prng_seed(h), objects::prng_seed(plain));
3919        release(plain);
3920        release(h);
3921    }
3922}
3923
3924/// Dynamic arrays and queues in a program (Task C14): sizes, elements, state.
3925#[cfg(test)]
3926mod array_tests {
3927    use super::*;
3928    use crate::collections::{KeyKind, Shape};
3929    use crate::constraints::ir::{ArrayNode, BinOp, LoopId, ReduceOp};
3930    use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
3931
3932    fn u8t() -> Ty {
3933        Ty::unsigned(8)
3934    }
3935    fn s32() -> Ty {
3936        Ty::signed(32)
3937    }
3938    fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
3939        Expr::Binary(op, Box::new(a), Box::new(b), t)
3940    }
3941    fn k(t: Ty, v: u64) -> Expr {
3942        Expr::constant(t, v)
3943    }
3944    fn node(n: ArrayNode) -> Expr {
3945        Expr::Array(n)
3946    }
3947    fn lp(id: LoopId) -> Expr {
3948        node(ArrayNode::Loop(id, s32()))
3949    }
3950    fn elem(a: u32, i: Expr) -> Expr {
3951        node(ArrayNode::Elem {
3952            array: a,
3953            index: Box::new(i),
3954            ty: u8t(),
3955        })
3956    }
3957    fn shape() -> Shape {
3958        Shape {
3959            elem_words: 1,
3960            assoc: false,
3961            key: KeyKind::Int {
3962                width: 32,
3963                signed: false,
3964            },
3965            obj: false,
3966            nested: collections::Nested::NONE,
3967        }
3968    }
3969    fn slot(o: usize) -> FieldSlot {
3970        FieldSlot {
3971            offset: o,
3972            words: 1,
3973        }
3974    }
3975    fn contents(h: u64) -> Vec<u64> {
3976        let mut b = [0u64];
3977        assert!(objects::field_get(h, slot(0), &mut b));
3978        collections::elements(b[0])
3979            .into_iter()
3980            .map(|e| e[0])
3981            .collect()
3982    }
3983
3984    /// `rand byte a[]` at word 0 (property 0) and `rand byte len` at word 1
3985    /// (property 1); the program's constraints are `conjuncts`.
3986    fn setup(id: u32, conjuncts: Vec<Expr>, initial: &[u64]) -> u64 {
3987        let t = ClassTemplateId(id);
3988        register_rand_props(
3989            t,
3990            vec![
3991                RandProp {
3992                    slot: slot(0),
3993                    kind: RandKind::Rand,
3994                },
3995                RandProp {
3996                    slot: slot(1),
3997                    kind: RandKind::Rand,
3998                },
3999            ],
4000        );
4001        register_program(
4002            t,
4003            Ok(Program {
4004                vars: vec![ProgVar {
4005                    ty: u8t(),
4006                    slot: slot(1),
4007                    home: Home::Instance,
4008                    prop_index: Some(1),
4009                    domain_conjunct: None,
4010                    randc: false,
4011                    window: None,
4012                }],
4013                conjuncts,
4014                arrays: vec![ProgArray {
4015                    elem_ty: u8t(),
4016                    elem_words: 1,
4017                    slot: slot(0),
4018                    home: Home::Instance,
4019                    rand: true,
4020                    prop_index: Some(0),
4021                    elem_domain: None,
4022                    assoc: false,
4023                    nested: false,
4024                    template_of: None,
4025                    inner_fixed: 0,
4026                    row_depth: 0,
4027                    window: None,
4028                }],
4029                ..Program::default()
4030            }),
4031        );
4032        let h = alloc(t, 2);
4033        objects::set_prng_seed(h, 0xA11A_5EED);
4034        let c = collections::from_words(&shape(), initial);
4035        assert!(objects::field_set(h, slot(0), &[c]));
4036        h
4037    }
4038
4039    /// LRM 18.4: `data.size == len` with `len inside {[2:4]}` and every
4040    /// element above its index. The array is resized and each element drawn.
4041    #[test]
4042    fn a_size_constraint_resizes_and_randomizes_every_element() {
4043        let size_eq = bin(
4044            BinOp::Eq,
4045            node(ArrayNode::Size(0)),
4046            Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
4047            Ty::BIT,
4048        );
4049        let lo = bin(BinOp::Ge, Expr::Var(0, u8t()), k(u8t(), 2), Ty::BIT);
4050        let hi = bin(BinOp::Le, Expr::Var(0, u8t()), k(u8t(), 4), Ty::BIT);
4051        let each = node(ArrayNode::Foreach {
4052            array: 0,
4053            var: 0,
4054            body: Box::new(bin(
4055                BinOp::Gt,
4056                Expr::Resize(Box::new(elem(0, lp(0))), s32()),
4057                lp(0),
4058                Ty::BIT,
4059            )),
4060        });
4061        let h = setup(0x7a00_0001, vec![size_eq, lo, hi, each], &[9]);
4062        let mut sizes = std::collections::BTreeSet::new();
4063        for _ in 0..60 {
4064            assert_eq!(randomize(h, &[]), Outcome::Solved);
4065            let a = contents(h);
4066            let mut len = [0u64];
4067            assert!(objects::field_get(h, slot(1), &mut len));
4068            assert_eq!(a.len() as u64, len[0], "size == len");
4069            assert!((2..=4).contains(&a.len()));
4070            for (i, v) in a.iter().enumerate() {
4071                assert!(*v > i as u64, "element {i} = {v}");
4072            }
4073            sizes.insert(a.len());
4074        }
4075        assert_eq!(sizes.len(), 3, "every size 2..=4 is reached: {sizes:?}");
4076        release(h);
4077    }
4078
4079    /// An array with no size constraint keeps its size; its elements are
4080    /// still drawn (LRM 18.4).
4081    #[test]
4082    fn an_unsized_array_keeps_its_length_but_gets_new_elements() {
4083        let each = node(ArrayNode::Foreach {
4084            array: 0,
4085            var: 0,
4086            body: Box::new(bin(BinOp::Ge, elem(0, lp(0)), k(u8t(), 200), Ty::BIT)),
4087        });
4088        let h = setup(0x7a00_0002, vec![each], &[1, 2, 3]);
4089        assert_eq!(randomize(h, &[]), Outcome::Solved);
4090        let a = contents(h);
4091        assert_eq!(a.len(), 3);
4092        assert!(a.iter().all(|v| *v >= 200), "{a:?}");
4093        release(h);
4094    }
4095
4096    /// `rand_mode(0)` on the array makes it state: untouched, and its current
4097    /// elements are what the constraints read.
4098    #[test]
4099    fn an_inactive_array_is_state_and_unchanged() {
4100        let sum = node(ArrayNode::Reduce {
4101            op: ReduceOp::Sum,
4102            array: 0,
4103            var: 0,
4104            index_var: 1,
4105            body: Box::new(Expr::Resize(
4106                Box::new(node(ArrayNode::Loop(0, u8t()))),
4107                s32(),
4108            )),
4109        });
4110        // len == a.sum()
4111        let c = bin(
4112            BinOp::Eq,
4113            Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
4114            sum,
4115            Ty::BIT,
4116        );
4117        let h = setup(0x7a00_0003, vec![c], &[3, 4, 5]);
4118        let mut m = objects::rand_meta(h).unwrap();
4119        m.rand_mode[0] = false;
4120        objects::set_rand_meta(h, m);
4121        assert_eq!(randomize(h, &[]), Outcome::Solved);
4122        assert_eq!(contents(h), vec![3, 4, 5]);
4123        let mut len = [0u64];
4124        assert!(objects::field_get(h, slot(1), &mut len));
4125        assert_eq!(len[0], 12);
4126        release(h);
4127    }
4128
4129    /// An element read outside the array is its own loud outcome, and the
4130    /// failed call changes nothing (not the array, not the seed).
4131    #[test]
4132    fn an_out_of_range_element_fails_loudly_and_changes_nothing() {
4133        let c = bin(BinOp::Eq, elem(0, k(s32(), 7)), k(u8t(), 1), Ty::BIT);
4134        let h = setup(0x7a00_0004, vec![c], &[1, 2]);
4135        let seed = objects::prng_seed(h);
4136        assert_eq!(randomize(h, &[]), Outcome::ArrayIndexOutOfRange);
4137        assert_eq!(contents(h), vec![1, 2]);
4138        assert_eq!(objects::prng_seed(h), seed);
4139        release(h);
4140    }
4141
4142    /// Sizes that satisfy the size constraint alone but not the elements are
4143    /// retried: `size inside {[1:3]}` and `a[2] == 5` only works for size 3.
4144    #[test]
4145    fn a_size_that_fails_the_element_constraints_is_retried() {
4146        let lo = bin(BinOp::Ge, node(ArrayNode::Size(0)), k(s32(), 1), Ty::BIT);
4147        let hi = bin(BinOp::Le, node(ArrayNode::Size(0)), k(s32(), 3), Ty::BIT);
4148        // `size > 2 -> a[2] == 5` would be a guard; use the unconditional form
4149        // under a size guard so a smaller size is merely unsatisfiable.
4150        let guarded = Expr::Guard(
4151            Box::new(bin(
4152                BinOp::Gt,
4153                node(ArrayNode::Size(0)),
4154                k(s32(), 2),
4155                Ty::BIT,
4156            )),
4157            Box::new(bin(BinOp::Eq, elem(0, k(s32(), 2)), k(u8t(), 5), Ty::BIT)),
4158            Box::new(k(Ty::BIT, 0)),
4159            Ty::BIT,
4160        );
4161        let h = setup(0x7a00_0005, vec![lo, hi, guarded], &[]);
4162        for _ in 0..20 {
4163            assert_eq!(randomize(h, &[]), Outcome::Solved);
4164            let a = contents(h);
4165            assert_eq!(a.len(), 3);
4166            assert_eq!(a[2], 5);
4167        }
4168        release(h);
4169    }
4170
4171    #[test]
4172    fn an_impossible_size_is_unsatisfiable_not_an_error() {
4173        let c = bin(BinOp::Lt, node(ArrayNode::Size(0)), k(s32(), 0), Ty::BIT);
4174        let h = setup(0x7a00_0006, vec![c], &[1]);
4175        assert_eq!(randomize(h, &[]), Outcome::Unsatisfiable);
4176        assert_eq!(contents(h), vec![1]);
4177        release(h);
4178    }
4179
4180    /// LRM 18.4: "The size and index values of an associative array are not
4181    /// randomizable": a `rand` associative array keeps its keys (and its
4182    /// `default:`), and every value at those keys is drawn again.
4183    #[test]
4184    fn a_rand_associative_array_keeps_its_keys_and_redraws_its_values() {
4185        use crate::collections::Key;
4186        let t = ClassTemplateId(0x7a00_0007);
4187        register_rand_props(
4188            t,
4189            vec![RandProp {
4190                slot: slot(0),
4191                kind: RandKind::Rand,
4192            }],
4193        );
4194        register_program(
4195            t,
4196            Ok(Program {
4197                arrays: vec![ProgArray {
4198                    elem_ty: u8t(),
4199                    elem_words: 1,
4200                    slot: slot(0),
4201                    home: Home::Instance,
4202                    rand: true,
4203                    prop_index: Some(0),
4204                    elem_domain: None,
4205                    assoc: true,
4206                    nested: false,
4207                    template_of: None,
4208                    inner_fixed: 0,
4209                    row_depth: 0,
4210                    window: None,
4211                }],
4212                ..Program::default()
4213            }),
4214        );
4215        let h = alloc(t, 1);
4216        objects::set_prng_seed(h, 0xA550_0001);
4217        let src = collections::frozen_map(
4218            vec![
4219                (Key::Int(7), vec![1]),
4220                (Key::Int(3), vec![1]),
4221                (Key::Int(40), vec![1]),
4222            ],
4223            Some(vec![9]),
4224        );
4225        let c = collections::clone(src);
4226        assert!(objects::field_set(h, slot(0), &[c]));
4227        let mut seen = std::collections::BTreeSet::new();
4228        for _ in 0..30 {
4229            assert_eq!(randomize(h, &[]), Outcome::Solved);
4230            let mut b = [0u64];
4231            assert!(objects::field_get(h, slot(0), &mut b));
4232            assert_eq!(
4233                collections::keys(b[0]),
4234                vec![Key::Int(3), Key::Int(7), Key::Int(40)],
4235                "the keys never change"
4236            );
4237            assert_eq!(collections::size(b[0]), 3);
4238            let mut dflt = [0u64];
4239            assert!(collections::lookup(b[0], &Key::Int(1000), &mut dflt));
4240            assert_eq!(dflt[0], 9, "the default survives");
4241            for e in collections::elements(b[0]) {
4242                seen.insert(e[0]);
4243            }
4244        }
4245        assert!(seen.len() > 20, "the values are redrawn: {seen:?}");
4246        release(h);
4247    }
4248}
4249
4250/// Constraint blocks and rand variables switched off through `constraint_mode()` /
4251/// `rand_mode()` (Task C20; LRM 18.8, 18.9).
4252#[cfg(test)]
4253mod mode_tests {
4254    use super::tests::{bin, get, k, setup, u, var};
4255    use super::*;
4256    use crate::constraints::ir::BinOp;
4257    use crate::objects::{alloc, register_rand_props, release};
4258
4259    fn slot(offset: usize) -> FieldSlot {
4260        FieldSlot { offset, words: 1 }
4261    }
4262
4263    fn randomize(h: u64) -> Outcome {
4264        super::randomize(h, &[])
4265    }
4266
4267    /// Re-registers `t`'s program with `blocks` over its conjuncts.
4268    fn with_blocks(t: ClassTemplateId, blocks: Vec<ProgBlock>) {
4269        let mut p = program(t).unwrap().as_ref().clone().unwrap();
4270        p.blocks = blocks;
4271        register_program(t, Ok(p));
4272    }
4273
4274    fn block(home: Home, ordinal: u32, conjuncts: std::ops::Range<usize>) -> ProgBlock {
4275        ProgBlock {
4276            home,
4277            ordinal,
4278            conjuncts,
4279            items: BlockItems::default(),
4280        }
4281    }
4282
4283    /// LRM 18.9: `constraint_mode(0)` leaves a block out of the solve, and only
4284    /// that block; switching it on again restores it.
4285    #[test]
4286    fn a_constraint_block_switched_off_is_left_out_of_the_solve() {
4287        // block 0: x0 > 200; block 1: x0 < 100: together unsatisfiable.
4288        let c = vec![
4289            bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4290            bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4291        ];
4292        let (t, h) = setup(0x7c00_0001, 1, c);
4293        with_blocks(
4294            t,
4295            vec![
4296                block(Home::Instance, 0, 0..1),
4297                block(Home::Instance, 1, 1..2),
4298            ],
4299        );
4300        assert_eq!(randomize(h), Outcome::Unsatisfiable);
4301        assert!(objects::set_constraint_mode_of(h, 0, false));
4302        for _ in 0..20 {
4303            assert_eq!(randomize(h), Outcome::Solved);
4304            assert!(get(h, 0) < 100, "block 1 still holds");
4305        }
4306        assert!(objects::set_constraint_mode_of(h, 0, true));
4307        assert!(objects::set_constraint_mode_of(h, 1, false));
4308        for _ in 0..20 {
4309            assert_eq!(randomize(h), Outcome::Solved);
4310            assert!(get(h, 0) > 200, "block 0 holds again, block 1 is off");
4311        }
4312        assert!(objects::set_constraint_mode_of(h, 1, true));
4313        assert_eq!(randomize(h), Outcome::Unsatisfiable);
4314        release(h);
4315    }
4316
4317    /// The modes are per object: switching a block off on one instance changes
4318    /// no other.
4319    #[test]
4320    fn constraint_mode_is_per_object() {
4321        let c = vec![
4322            bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4323            bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4324        ];
4325        let (t, a) = setup(0x7c00_0002, 1, c);
4326        with_blocks(
4327            t,
4328            vec![
4329                block(Home::Instance, 0, 0..1),
4330                block(Home::Instance, 1, 1..2),
4331            ],
4332        );
4333        let b = alloc(t, 2);
4334        objects::set_prng_seed(b, 7);
4335        assert!(objects::set_constraint_mode_of(a, 0, false));
4336        assert_eq!(randomize(a), Outcome::Solved);
4337        assert_eq!(randomize(b), Outcome::Unsatisfiable);
4338        release(a);
4339        release(b);
4340    }
4341
4342    /// LRM 18.9: a `static` block's mode lives in the class, so it is read from
4343    /// the static-property object, and every instance sees it.
4344    #[test]
4345    fn a_static_blocks_mode_is_shared_by_every_instance() {
4346        let c = vec![
4347            bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4348            bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4349        ];
4350        let (t, a) = setup(0x7c00_0004, 1, c);
4351        with_blocks(
4352            t,
4353            vec![
4354                block(Home::Static(9), 0, 0..1),
4355                block(Home::Instance, 1, 1..2),
4356            ],
4357        );
4358        let b = alloc(t, 2);
4359        objects::set_prng_seed(b, 11);
4360        let st = alloc(ClassTemplateId(0x7c00_0104), 1);
4361        let pairs = [(9u32, st)];
4362        assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4363        assert!(objects::set_constraint_mode_of(st, 0, false));
4364        assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4365        assert_eq!(super::randomize(b, &pairs), Outcome::Solved);
4366        // A call site that did not pass the static object cannot see its mode.
4367        assert_eq!(super::randomize(a, &[]), Outcome::Unsatisfiable);
4368        release(a);
4369        release(b);
4370        release(st);
4371    }
4372
4373    /// LRM 18.8: a `static` `rand` variable switched off through any instance
4374    /// is state for every instance's call.
4375    #[test]
4376    fn a_static_rand_mode_is_shared_and_makes_the_variable_state() {
4377        let t = ClassTemplateId(0x7c00_0005);
4378        register_rand_props(t, Vec::new());
4379        register_program(
4380            t,
4381            Ok(Program {
4382                vars: vec![ProgVar {
4383                    ty: u(8),
4384                    slot: slot(0),
4385                    home: Home::Static(5),
4386                    prop_index: None,
4387                    domain_conjunct: None,
4388                    randc: false,
4389                    window: None,
4390                }],
4391                conjuncts: vec![bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT)],
4392                ..Program::default()
4393            }),
4394        );
4395        let a = alloc(t, 1);
4396        objects::set_prng_seed(a, 3);
4397        let st = alloc(ClassTemplateId(0x7c00_0105), 1);
4398        let pairs = [(5u32, st)];
4399        assert!(objects::field_set(st, slot(0), &[7]));
4400        // Active: the constraint redraws it above 200.
4401        assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4402        assert!(get(st, 0) > 200);
4403        // Off: it is a constant 7, so `> 200` fails and the value is kept.
4404        assert!(objects::field_set(st, slot(0), &[7]));
4405        assert!(objects::set_static_rand_mode(st, 0, false));
4406        assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4407        assert_eq!(get(st, 0), 7);
4408        assert!(objects::set_static_rand_mode(st, 0, true));
4409        assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4410        release(a);
4411        release(st);
4412    }
4413}
4414
4415/// Functions called from constraints (Task C13; LRM 18.5.11).
4416#[cfg(test)]
4417mod func_tests {
4418    use super::*;
4419    use crate::constraints::func::{Stmt, tests::count_ones};
4420    use crate::constraints::ir::{ArrayNode, BinOp};
4421    use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
4422
4423    fn u(w: u32) -> Ty {
4424        Ty::unsigned(w)
4425    }
4426    fn s32() -> Ty {
4427        Ty::signed(32)
4428    }
4429    fn var(i: u32, t: Ty) -> Expr {
4430        Expr::Var(i, t)
4431    }
4432    fn k(t: Ty, v: u64) -> Expr {
4433        Expr::constant(t, v)
4434    }
4435    fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
4436        Expr::Binary(op, Box::new(a), Box::new(b), t)
4437    }
4438    fn widen(x: Expr, t: Ty) -> Expr {
4439        Expr::Resize(Box::new(x), t)
4440    }
4441    fn call(func: u32, args: Vec<Expr>, ty: Ty) -> Expr {
4442        Expr::Array(ArrayNode::Call { func, args, ty })
4443    }
4444    fn slot(o: usize) -> FieldSlot {
4445        FieldSlot {
4446            offset: o,
4447            words: 1,
4448        }
4449    }
4450
4451    /// `function byte twice(byte y); twice = y + y; endfunction`
4452    fn twice() -> Func {
4453        Func {
4454            name: "twice".into(),
4455            slots: vec![u(8), u(8)],
4456            n_params: 1,
4457            ret: 1,
4458            body: vec![Stmt::Assign(
4459                1,
4460                bin(BinOp::Add, var(0, u(8)), var(0, u(8)), u(8)),
4461            )],
4462        }
4463    }
4464
4465    /// A function that always returns 0.
4466    fn zero() -> Func {
4467        Func {
4468            name: "zero".into(),
4469            slots: vec![u(8), u(8)],
4470            n_params: 1,
4471            ret: 1,
4472            body: vec![Stmt::Assign(1, k(u(8), 0))],
4473        }
4474    }
4475
4476    /// `n` 8-bit rand fields at words `0..n`; the constraints and functions
4477    /// are the test's.
4478    fn setup(id: u32, n: usize, funcs: Vec<Func>, conjuncts: Vec<Expr>) -> u64 {
4479        let t = ClassTemplateId(id);
4480        register_rand_props(
4481            t,
4482            (0..n)
4483                .map(|i| RandProp {
4484                    slot: slot(i),
4485                    kind: RandKind::Rand,
4486                })
4487                .collect(),
4488        );
4489        register_program(
4490            t,
4491            Ok(Program {
4492                vars: (0..n)
4493                    .map(|i| ProgVar {
4494                        ty: u(8),
4495                        slot: slot(i),
4496                        home: Home::Instance,
4497                        prop_index: Some(i),
4498                        domain_conjunct: None,
4499                        randc: false,
4500                        window: None,
4501                    })
4502                    .collect(),
4503                conjuncts,
4504                funcs,
4505                ..Program::default()
4506            }),
4507        );
4508        let h = alloc(t, n);
4509        objects::set_prng_seed(h, 0xF00D_0001);
4510        h
4511    }
4512
4513    fn get(h: u64, off: usize) -> u64 {
4514        let mut b = [0u64];
4515        assert!(objects::field_get(h, slot(off), &mut b));
4516        b[0]
4517    }
4518
4519    /// LRM 18.5.11 C1: `len == count_ones(v)`. The argument `v` is solved
4520    /// first (uniform over its 256 values), so `len` follows it; `len` is
4521    /// binomial (4 is far likelier than 0), not uniform over its values.
4522    #[test]
4523    fn a_return_value_follows_its_argument_which_is_solved_first() {
4524        // v = var 0, len = var 1.
4525        let c = bin(
4526            BinOp::Eq,
4527            widen(var(1, u(8)), s32()),
4528            call(0, vec![widen(var(0, u(8)), u(10))], s32()),
4529            Ty::BIT,
4530        );
4531        let h = setup(0x7a00_0008, 2, vec![count_ones()], vec![c]);
4532        let mut by_len = [0u32; 9];
4533        for _ in 0..400 {
4534            assert_eq!(randomize(h, &[]), Outcome::Solved);
4535            let (v, len) = (get(h, 0), get(h, 1));
4536            assert_eq!(len, u64::from(v.count_ones()), "len == count_ones({v})");
4537            by_len[len as usize] += 1;
4538        }
4539        assert!(by_len[4] > 60, "len 4 is the likeliest: {by_len:?}");
4540        assert!(by_len[0] < 10, "len 0 needs v == 0: {by_len:?}");
4541        release(h);
4542    }
4543
4544    /// LRM 18.5.11: `x <= F(y)` with `y inside {2, 4, 8}` solves `y` first,
4545    /// so each `y` is drawn with probability 1/3 whatever number of `x` it
4546    /// admits (a joint solve would favour 8, 17 of 31 pairs).
4547    #[test]
4548    fn the_argument_is_drawn_uniformly_before_the_variable_it_bounds() {
4549        // x = var 0, y = var 1.
4550        let le = bin(
4551            BinOp::Le,
4552            widen(var(0, u(8)), u(8)),
4553            call(0, vec![var(1, u(8))], u(8)),
4554            Ty::BIT,
4555        );
4556        let y = var(1, u(8));
4557        let inside = [2u64, 4, 8]
4558            .into_iter()
4559            .map(|v| bin(BinOp::Eq, y.clone(), k(u(8), v), Ty::BIT))
4560            .reduce(|a, b| bin(BinOp::LogOr, a, b, Ty::BIT))
4561            .unwrap();
4562        let h = setup(0x7a00_0009, 2, vec![twice()], vec![le, inside]);
4563        let mut hits = [0u32; 3];
4564        for _ in 0..300 {
4565            assert_eq!(randomize(h, &[]), Outcome::Solved);
4566            let (x, y) = (get(h, 0), get(h, 1));
4567            assert!(x <= 2 * y, "x={x} y={y}");
4568            hits[match y {
4569                2 => 0,
4570                4 => 1,
4571                8 => 2,
4572                other => panic!("y = {other}"),
4573            }] += 1;
4574        }
4575        assert!(
4576            hits.iter().all(|&n| n > 75),
4577            "each y about a third of the time: {hits:?}"
4578        );
4579        release(h);
4580    }
4581
4582    /// LRM 18.5.11: "a circular dependency ... shall be an error".
4583    #[test]
4584    fn a_circular_implicit_order_is_a_loud_outcome() {
4585        let cs = vec![
4586            bin(
4587                BinOp::Eq,
4588                var(0, u(8)),
4589                call(0, vec![var(1, u(8))], u(8)),
4590                Ty::BIT,
4591            ),
4592            bin(
4593                BinOp::Eq,
4594                var(1, u(8)),
4595                call(0, vec![var(0, u(8))], u(8)),
4596                Ty::BIT,
4597            ),
4598        ];
4599        let h = setup(0x7a00_000a, 2, vec![twice()], cs);
4600        let seed = objects::prng_seed(h);
4601        assert_eq!(randomize(h, &[]), Outcome::ImplicitOrderCycle);
4602        assert_eq!(objects::prng_seed(h), seed, "a failed call leaves the seed");
4603        release(h);
4604    }
4605
4606    /// A check over one stage that no draw satisfies is a loud outcome, not a
4607    /// hang or a quiet pick.
4608    #[test]
4609    fn an_unsatisfiable_check_is_function_search_not_a_wrong_value() {
4610        let c = bin(
4611            BinOp::Eq,
4612            call(0, vec![var(0, u(8))], u(8)),
4613            k(u(8), 1),
4614            Ty::BIT,
4615        );
4616        let h = setup(0x7a00_000b, 1, vec![zero()], vec![c]);
4617        assert_eq!(randomize(h, &[]), Outcome::FunctionSearch);
4618        release(h);
4619    }
4620
4621    /// A check some draws satisfy is solved by rejection sampling.
4622    #[test]
4623    fn a_satisfiable_check_is_found_by_sampling() {
4624        // twice(x) == 6: x is 3 (or 131, the byte wraps).
4625        let c = bin(
4626            BinOp::Eq,
4627            call(0, vec![var(0, u(8))], u(8)),
4628            k(u(8), 6),
4629            Ty::BIT,
4630        );
4631        let h = setup(0x7a00_000c, 1, vec![twice()], vec![c]);
4632        for _ in 0..10 {
4633            assert_eq!(randomize(h, &[]), Outcome::Solved);
4634            assert!(matches!(get(h, 0), 3 | 131));
4635        }
4636        release(h);
4637    }
4638}
4639
4640/// `std::randomize()`: solving a spec with no object (Task C21; LRM 18.12).
4641#[cfg(test)]
4642mod scope_tests {
4643    use super::tests::{bin, k, u, var};
4644    use super::*;
4645    use crate::constraints::ir::BinOp;
4646
4647    fn scope_var(offset: usize, width: u32) -> ProgVar {
4648        ProgVar {
4649            ty: u(width),
4650            slot: FieldSlot {
4651                offset,
4652                words: (2 * width as usize).div_ceil(64).max(1),
4653            },
4654            home: Home::Instance,
4655            prop_index: None,
4656            domain_conjunct: None,
4657            randc: false,
4658            window: None,
4659        }
4660    }
4661
4662    fn captured_state(width: u32) -> ProgState {
4663        ProgState {
4664            class_of: false,
4665            root_bit: 0,
4666            ty: u(width),
4667            slot: FieldSlot {
4668                offset: 0,
4669                words: 1,
4670            },
4671            home: Home::Instance,
4672            via: Vec::new(),
4673            window: None,
4674        }
4675    }
4676
4677    fn register(vars: Vec<ProgVar>, states: Vec<ProgState>, conjuncts: Vec<Expr>) -> u32 {
4678        let captures = states
4679            .iter()
4680            .enumerate()
4681            .map(|(i, s)| Capture {
4682                state: i as u32,
4683                width: s.ty.width,
4684            })
4685            .collect();
4686        register_spec(Spec {
4687            frag: Program {
4688                vars,
4689                states,
4690                conjuncts,
4691                ..Program::default()
4692            },
4693            captures,
4694            only: None,
4695        })
4696    }
4697
4698    fn lt(a: Expr, b: Expr) -> Expr {
4699        bin(BinOp::Lt, a, b, Ty::BIT)
4700    }
4701
4702    #[test]
4703    fn a_scope_randomize_solves_without_any_object_and_returns_the_values() {
4704        // a < b, b <= 10: the two variables at words 0 and 1 of the output
4705        let id = register(
4706            vec![scope_var(0, 8), scope_var(1, 8)],
4707            Vec::new(),
4708            vec![
4709                lt(var(0, u(8)), var(1, u(8))),
4710                bin(BinOp::Le, var(1, u(8)), k(u(8), 10), Ty::BIT),
4711            ],
4712        );
4713        let mut distinct = std::collections::BTreeSet::new();
4714        for seed in 1..60u64 {
4715            let mut out = [0xffu64; 2];
4716            assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4717            assert!(out[0] < out[1] && out[1] <= 10, "{out:?}");
4718            distinct.insert((out[0], out[1]));
4719        }
4720        assert!(
4721            distinct.len() > 10,
4722            "the draw follows the seed: {distinct:?}"
4723        );
4724    }
4725
4726    #[test]
4727    fn the_same_seed_draws_the_same_values() {
4728        let id = register(
4729            vec![scope_var(0, 16)],
4730            Vec::new(),
4731            vec![lt(var(0, u(16)), k(u(16), 1000))],
4732        );
4733        let (mut a, mut b) = ([0u64], [0u64]);
4734        assert_eq!(randomize_scope(id, &[], &mut a, 77), Outcome::Solved);
4735        assert_eq!(randomize_scope(id, &[], &mut b, 77), Outcome::Solved);
4736        assert_eq!(a, b);
4737    }
4738
4739    #[test]
4740    fn captured_locals_are_constants_of_the_solve() {
4741        // x == s + 1 with s captured
4742        let id = register(
4743            vec![scope_var(0, 8)],
4744            vec![captured_state(8)],
4745            vec![bin(
4746                BinOp::Eq,
4747                var(0, u(8)),
4748                bin(BinOp::Add, Expr::State(0, u(8)), k(u(8), 1), u(8)),
4749                Ty::BIT,
4750            )],
4751        );
4752        for s in [0u64, 41, 200] {
4753            let mut out = [0u64];
4754            assert_eq!(randomize_scope(id, &[s], &mut out, 5), Outcome::Solved);
4755            assert_eq!(out[0], s + 1);
4756        }
4757    }
4758
4759    #[test]
4760    fn an_unsatisfiable_scope_randomize_writes_nothing() {
4761        let id = register(
4762            vec![scope_var(0, 8)],
4763            Vec::new(),
4764            vec![
4765                bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
4766                lt(var(0, u(8)), k(u(8), 3)),
4767            ],
4768        );
4769        let mut out = [0x77u64];
4770        assert_eq!(
4771            randomize_scope(id, &[], &mut out, 1),
4772            Outcome::Unsatisfiable
4773        );
4774        assert_eq!(out, [0x77], "a failed randomize changes nothing (18.6.1)");
4775    }
4776
4777    #[test]
4778    fn with_no_variable_the_call_is_a_checker() {
4779        // x < y over two captured locals
4780        let id = register(
4781            Vec::new(),
4782            vec![captured_state(8), captured_state(8)],
4783            vec![lt(Expr::State(0, u(8)), Expr::State(1, u(8)))],
4784        );
4785        let mut none: [u64; 0] = [];
4786        assert_eq!(randomize_scope(id, &[3, 9], &mut none, 1), Outcome::Solved);
4787        assert_eq!(
4788            randomize_scope(id, &[9, 3], &mut none, 1),
4789            Outcome::Unsatisfiable
4790        );
4791        assert_eq!(
4792            randomize_scope(id, &[4, 4], &mut none, 1),
4793            Outcome::Unsatisfiable
4794        );
4795    }
4796
4797    #[test]
4798    fn an_unknown_bit_in_a_captured_local_is_an_error_not_a_value() {
4799        let id = register(
4800            Vec::new(),
4801            vec![captured_state(8)],
4802            vec![lt(Expr::State(0, u(8)), k(u(8), 9))],
4803        );
4804        let mut none: [u64; 0] = [];
4805        // bit 0 of the 8-bit local is X: its unknown mask (the upper half) is set
4806        let x_bit = 1u64 << 8;
4807        assert_eq!(
4808            randomize_scope(id, &[x_bit], &mut none, 1),
4809            Outcome::StateUnknown
4810        );
4811    }
4812
4813    #[test]
4814    fn a_wide_variable_spans_words_and_keeps_its_pinned_bits() {
4815        // a 70-bit variable: 140 packed bits = 3 words; its top 6 bits == 0x2a
4816        let wide = scope_var(0, 70);
4817        assert_eq!(wide.slot.words, 3);
4818        let top = bin(
4819            BinOp::Eq,
4820            Expr::Slice(Box::new(var(0, u(70))), 64, u(6)),
4821            k(u(6), 0x2a),
4822            Ty::BIT,
4823        );
4824        let id = register(vec![wide], Vec::new(), vec![top]);
4825        let mut seen_low = std::collections::BTreeSet::new();
4826        for seed in 1..20u64 {
4827            let mut out = [0u64; 3];
4828            assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4829            assert_eq!(out[1] & 0x3f, 0x2a, "{out:x?}");
4830            assert_eq!(out[2], 0, "no unknown bits: {out:x?}");
4831            seen_low.insert(out[0]);
4832        }
4833        assert!(seen_low.len() > 10, "the other bits are random");
4834    }
4835
4836    #[test]
4837    fn a_call_that_does_not_match_its_spec_fails_loudly() {
4838        let id = register(
4839            vec![scope_var(0, 8)],
4840            vec![captured_state(8)],
4841            vec![lt(var(0, u(8)), Expr::State(0, u(8)))],
4842        );
4843        let spec = spec(id).expect("registered");
4844        assert_eq!((spec.scope_words(), spec.capture_words()), (1, 1));
4845        let mut one = [0u64];
4846        let mut two = [0u64; 2];
4847        assert_eq!(randomize_scope(id, &[], &mut one, 1), Outcome::Failed);
4848        assert_eq!(randomize_scope(id, &[5], &mut two, 1), Outcome::Failed);
4849        assert_eq!(randomize_scope(0, &[], &mut one, 1), Outcome::Failed);
4850        assert_eq!(randomize_scope(u32::MAX, &[], &mut one, 1), Outcome::Failed);
4851    }
4852}