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