Skip to main content

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