Skip to main content

vj_sim/constraints/
build.rs

1//! The sv-lang `Constraint`/`Expression` to [`ir::Expr`] builder (plan task
2//! C2; LRM 18.3 and 18.5).
3//!
4//! # Contract
5//!
6//! A [`Builder`] is told which symbols are *active random variables*
7//! ([`Builder::declare_rand`]); every other value a constraint reads becomes a
8//! *state variable* (LRM 18.3: a non-`rand` variable, or one whose
9//! `rand_mode` is off, is a constant during the solve), registered on first
10//! use. Constraint blocks are then fed in ([`Builder::add_block`],
11//! [`Builder::add_constraint`]); each constraint item becomes one boolean
12//! conjunct. [`Builder::finish`] appends the implicit enum-domain conjuncts
13//! (LRM 18.3: a `rand` enum variable only takes the enum's named constants)
14//! and re-validates every tree, so a builder bug is a loud [`BuildError::Ir`]
15//! and never a wrong sample.
16//!
17//! # Typing
18//!
19//! slang has already applied LRM 11.6 (context-determined widths) and
20//! inserted a `Conversion` node wherever an operand's type differs from its
21//! context's, so every arithmetic/bitwise/compare node here sees operands of
22//! one type. The conversion's extension rule is the one place a mistake is
23//! easy: a `Propagated` conversion extends by the *context's* signedness (a
24//! signed operand in an unsigned context is zero-extended, LRM 11.8.2), while
25//! an `Implicit`/`Explicit` one extends by the *operand's* (assignment
26//! semantics). Both were verified against slang's own constant evaluator.
27//!
28//! # What is rejected, by name
29//!
30//! Every construct outside this task's scope is a typed [`BuildError`] naming
31//! the construct and the plan task that owns it; nothing is silently dropped
32//! (inline `with`, a `rand` handle, ...).
33//!
34//! # Soft constraints (C15; LRM 18.5.13)
35//!
36//! A `soft` constraint is an ordinary conjunct wrapped in its discard flag
37//! ([`priority::wrap_soft`]) and recorded in [`ConstraintSet::softs`] with its
38//! [`Priority`] and the variables it mentions; a `disable soft x` is applied
39//! once, in [`Builder::finish`], by marking the lower-priority softs that name
40//! `x` as disabled. The relaxation itself is the registry's
41//! ([`priority::solve_relaxing`]). Priorities follow the order constraints are
42//! fed in (later is higher) and the scope set by
43//! [`Builder::set_priority_scope`] (a derived class above its bases, a
44//! contained object below its container). A `soft` over a real value, inside a
45//! `foreach`, or over a `randc` variable, and a `disable soft` anywhere but at
46//! the top level of a block, are refused by name.
47//!
48//! # Arrays and iterative constraints (C11, C12, C14; LRM 18.4, 18.5.7)
49//!
50//! Dynamic-array and queue properties become *array-level* IR
51//! ([`ir::ArrayNode`]): `A.size`, `A[i]`, `foreach` and the reduction methods
52//! with their iterator variables. Expanding them needs the run-time sizes, so
53//! it is the registry's job (`super::array`); the limits of what is built here
54//! are listed in the `arrays` submodule. A packed struct or union is one
55//! integral variable and `s.member` a window of it (18.4). An unpacked struct
56//! or fixed-size unpacked array property is flattened to its *leaves*, each a
57//! variable windowed into the property's value ([`Builder::declare_rand_aggregate`],
58//! the `aggregate` submodule): `rand` makes every element or member random
59//! (18.4), a member's own qualifier overrides the inherited one, and `a[i]` at
60//! a state or loop index is a run-time choice among the elements
61//! ([`ir::ArrayNode::Pick`]).
62//!
63//! # Real-valued constraints (C9; LRM 18.5.3, 18.5.13)
64//!
65//! A `rand real`/`shortreal` variable ([`Builder::declare_rand_real`]) lives
66//! in its own namespace: its constraints build [`real_domain`](super::real_domain)
67//! conditions into [`ConstraintSet::real`] and never an integral conjunct (see
68//! the `real` submodule for the supported subset and its limits). A constraint
69//! that reads a real random variable is a real constraint; one mixing it with
70//! an integral random variable is a named [`BuildError::RealConstraint`].
71//!
72//! # Uniqueness (C10; LRM 18.5.4)
73//!
74//! `unique { a, b, c[2:3] }` becomes one [`Expr::AllDistinct`] conjunct whose
75//! members are the group's singular variables and the leaf elements of its
76//! array members, in source order (see the `unique` submodule). An array
77//! element is an IR variable once [`Builder::declare_rand_aggregate`] (or
78//! [`Builder::declare_rand_element`]) has declared it.
79//!
80//! # Guards and object handles (C8; LRM 18.5.5, 18.5.6, 18.5.12)
81//!
82//! A constraint-level implication and an `if`/`else` build [`Expr::Guard`]
83//! (predicate, body, else-body) rather than a plain `Ite`: the solver
84//! evaluates the predicate by the Figure 18-3 rules before solving, so a null
85//! handle read where no operand decides the guard is an error and a decided
86//! guard drops the unselected arm unexamined. A non-`rand` handle read in a
87//! constraint is a state variable: `null` is the constant 0, a handle itself
88//! a 64-bit state, and `a.x` / `a.next.x` a *path* state variable
89//! ([`StateInfo::via`]) the registry walks at randomize time, so a null link
90//! is a typed outcome and not a crash. Without an object layout resolver a
91//! member read through a handle stays [`BuildError::ForeignObjectAccess`].
92//! `solve ... before` is NOT a boolean constraint: it
93//! contributes ordering edges ([`ConstraintSet::before`]) and no conjunct
94//! (LRM 18.5.9: it never changes the solution space). 4-state operators and
95//! literals are [`BuildError::FourStateOperator`]/[`BuildError::FourStateLiteral`]
96//! (LRM 18.3: "illegal and shall result in an error"); a `virtual interface`
97//! `rand` variable is [`BuildError::VirtualInterfaceRand`].
98
99use super::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
100use super::func::Func;
101use super::ir::{ArrayNode, BinOp, Expr, IrError, Ty, UnOp, VarId, mask};
102use super::order::{self, OrderError};
103use super::priority::{self, Disable, Priority, Soft, SoftKey};
104use super::randc::MAX_RANDC_WIDTH;
105use super::real_domain::{RealCond, RealDist, RealProblem};
106use super::reduce::BlockItems;
107use crate::objects::{FieldSlot, Window};
108use crate::symid::{SymbolId, SymbolUid};
109use core::fmt;
110use num_bigint::{BigInt, BigUint};
111use num_traits::{One, Zero};
112use std::collections::{HashMap, HashSet};
113use sv_lang::kinds::{ConstraintKind, ExpressionKind, PatternKind, SymbolKind};
114use sv_lang::nodes::{
115    ExpressionConstraintSyntax, SolveBeforeConstraintSyntax, ValueRangeExpressionSyntax,
116};
117use sv_lang::{
118    AstNode, BinaryOp, Bit, Constraint, ConstraintBlockFlags, DistWeightKind, Expression, RandMode,
119    SVInt, Symbol, SyntaxTree, Type, UnaryOp,
120};
121
122mod aggregate;
123mod arrays;
124mod funcs;
125mod real;
126mod unique;
127pub use aggregate::{Declared, Step, is_aggregate_type, property_is_random};
128pub use arrays::ArrayInfo;
129use arrays::LoopScope;
130pub use real::RealVarInfo;
131
132/// Why a constraint could not be turned into IR.
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub enum BuildError {
135    /// A 4-state operator (`===`, `!==`, `==?`, `!=?`): LRM 18.3.
136    FourStateOperator {
137        /// The operator's spelling.
138        op: &'static str,
139    },
140    /// A literal or constant with an X/Z bit: LRM 18.3.
141    FourStateLiteral {
142        /// The constant's text.
143        text: String,
144    },
145    /// A `rand` variable of virtual-interface type (plan section 1.5).
146    VirtualInterfaceRand {
147        /// The variable.
148        name: String,
149    },
150    /// A value that is not an integral bit-vector (real, string, class
151    /// handle, unpacked aggregate, ...).
152    NonIntegral {
153        /// What was found.
154        what: String,
155    },
156    /// A reference through another object (`other.field`) that the joint
157    /// solver of plan task C17 does not cover.
158    ForeignObjectAccess {
159        /// What was referenced.
160        what: String,
161    },
162    /// `declare_rand` on a symbol that is not `rand`.
163    NotRand {
164        /// The symbol.
165        name: String,
166    },
167    /// A construct this task (or the current plan position) does not build.
168    Unsupported {
169        /// The construct.
170        construct: String,
171        /// The plan task that owns it.
172        task: &'static str,
173    },
174    /// A constant select index outside the declared range.
175    OutOfRangeSelect {
176        /// What was selected.
177        what: String,
178    },
179    /// An sv-lang node lacked an accessor the builder needs.
180    Missing {
181        /// What was missing.
182        what: String,
183    },
184    /// A `randc` variable wider than the permutation engine supports (LRM
185    /// 18.4.2 allows the limit as long as it is at least 8 bits).
186    RandcTooWide {
187        /// The variable.
188        name: String,
189        /// Its width.
190        width: u32,
191        /// The widest supported.
192        max: u32,
193    },
194    /// A `solve ... before` operand that LRM 18.5.9 does not allow (not a
195    /// `rand` variable of the class, or a `randc` one).
196    BadSolveOperand {
197        /// What was wrong with it.
198        what: String,
199    },
200    /// A `dist` (or value range) the LRM does not allow, or one this
201    /// implementation cannot weight exactly.
202    BadDist {
203        /// What was wrong with it.
204        what: String,
205    },
206    /// A `unique` group the LRM does not allow (a `randc` member, members of
207    /// different types, something that is not a variable; 18.5.4).
208    BadUnique {
209        /// What was wrong with it.
210        what: String,
211    },
212    /// A constraint on a `real`/`shortreal` variable the real-domain solver
213    /// cannot take (see [`super::real_domain`]), or a real `rand` declaration
214    /// the LRM does not allow.
215    RealConstraint {
216        /// What was wrong with it.
217        what: String,
218    },
219    /// An array constraint the LRM does not allow or that has no meaning
220    /// (an associative array's size or elements, a random index; 18.4,
221    /// 18.5.7).
222    BadArray {
223        /// What was wrong with it.
224        what: String,
225    },
226    /// A function called from a constraint that cannot be evaluated before the
227    /// solve (18.5.11): a side effect, a construct outside the supported
228    /// subset, a read of a random variable that is not an argument.
229    BadFunction {
230        /// The function.
231        function: String,
232        /// What was wrong with it.
233        what: String,
234    },
235    /// A `soft` constraint or `disable soft` the LRM does not allow or that
236    /// this implementation cannot relax exactly (18.5.13).
237    BadSoft {
238        /// What was wrong with it.
239        what: String,
240    },
241    /// An `extern constraint name;` prototype with no external constraint
242    /// block anywhere in its scope (18.5.1).
243    MissingExternBody {
244        /// The prototype's name.
245        name: String,
246    },
247    /// The `solve ... before` constraints have no order (a cycle).
248    Order(OrderError),
249    /// The built tree failed [`Expr::validate`] (a builder bug).
250    Ir(IrError),
251}
252
253impl fmt::Display for BuildError {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        match self {
256            BuildError::FourStateOperator { op } => write!(
257                f,
258                "the 4-state operator `{op}` is illegal in a constraint (IEEE 1800-2023 18.3)"
259            ),
260            BuildError::FourStateLiteral { text } => write!(
261                f,
262                "the 4-state value `{text}` is illegal in a constraint (IEEE 1800-2023 18.3)"
263            ),
264            BuildError::VirtualInterfaceRand { name } => write!(
265                f,
266                "`rand` variable `{name}` has a virtual interface type, which cannot be randomized"
267            ),
268            BuildError::NonIntegral { what } => {
269                write!(
270                    f,
271                    "{what} is not an integral value; constraints solve integral variables only"
272                )
273            }
274            BuildError::ForeignObjectAccess { what } => write!(
275                f,
276                "{what}: this reference to another object's members is not covered by the \
277                 multi-object solver (constraints plan task C17 follow-up)"
278            ),
279            BuildError::NotRand { name } => {
280                write!(f, "`{name}` is not a `rand`/`randc` variable")
281            }
282            BuildError::Unsupported { construct, task } => write!(
283                f,
284                "{construct} in a constraint is not implemented yet (constraints plan {task})"
285            ),
286            BuildError::OutOfRangeSelect { what } => {
287                write!(f, "constant select out of the declared range: {what}")
288            }
289            BuildError::Missing { what } => write!(f, "sv-lang gave no {what}"),
290            BuildError::RandcTooWide { name, width, max } => write!(
291                f,
292                "`randc` variable `{name}` is {width} bits wide; this implementation cycles \
293                 `randc` variables of at most {max} bits (IEEE 1800-2023 18.4.2 permits a \
294                 limit of at least 8)"
295            ),
296            BuildError::BadSolveOperand { what } => write!(
297                f,
298                "invalid `solve ... before` operand: {what} (IEEE 1800-2023 18.5.9)"
299            ),
300            BuildError::BadDist { what } => {
301                write!(
302                    f,
303                    "invalid `dist` constraint: {what} (IEEE 1800-2023 18.5.3)"
304                )
305            }
306            BuildError::BadUnique { what } => {
307                write!(
308                    f,
309                    "invalid `unique` constraint: {what} (IEEE 1800-2023 18.5.4)"
310                )
311            }
312            BuildError::RealConstraint { what } => {
313                write!(f, "unsupported real-valued constraint: {what}")
314            }
315            BuildError::BadArray { what } => {
316                write!(f, "invalid array constraint: {what}")
317            }
318            BuildError::BadFunction { function, what } => write!(
319                f,
320                "function `{function}` cannot be called from a constraint: {what} \
321                 (IEEE 1800-2023 18.5.11)"
322            ),
323            BuildError::BadSoft { what } => {
324                write!(
325                    f,
326                    "invalid soft constraint: {what} (IEEE 1800-2023 18.5.13)"
327                )
328            }
329            BuildError::MissingExternBody { name } => write!(
330                f,
331                "`extern constraint {name};` has no external constraint block \
332                 (IEEE 1800-2023 18.5.1)"
333            ),
334            BuildError::Order(e) => write!(f, "{e}"),
335            BuildError::Ir(e) => write!(f, "{e}"),
336        }
337    }
338}
339
340impl std::error::Error for BuildError {}
341
342impl From<IrError> for BuildError {
343    fn from(e: IrError) -> Self {
344        BuildError::Ir(e)
345    }
346}
347
348impl From<OrderError> for BuildError {
349    fn from(e: OrderError) -> Self {
350        BuildError::Order(e)
351    }
352}
353
354fn unsupported(construct: impl Into<String>, task: &'static str) -> BuildError {
355    BuildError::Unsupported {
356        construct: construct.into(),
357        task,
358    }
359}
360
361/// An active random variable.
362#[derive(Clone, Debug, PartialEq, Eq)]
363pub struct VarInfo {
364    /// The declaring symbol's key.
365    pub symbol: SymbolId,
366    /// Its declared name.
367    pub name: String,
368    /// Its type (a 4-state declaration is solved as 2-state).
369    pub ty: Ty,
370    /// For an enum-typed variable, the named constants it may take (LRM 18.3).
371    pub enum_domain: Option<Vec<BigUint>>,
372    /// Whether it is `randc`: drawn from its own cyclic permutation before
373    /// every `rand` variable (LRM 18.4.2) instead of from the joint solution
374    /// space.
375    pub randc: bool,
376    /// For one leaf of an aggregate property ([`Builder::declare_rand_aggregate`],
377    /// [`Builder::declare_rand_element`]), the steps from the property to it,
378    /// outermost first; empty for a property. [`Self::symbol`] is then the
379    /// property's key and [`Self::name`] reads `a[2]` or `s.f[1]`.
380    pub path: Vec<Step>,
381    /// Where that leaf sits in its property's stored value; `None` for a
382    /// property itself.
383    pub window: Option<Window>,
384}
385
386/// One step of a state variable's path through object handles.
387#[derive(Clone, Debug, PartialEq, Eq)]
388pub struct StateHop {
389    /// The member's key.
390    pub symbol: SymbolId,
391    /// Where the member lives in the object the previous step's handle
392    /// names, as laid out by the builder's slot resolver.
393    pub slot: FieldSlot,
394}
395
396/// A state variable: a value read before the solve and fixed during it.
397#[derive(Clone, Debug, PartialEq, Eq)]
398pub struct StateInfo {
399    /// The symbol's key: the property of the randomized class that holds
400    /// the value, or (when [`Self::via`] is not empty) the object handle the
401    /// path starts at.
402    pub symbol: SymbolId,
403    /// Its name as written: `x`, or `a.x` for a member read through a handle.
404    pub name: String,
405    /// Its type (a class handle is a 64-bit unsigned value).
406    pub ty: Ty,
407    /// The members followed from the handle [`Self::symbol`] to the value:
408    /// the first is read from the object that handle names, the next from
409    /// the object the first one's value names, and so on. A null (or
410    /// deleted) handle on the way makes the state unreadable. Empty for a
411    /// plain property.
412    pub via: Vec<StateHop>,
413    /// For a leaf of an aggregate property that is not random (a plain
414    /// member of a struct, an element of a non-`rand` array), where it sits in
415    /// the property [`Self::symbol`] names; `None` for the whole value.
416    pub window: Option<Window>,
417    /// The bit of the root handle property the first handle's word starts at:
418    /// `64 * i` for `es[i].v` through a fixed array of handles (LRM 18.5.8), 0
419    /// otherwise.
420    pub root_bit: u32,
421    /// The value is the dynamic class template id of the object the handle at
422    /// the end of the path names (0 never; a null handle makes the state
423    /// unreadable), not the handle itself: the selector of a virtual call
424    /// through a handle.
425    pub class_of: bool,
426}
427
428/// A local of an inline constraint's call site (LRM 18.7): its value is read
429/// when the call is made and is a constant of the solve.
430#[derive(Clone, Debug, PartialEq, Eq)]
431pub struct CaptureInfo {
432    /// The state variable that stands for it ([`ConstraintSet::states`]).
433    pub state: u32,
434    /// The local's key (for a member read through a local handle, the handle).
435    pub symbol: SymbolId,
436    /// The members followed from the local handle [`Self::symbol`] to the
437    /// captured value (`o.lim` in a call-site `with { x < o.lim }`, LRM 18.7:
438    /// the whole read is one call-site constant); empty for the local itself.
439    pub hops: Vec<SymbolId>,
440    /// Its name as written.
441    pub name: String,
442    /// Its type.
443    pub ty: Ty,
444}
445
446/// Decides whether a name read by an inline constraint resolves in the scope
447/// of the `randomize()` call (a *local*, LRM 18.7) rather than in the
448/// randomized object's class; see [`Builder::set_local_classifier`].
449pub type LocalClassifier<'t> = Box<dyn for<'s> Fn(Expression<'s>) -> bool + 't>;
450
451/// The builder's product: variable tables plus the boolean conjuncts that
452/// must all hold.
453#[derive(Clone, Debug, PartialEq, Eq, Default)]
454pub struct ConstraintSet {
455    /// Random variables, indexed by [`super::ir::VarId`].
456    pub vars: Vec<VarInfo>,
457    /// State variables, indexed by [`super::ir::StateId`].
458    pub states: Vec<StateInfo>,
459    /// One-bit expressions, all of which must be true (user constraints in
460    /// source order, then the implicit enum-domain conjuncts).
461    pub conjuncts: Vec<Expr>,
462    /// For each variable (indexed like [`Self::vars`]), the index into
463    /// [`Self::conjuncts`] of its implicit enum-domain conjunct, if any.
464    /// An inactive (`rand_mode(0)`) enum variable behaves as a state
465    /// variable (LRM 18.8) and so must not be forced into its enum domain;
466    /// the dispatcher drops exactly these conjuncts.
467    pub domain_conjunct: Vec<Option<usize>>,
468    /// `solve ... before` edges `(first, later)` over [`Self::vars`] (LRM
469    /// 18.5.9), in source order. Acyclic: [`Builder::finish`] checks.
470    pub before: Vec<(VarId, VarId)>,
471    /// The `dist` constraints (LRM 18.5.3). Dist `j`'s hidden selector
472    /// variable is `SELECTOR_BASE + j`; its membership conjunct is already in
473    /// [`Self::conjuncts`], and the weighting is applied when the solution is
474    /// drawn (see [`super::dist`]).
475    pub dists: Vec<Dist>,
476    /// The `real`/`shortreal` random variables, indexed by the real
477    /// namespace's own ids (the `Var` ids of [`Self::real`]).
478    pub real_vars: Vec<RealVarInfo>,
479    /// The constraints over [`Self::real_vars`], solved after the integral
480    /// variables (see [`super::real_domain`]).
481    pub real: RealProblem,
482    /// The dynamic arrays and queues the constraints reach, indexed by
483    /// [`super::ir::ArrayId`] (see [`ArrayInfo`]).
484    pub arrays: Vec<ArrayInfo>,
485    /// The functions the constraints call, indexed by [`super::ir::FuncId`].
486    pub funcs: Vec<Func>,
487    /// The soft constraints (LRM 18.5.13): soft `k`'s discard flag is
488    /// `SOFT_BASE + k` in [`Self::conjuncts`] (see [`priority`]). Those a
489    /// higher-priority `disable soft` reaches are already marked disabled.
490    pub softs: Vec<Soft>,
491    /// The call-site locals an inline constraint reads (empty outside
492    /// [`Builder::set_local_classifier`] mode); each is one of [`Self::states`].
493    pub captures: Vec<CaptureInfo>,
494    /// One entry per [`Builder::add_block`] call, in call order: which of
495    /// [`Self::conjuncts`] the block produced and whether it can be switched
496    /// off (see [`BlockInfo`]).
497    pub blocks: Vec<BlockInfo>,
498    /// How many `disable soft`s the constraints hold (the table
499    /// [`Soft::disabled_by`] indexes).
500    pub disables: usize,
501    /// The target of each `disable soft`, in the order of [`Self::disables`]'s
502    /// table: an inline constraint's are applied to the object's own softs
503    /// when the fragment is overlaid (LRM 18.5.13.2, 18.7).
504    pub disable_keys: Vec<SoftKey>,
505}
506
507/// What one constraint block contributed to a [`ConstraintSet`]; the data
508/// `constraint_mode(0)` needs (LRM 18.9).
509#[derive(Clone, Debug, PartialEq, Eq, Default)]
510pub struct BlockInfo {
511    /// The block's conjuncts, as a range of [`ConstraintSet::conjuncts`]
512    /// (empty for an extern prototype with no body, which is a constraint of
513    /// no effect). The implicit enum-domain conjuncts are not part of any
514    /// block.
515    pub conjuncts: std::ops::Range<usize>,
516    /// What the block added besides conjuncts (a `dist`, a `solve ... before`,
517    /// a `real` constraint, a `disable soft`), which a call that has the block
518    /// switched off must leave out as well.
519    pub items: BlockItems,
520}
521
522/// The AST to IR builder; see the module docs.
523pub struct Builder<'t> {
524    trees: &'t [SyntaxTree],
525    vars: Vec<VarInfo>,
526    var_ix: HashMap<SymbolId, u32>,
527    /// Pattern variables (`.n`) in scope while a `matches` conditional is
528    /// built, each bound to its subject's expression (LRM 12.6).
529    pattern_binds: HashMap<SymbolId, Expr>,
530    /// Aggregate leaves declared as random variables, by property and path.
531    elem_ix: HashMap<(SymbolId, Vec<Step>), u32>,
532    /// Non-property aggregate variables declared random for one call
533    /// ([`Builder::declare_aggregate_forced`], `std::randomize`, LRM 18.12).
534    forced_roots: HashSet<SymbolId>,
535    real_vars: Vec<RealVarInfo>,
536    real_var_ix: HashMap<SymbolId, u32>,
537    /// Real aggregate leaves declared as random variables, by property and path.
538    real_leaf_ix: HashMap<(SymbolId, Vec<Step>), u32>,
539    /// State variables that are leaves of an aggregate property.
540    leaf_state_ix: HashMap<(SymbolId, Vec<Step>), u32>,
541    real_conds: Vec<RealCond>,
542    real_dists: Vec<RealDist>,
543    real_before: Vec<(u32, u32)>,
544    states: Vec<StateInfo>,
545    /// State variables by path: the root symbol, then each hop's.
546    state_ix: HashMap<(Vec<SymbolId>, u32), u32>,
547    slot_of: Option<SlotResolver<'t>>,
548    /// The implementation of a virtual function in the randomized object's
549    /// own class, when the caller knows it ([`Builder::set_virtual_resolver`]).
550    virtual_impl: Option<VirtualResolver<'t>>,
551    /// Every implementation a virtual function can have on the objects a
552    /// handle of a given static class may name ([`Builder::set_virtual_targets`]).
553    virtual_targets: Option<VirtualTargets<'t>>,
554    conjuncts: Vec<Expr>,
555    before: Vec<(VarId, VarId)>,
556    dists: Vec<Dist>,
557    /// Dynamic arrays and queues, by id and by symbol.
558    arrays: Vec<ArrayInfo>,
559    array_ix: HashMap<SymbolId, u32>,
560    /// The row template of each array of arrays that has one, by outer id.
561    templates: HashMap<u32, u32>,
562    /// The `foreach` and array-method names in scope, innermost last.
563    loops: Vec<LoopScope>,
564    next_loop: u32,
565    /// Set while building a construct that cannot hold array-level values (a
566    /// `dist` item), naming it.
567    no_arrays: Option<&'static str>,
568    /// The functions called so far, by id and by symbol.
569    funcs: Vec<Func>,
570    func_ix: HashMap<(SymbolId, Vec<SymbolId>), u32>,
571    /// While the body of a function called through an object handle
572    /// (`h.f()`, LRM 18.5.12) is built: where that object lives, so the
573    /// properties the body reads are state variables read through it.
574    fn_base: Option<funcs::FnBase>,
575    /// The frame of the function body being built, if any.
576    frame: Option<funcs::Frame>,
577    /// The soft constraints and `disable soft`s built so far (C15).
578    softs: Vec<Soft>,
579    disables: Vec<Disable>,
580    /// The (object rank, class level) the next constraints are built at, and
581    /// the running declaration sequence (see [`Self::set_priority_scope`]).
582    scope: (u32, u32),
583    seq: u32,
584    /// How many implication / `if` constraints enclose the one being built.
585    guard_depth: u32,
586    /// Inline-constraint mode (C19): which names are locals of the call site,
587    /// the locals read so far (by key, into [`Self::captures`]).
588    local_of: Option<LocalClassifier<'t>>,
589    captures: Vec<CaptureInfo>,
590    capture_ix: HashMap<Vec<SymbolId>, usize>,
591    /// What each block added ([`ConstraintSet::blocks`]).
592    blocks: Vec<BlockInfo>,
593}
594
595/// Places a class property in its object's field buffer (the object layout
596/// lives in the code generator, so the builder is handed this).
597/// Maps `(virtual function, static class of the receiver)` to every
598/// `(dynamic class template id, implementation)` pair the call can run.
599type VirtualTargets<'t> =
600    Box<dyn for<'s> Fn(Symbol<'s>, Symbol<'s>) -> Vec<(u32, Symbol<'t>)> + 't>;
601type VirtualResolver<'t> = Box<dyn for<'s> Fn(Symbol<'s>) -> Option<Symbol<'t>> + 't>;
602type SlotResolver<'t> = Box<dyn for<'s> Fn(Symbol<'s>) -> Result<FieldSlot, String> + 't>;
603
604/// A `solve ... before` operand, in the namespace of the variable it names.
605#[derive(Clone, Copy, Debug)]
606enum SolveOperand {
607    Int(VarId),
608    Real(u32),
609    /// `A.size` of a dynamic array or queue (LRM 18.4, 18.5.9).
610    Size,
611}
612
613/// The type of a class handle value: the object table's 64-bit id, `0` for
614/// `null`. Handles are only ever compared in a constraint.
615const HANDLE_TY: Ty = Ty::unsigned(64);
616
617/// How a `[a ... b]` value range reads its operands (LRM 11.4.13, 18.5.3).
618#[derive(Clone, Copy, Debug, PartialEq, Eq)]
619enum RangeOp {
620    /// `[a:b]`.
621    Span,
622    /// `[a +/- b]`: `[a - b : a + b]`.
623    Abs,
624    /// `[a +%- b]`: `[a - a*b/100 : a + a*b/100]`.
625    Pct,
626}
627
628/// The inclusive bounds of a value range, each with the type the compared
629/// value and the bound are converted to (LRM 11.8); `None` is `$`.
630struct Bounds {
631    lo: Option<(Ty, Expr)>,
632    hi: Option<(Ty, Expr)>,
633}
634
635/// The type two operands of one comparison are converted to (LRM 11.8.1).
636fn common_ty(a: Ty, b: Ty) -> Ty {
637    Ty {
638        width: a.width.max(b.width),
639        signed: a.signed && b.signed,
640    }
641}
642
643/// Whether `t` is a class handle or the `null` literal's type.
644fn is_handle_type(t: Option<Type<'_>>) -> bool {
645    t.is_some_and(|t| {
646        let c = t.canonical();
647        c.is_class() || c.is_null()
648    })
649}
650
651fn ty_of(t: Option<Type<'_>>, what: &str) -> Result<Ty, BuildError> {
652    let t = t.ok_or_else(|| BuildError::Missing {
653        what: format!("type for {what}"),
654    })?;
655    if t.is_virtual_interface() {
656        return Err(BuildError::NonIntegral {
657            what: format!("{what} of virtual interface type"),
658        });
659    }
660    if !t.is_integral() {
661        return Err(BuildError::NonIntegral {
662            what: format!("{what} of type `{}`", t.to_sv_string()),
663        });
664    }
665    let width = u32::try_from(t.bit_width()).map_err(|_| BuildError::Missing {
666        what: format!("representable width for {what}"),
667    })?;
668    if width == 0 {
669        return Err(BuildError::NonIntegral {
670            what: format!("zero-width {what}"),
671        });
672    }
673    Ok(Ty {
674        width,
675        signed: t.is_signed(),
676    })
677}
678
679/// A constant of `ty` holding the (possibly negative) `v`, reduced mod
680/// `2^width`.
681fn iconst(ty: Ty, v: i64) -> Expr {
682    let m = BigInt::one() << ty.width;
683    let r = ((BigInt::from(v) % &m) + &m) % &m;
684    Expr::Const(ty, r.to_biguint().expect("reduced into [0, 2^w)"))
685}
686
687fn svint_to_biguint(v: &SVInt, ty: Ty) -> Result<BigUint, BuildError> {
688    if v.has_unknown() {
689        return Err(BuildError::FourStateLiteral {
690            text: format!("{v:?}"),
691        });
692    }
693    let w = v.bit_width();
694    let bits = v.bits();
695    let mut acc = BigUint::zero();
696    for (i, b) in bits.iter().enumerate() {
697        if *b == Bit::One {
698            acc.set_bit(i as u64, true);
699        }
700    }
701    if ty.width > w && v.is_signed() && bits.last() == Some(&Bit::One) {
702        acc |= mask(ty.width) ^ mask(w);
703    }
704    Ok(acc & mask(ty.width))
705}
706
707/// Converts `x` to `to` the way LRM 11.8.2 does when the *context* is `to`:
708/// an unsigned context zero-extends even a signed operand.
709fn coerce(x: Expr, to: Ty) -> Expr {
710    let from = x.ty();
711    if from == to {
712        return x;
713    }
714    if !to.signed && from.signed && to.width > from.width {
715        let unsigned = Expr::Resize(Box::new(x), Ty::unsigned(from.width));
716        return Expr::Resize(Box::new(unsigned), to);
717    }
718    Expr::Resize(Box::new(x), to)
719}
720
721fn to_bool(x: Expr) -> Expr {
722    if x.ty() == Ty::BIT {
723        return x;
724    }
725    let zero = Expr::Const(x.ty(), BigUint::zero());
726    Expr::Binary(BinOp::Ne, Box::new(x), Box::new(zero), Ty::BIT)
727}
728
729fn and_all(mut parts: Vec<Expr>) -> Expr {
730    if parts.is_empty() {
731        return Expr::Const(Ty::BIT, BigUint::one());
732    }
733    let first = parts.remove(0);
734    parts.into_iter().fold(to_bool(first), |acc, p| {
735        Expr::Binary(BinOp::LogAnd, Box::new(acc), Box::new(to_bool(p)), Ty::BIT)
736    })
737}
738
739/// `base ** exp` (LRM 11.4.3) as square-and-multiply over the exponent's
740/// bits, so the solver sees only multiplies and muxes. `base` is already in
741/// the context type `ty`; the exponent is self-determined. A negative
742/// (signed) exponent follows the LRM's table: `1 ** n` is 1, `(-1) ** n` is
743/// -1 for odd `n` and 1 for even `n`, and any other base gives 0 (integer
744/// `1 / base^|n|`; `0 ** negative` is `x` in the LRM and 0 in two-state).
745fn power(base: Expr, exp: Expr, ty: Ty) -> Expr {
746    let one = || Expr::Const(ty, BigUint::from(1u8));
747    let ew = exp.ty().width;
748    let mag_bits = if exp.ty().signed { ew - 1 } else { ew };
749    let bit = |i: u32| Expr::Slice(Box::new(exp.clone()), i, Ty::BIT);
750    let mut acc = one();
751    let mut sq = base.clone();
752    for i in 0..mag_bits {
753        acc = Expr::Ite(
754            Box::new(bit(i)),
755            Box::new(bin(BinOp::Mul, acc.clone(), sq.clone(), ty)),
756            Box::new(acc),
757            ty,
758        );
759        if i + 1 < mag_bits {
760            sq = bin(BinOp::Mul, sq.clone(), sq, ty);
761        }
762    }
763    if !exp.ty().signed {
764        return acc;
765    }
766    let is = |v: Expr| bin(BinOp::Eq, base.clone(), v, Ty::BIT);
767    let mut neg = Expr::Const(ty, BigUint::default());
768    if ty.signed {
769        let minus_one = Expr::Const(ty, (BigUint::from(1u8) << ty.width) - 1u8);
770        let alt = Expr::Ite(
771            Box::new(bit(0)),
772            Box::new(minus_one.clone()),
773            Box::new(one()),
774            ty,
775        );
776        neg = Expr::Ite(Box::new(is(minus_one)), Box::new(alt), Box::new(neg), ty);
777    }
778    neg = Expr::Ite(Box::new(is(one())), Box::new(one()), Box::new(neg), ty);
779    Expr::Ite(Box::new(bit(ew - 1)), Box::new(neg), Box::new(acc), ty)
780}
781
782fn bin(op: BinOp, l: Expr, r: Expr, ty: Ty) -> Expr {
783    Expr::Binary(op, Box::new(l), Box::new(r), ty)
784}
785
786/// Declared select geometry of a packed value, in elements of `ew` bits.
787struct Geom {
788    left: i64,
789    right: i64,
790    ew: u32,
791}
792
793impl Geom {
794    fn descending(&self) -> bool {
795        self.left >= self.right
796    }
797    fn elems(&self) -> i64 {
798        (self.left - self.right).abs() + 1
799    }
800    /// Physical element position of declared index `i`.
801    fn pos(&self, i: i64) -> i64 {
802        if self.descending() {
803            i - self.right
804        } else {
805            self.right - i
806        }
807    }
808    fn of(ty: Type<'_>, width: u32, what: &str) -> Result<Geom, BuildError> {
809        if ty.is_packed_array() {
810            let r = ty.packed_array_range();
811            let ew = ty
812                .element_type()
813                .map_or(1, |e| u32::try_from(e.bit_width()).unwrap_or(1).max(1));
814            if u64::from(r.width()) * u64::from(ew) == u64::from(width) {
815                return Ok(Geom {
816                    left: i64::from(r.left),
817                    right: i64::from(r.right),
818                    ew,
819                });
820            }
821        } else if ty.is_integral() && !ty.is_struct() {
822            let r = ty.bit_vector_range();
823            if r.width() == width {
824                return Ok(Geom {
825                    left: i64::from(r.left),
826                    right: i64::from(r.right),
827                    ew: 1,
828                });
829            }
830        }
831        Err(unsupported(
832            format!("a select into {what} of type `{}`", ty.to_sv_string()),
833            "C11",
834        ))
835    }
836}
837
838/// The uids along a handle chain `a.b.c` (`[a, b, c]`), for matching a
839/// captured member read to its occurrence in the constraint ([`CaptureInfo::hops`]).
840pub fn handle_path_uids(e: Expression<'_>) -> Option<Vec<SymbolId>> {
841    Builder::handle_path(e)
842        .ok()
843        .map(|p| p.iter().map(|s| s.uid()).collect())
844}
845
846impl<'t> Builder<'t> {
847    /// A builder reading `soft` markers from `trees` (the design's syntax
848    /// trees; `soft` is not exposed by the AST, plan decision E7).
849    pub fn new(trees: &'t [SyntaxTree]) -> Self {
850        Builder {
851            trees,
852            vars: Vec::new(),
853            var_ix: HashMap::new(),
854            pattern_binds: HashMap::new(),
855            elem_ix: HashMap::new(),
856            forced_roots: HashSet::new(),
857            real_vars: Vec::new(),
858            real_var_ix: HashMap::new(),
859            real_leaf_ix: HashMap::new(),
860            leaf_state_ix: HashMap::new(),
861            real_conds: Vec::new(),
862            real_dists: Vec::new(),
863            real_before: Vec::new(),
864            states: Vec::new(),
865            state_ix: HashMap::new(),
866            slot_of: None,
867            virtual_impl: None,
868            virtual_targets: None,
869            conjuncts: Vec::new(),
870            before: Vec::new(),
871            dists: Vec::new(),
872            arrays: Vec::new(),
873            array_ix: HashMap::new(),
874            templates: HashMap::new(),
875            loops: Vec::new(),
876            next_loop: 0,
877            no_arrays: None,
878            funcs: Vec::new(),
879            func_ix: HashMap::new(),
880            fn_base: None,
881            frame: None,
882            softs: Vec::new(),
883            disables: Vec::new(),
884            scope: (0, 0),
885            seq: 0,
886            guard_depth: 0,
887            local_of: None,
888            captures: Vec::new(),
889            capture_ix: HashMap::new(),
890            blocks: Vec::new(),
891        }
892    }
893
894    /// Puts the builder in *inline-constraint* mode (constraints plan C19, LRM
895    /// 18.7): the constraints fed in next are those of a `randomize() with
896    /// {...}` call, and `classify` says which names they read resolve in the
897    /// scope of the call (a local variable, a subroutine argument, a property
898    /// of the calling class, or anything `local::`-qualified) and not in the
899    /// randomized object's class. Each such local becomes a *captured* state
900    /// variable ([`ConstraintSet::captures`]): a constant of the solve whose
901    /// value the call supplies. Only integral locals can be read. A `disable
902    /// soft` is recorded ([`ConstraintSet::disable_keys`]) and reaches the
903    /// object's own soft constraints when the fragment is overlaid.
904    pub fn set_local_classifier(&mut self, classify: impl for<'s> Fn(Expression<'s>) -> bool + 't) {
905        self.local_of = Some(Box::new(classify));
906    }
907
908    /// Whether `e` names a local of an inline constraint's call site (LRM 18.7).
909    /// Never inside a function body: its names resolve in the function's own
910    /// scope (and its class's), which the call's scope does not reach.
911    pub(super) fn is_call_local(&self, e: Expression<'_>) -> bool {
912        self.frame.is_none() && self.local_of.as_ref().is_some_and(|c| c(e))
913    }
914
915    /// The state variable standing for the call-site local `sym` of type `ty`,
916    /// registered on first use.
917    fn capture_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Expr {
918        self.capture_path(sym, &[], ty)
919    }
920
921    /// The state variable for the call-site value `sym.hops...` (a local, or a
922    /// member read through a local object handle).
923    fn capture_path(&mut self, sym: Symbol<'_>, hops: &[Symbol<'_>], ty: Ty) -> Expr {
924        let uid = sym.uid();
925        let key: Vec<SymbolId> = std::iter::once(uid.clone())
926            .chain(hops.iter().map(|h| h.uid()))
927            .collect();
928        if let Some(&ix) = self.capture_ix.get(&key) {
929            let state = self.captures[ix].state;
930            return Expr::State(state, self.states[state as usize].ty);
931        }
932        let name = hops
933            .iter()
934            .fold(sym.name().to_string(), |n, h| format!("{n}.{}", h.name()));
935        let state = self.states.len() as u32;
936        self.states.push(StateInfo {
937            root_bit: 0,
938            symbol: uid.clone(),
939            name: name.clone(),
940            ty,
941            via: Vec::new(),
942            window: None,
943            class_of: false,
944        });
945        self.capture_ix.insert(key, self.captures.len());
946        self.captures.push(CaptureInfo {
947            state,
948            symbol: uid,
949            hops: hops.iter().map(|h| h.uid()).collect(),
950            name,
951            ty,
952        });
953        Expr::State(state, ty)
954    }
955
956    /// Sets the priority scope of the constraints added from now on (LRM
957    /// 18.5.13.1): the rank of the object they belong to (an object a `rand`
958    /// handle contains ranks below its container, see
959    /// [`priority::object_order`]) and their class level (a derived class's
960    /// level is above its bases'). Within a scope a later soft constraint
961    /// is higher; the default scope is `(0, 0)`.
962    pub fn set_priority_scope(&mut self, object: u32, level: u32) {
963        self.scope = (object, level);
964    }
965
966    fn next_priority(&mut self) -> Priority {
967        let p = Priority {
968            object: self.scope.0,
969            level: self.scope.1,
970            seq: self.seq,
971        };
972        self.seq += 1;
973        p
974    }
975
976    /// The soft constraint `body` (already built; `dists` is the range of
977    /// [`Self::dists`] it created), recorded and wrapped in its discard flag.
978    fn make_soft(&mut self, body: Expr, dists: std::ops::Range<usize>) -> Result<Expr, BuildError> {
979        if self.in_runtime_loop() || self.frame.is_some() {
980            return Err(BuildError::BadSoft {
981                what: "a `soft` constraint inside a `foreach` over a dynamic array or queue is \
982                       not supported: it would be one soft constraint per run-time element"
983                    .into(),
984            });
985        }
986        let dist_refs: Vec<&Dist> = self.dists[dists.clone()].iter().collect();
987        let keys = priority::keys_of(&body, &dist_refs);
988        for k in &keys {
989            if let SoftKey::Var(v) = k
990                && self.vars[*v as usize].randc
991            {
992                return Err(BuildError::BadSoft {
993                    what: format!(
994                        "a `soft` constraint on the `randc` variable `{}` (soft constraints \
995                         apply to `rand` variables only)",
996                        self.vars[*v as usize].name
997                    ),
998                });
999            }
1000        }
1001        let id = self.softs.len() as u32;
1002        let priority = self.next_priority();
1003        self.softs.push(Soft {
1004            priority,
1005            dists: dists.map(|d| d as u32).collect(),
1006            keys,
1007            disabled: false,
1008            disabled_by: Vec::new(),
1009        });
1010        Ok(priority::wrap_soft(id, body))
1011    }
1012
1013    /// `disable soft target;` (18.5.13.2).
1014    fn disable_soft(&mut self, c: Constraint<'_>) -> Result<(), BuildError> {
1015        if self.guard_depth > 0 || self.in_runtime_loop() {
1016            return Err(BuildError::BadSoft {
1017                what: "a `disable soft` inside an implication, an `if` or a `foreach` over a \
1018                       dynamic array or queue is not supported (it discards unconditionally)"
1019                    .into(),
1020            });
1021        }
1022        let target = c
1023            .children()
1024            .into_iter()
1025            .find_map(|n| n.as_expression())
1026            .ok_or_else(|| BuildError::Missing {
1027                what: "target expression of `disable soft`".into(),
1028            })?;
1029        let key = if let Some(arr) = Self::size_call_array(target) {
1030            SoftKey::ArraySize(self.array_of(arr)?)
1031        } else if let Some(chain) = aggregate::aggregate_chain(target, &self.forced_roots)
1032            && !chain.steps.is_empty()
1033        {
1034            let (leaf, declared) = self.declared_leaf(chain, "the target of `disable soft`")?;
1035            match declared {
1036                Some(Declared::Int(id)) => SoftKey::Var(id),
1037                Some(Declared::Array(_)) | Some(Declared::Real(_)) => {
1038                    return Err(BuildError::BadSoft {
1039                        what: "`disable soft` of a real variable is not supported (soft \
1040                               constraints are relaxed over integral variables only)"
1041                            .into(),
1042                    });
1043                }
1044                None => {
1045                    return Err(BuildError::BadSoft {
1046                        what: format!(
1047                            "`disable soft` names `{}`, which is not a `rand` variable of this \
1048                             class",
1049                            leaf.name
1050                        ),
1051                    });
1052                }
1053            }
1054        } else {
1055            let sym = match target.kind() {
1056                ExpressionKind::NamedValue => target.referenced_symbol(),
1057                ExpressionKind::MemberAccess => {
1058                    let base = Self::operand(target, "a member access")?;
1059                    let is_this = base.kind() == ExpressionKind::NamedValue
1060                        && base.referenced_symbol().is_some_and(|s| s.name() == "this");
1061                    if !is_this {
1062                        return Err(BuildError::ForeignObjectAccess {
1063                            what: "the target of `disable soft`".into(),
1064                        });
1065                    }
1066                    target.member_symbol()
1067                }
1068                other => {
1069                    return Err(BuildError::BadSoft {
1070                        what: format!(
1071                            "a `{other:?}` expression as the target of `disable soft` (name a \
1072                             whole variable, `this.v` or `A.size`)"
1073                        ),
1074                    });
1075                }
1076            };
1077            let sym = sym.ok_or_else(|| BuildError::Missing {
1078                what: "symbol of the target of `disable soft`".into(),
1079            })?;
1080            let uid = sym.uid();
1081            if let Some(&id) = self.var_ix.get(&uid) {
1082                SoftKey::Var(id)
1083            } else if self.array_ix.contains_key(&uid) {
1084                SoftKey::Array(self.array_of(target)?)
1085            } else if self.real_var_ix.contains_key(&uid) {
1086                return Err(BuildError::BadSoft {
1087                    what: "`disable soft` of a real variable is not supported (soft constraints \
1088                           are relaxed over integral variables only)"
1089                        .into(),
1090                });
1091            } else {
1092                return Err(BuildError::BadSoft {
1093                    what: format!(
1094                        "`disable soft` names `{}`, which is not a `rand` variable of this class",
1095                        sym.name()
1096                    ),
1097                });
1098            }
1099        };
1100        let priority = self.next_priority();
1101        self.disables.push(Disable { priority, key });
1102        Ok(())
1103    }
1104
1105    /// Lets constraints read members of *other* objects through non-`rand`
1106    /// handles (`a.x`, `next.n`; LRM 18.5.12): each such read becomes a state
1107    /// variable whose [`StateInfo::via`] path `resolver` places. Without a
1108    /// resolver, such a read is [`BuildError::ForeignObjectAccess`].
1109    pub fn set_slot_resolver(
1110        &mut self,
1111        resolver: impl for<'s> Fn(Symbol<'s>) -> Result<FieldSlot, String> + 't,
1112    ) {
1113        self.slot_of = Some(Box::new(resolver));
1114    }
1115
1116    /// Tells the builder which function a call to a `virtual` function runs: the
1117    /// constraints are built for one concrete class, so the override that
1118    /// class (or its nearest base) declares is the one an object of it calls
1119    /// (LRM 8.20). `resolver` maps the statically named function to that
1120    /// implementation, or `None` when it cannot. Without a resolver, a call to
1121    /// a virtual function is refused.
1122    pub fn set_virtual_resolver(
1123        &mut self,
1124        resolver: impl for<'s> Fn(Symbol<'s>) -> Option<Symbol<'t>> + 't,
1125    ) {
1126        self.virtual_impl = Some(Box::new(resolver));
1127    }
1128
1129    /// Tells the builder, for a call to a `virtual` function through an object
1130    /// handle (the object's dynamic class is known only at solve time, LRM
1131    /// 8.20), every `(template id, implementation)` pair of the classes a handle
1132    /// of the given static class can name. The call is then built as a
1133    /// run-time pick among the implementations on the object's template id.
1134    pub fn set_virtual_targets(
1135        &mut self,
1136        resolver: impl for<'s> Fn(Symbol<'s>, Symbol<'s>) -> Vec<(u32, Symbol<'t>)> + 't,
1137    ) {
1138        self.virtual_targets = Some(Box::new(resolver));
1139    }
1140
1141    /// Registers `sym` as an active random variable and returns its id
1142    /// (idempotent).
1143    pub fn declare_rand(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
1144        let uid = sym.uid();
1145        if let Some(&id) = self.var_ix.get(&uid) {
1146            return Ok(id);
1147        }
1148        let name = sym.name().to_string();
1149        let randc = match sym.rand_mode() {
1150            RandMode::None => return Err(BuildError::NotRand { name }),
1151            RandMode::RandC => true,
1152            RandMode::Rand => false,
1153        };
1154        let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
1155            what: format!("value type for `{name}`"),
1156        })?;
1157        let id = self.new_rand_var(uid.clone(), name, vt, randc, Vec::new(), None)?;
1158        self.var_ix.insert(uid, id);
1159        Ok(id)
1160    }
1161
1162    /// Registers `sym` as a random variable whether or not it is declared
1163    /// `rand` (LRM 18.11: an argument of `randomize(v1, v2)` is random for that
1164    /// call). The argument list does not change the cyclic nature of a `randc`
1165    /// property, which stays `randc`; a non-`rand` property is never `randc`.
1166    /// Idempotent, and returns the id [`Self::declare_rand`] would also have
1167    /// given a `rand` property.
1168    pub fn declare_var_forced(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
1169        let uid = sym.uid();
1170        if let Some(&id) = self.var_ix.get(&uid) {
1171            return Ok(id);
1172        }
1173        let name = sym.name().to_string();
1174        let randc = matches!(sym.rand_mode(), RandMode::RandC);
1175        let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
1176            what: format!("value type for `{name}`"),
1177        })?;
1178        let id = self.new_rand_var(uid.clone(), name, vt, randc, Vec::new(), None)?;
1179        self.var_ix.insert(uid, id);
1180        Ok(id)
1181    }
1182
1183    /// The named constants of an enum type `vt` as values of `ty` (LRM 18.3:
1184    /// a `rand` enum only takes these), or `None` for any other type.
1185    fn enum_values(vt: Type<'_>, ty: Ty) -> Result<Option<Vec<BigUint>>, BuildError> {
1186        if !vt.is_enum() {
1187            return Ok(None);
1188        }
1189        let mut vals = Vec::new();
1190        for m in vt.canonical().enum_members() {
1191            let cv = m.constant_value().ok_or_else(|| BuildError::Missing {
1192                what: format!("value of enum member `{}`", m.name()),
1193            })?;
1194            let i = cv.as_integer().ok_or_else(|| BuildError::Missing {
1195                what: format!("integer value of enum member `{}`", m.name()),
1196            })?;
1197            vals.push(svint_to_biguint(i, ty)?);
1198        }
1199        Ok(Some(vals))
1200    }
1201
1202    /// Appends a random variable of type `vt` (its declaring symbol's key
1203    /// `uid`; `path` and `window` place it when it is a leaf of an aggregate
1204    /// property, and are empty / `None` for a property itself).
1205    fn new_rand_var(
1206        &mut self,
1207        uid: SymbolId,
1208        name: String,
1209        vt: Type<'_>,
1210        randc: bool,
1211        path: Vec<Step>,
1212        window: Option<Window>,
1213    ) -> Result<u32, BuildError> {
1214        if vt.is_virtual_interface() {
1215            return Err(BuildError::VirtualInterfaceRand { name });
1216        }
1217        let ty = ty_of(Some(vt), &format!("`rand` variable `{name}`"))?;
1218        if randc && ty.width > MAX_RANDC_WIDTH {
1219            return Err(BuildError::RandcTooWide {
1220                name,
1221                width: ty.width,
1222                max: MAX_RANDC_WIDTH,
1223            });
1224        }
1225        let enum_domain = Self::enum_values(vt, ty)?;
1226        let id = self.vars.len() as u32;
1227        self.vars.push(VarInfo {
1228            symbol: uid,
1229            name,
1230            ty,
1231            enum_domain,
1232            randc,
1233            path,
1234            window,
1235        });
1236        Ok(id)
1237    }
1238
1239    /// Adds every constraint of the named constraint block `block`.
1240    pub fn add_block(&mut self, block: Symbol<'_>) -> Result<(), BuildError> {
1241        let start = self.conjuncts.len();
1242        let from = [
1243            self.dists.len(),
1244            self.softs.len(),
1245            self.before.len(),
1246            self.real_conds.len(),
1247            self.real_dists.len(),
1248            self.real_before.len(),
1249            self.disables.len(),
1250        ];
1251        self.add_block_body(block)?;
1252        self.blocks.push(BlockInfo {
1253            conjuncts: start..self.conjuncts.len(),
1254            items: BlockItems {
1255                dists: from[0]..self.dists.len(),
1256                softs: from[1]..self.softs.len(),
1257                before: from[2]..self.before.len(),
1258                real_conds: from[3]..self.real_conds.len(),
1259                real_dists: from[4]..self.real_dists.len(),
1260                real_before: from[5]..self.real_before.len(),
1261                disables: from[6]..self.disables.len(),
1262            },
1263        });
1264        Ok(())
1265    }
1266
1267    fn add_block_body(&mut self, block: Symbol<'_>) -> Result<(), BuildError> {
1268        let tree = block
1269            .constraint_block_constraints()
1270            .ok_or_else(|| BuildError::Missing {
1271                what: format!("bound body for constraint block `{}`", block.name()),
1272            })?;
1273        let c = tree.as_constraint().ok_or_else(|| BuildError::Missing {
1274            what: format!("constraint body of block `{}`", block.name()),
1275        })?;
1276        // A constraint prototype whose external block was never provided
1277        // (18.5.1): the explicit form `extern constraint p;` is an error, the
1278        // implicit form `constraint p;` is an empty constraint (one with no
1279        // effect on randomization, "equivalent to a constraint block
1280        // containing the constant expression 1"). `pure` prototypes carry no
1281        // body by definition and are skipped by the caller.
1282        let flags = block.constraint_block_flags();
1283        if c.kind() == ConstraintKind::Invalid
1284            && flags.contains(ConstraintBlockFlags::EXTERN)
1285            && !flags.contains(ConstraintBlockFlags::PURE)
1286        {
1287            if flags.contains(ConstraintBlockFlags::EXPLICIT_EXTERN) {
1288                return Err(BuildError::MissingExternBody {
1289                    name: block.name().to_string(),
1290                });
1291            }
1292            return Ok(());
1293        }
1294        self.add_constraint(c)
1295    }
1296
1297    /// Adds one constraint (a block body, a list, or a single item).
1298    pub fn add_constraint(&mut self, c: Constraint<'_>) -> Result<(), BuildError> {
1299        let mut out = self.constraint(c)?;
1300        self.conjuncts.append(&mut out);
1301        Ok(())
1302    }
1303
1304    /// Finishes: appends the implicit enum-domain conjuncts and validates.
1305    pub fn finish(mut self) -> Result<ConstraintSet, BuildError> {
1306        let mut domain_conjunct = vec![None; self.vars.len()];
1307        for (i, v) in self.vars.iter().enumerate() {
1308            let Some(domain) = &v.enum_domain else {
1309                continue;
1310            };
1311            let var = Expr::Var(i as u32, v.ty);
1312            let mut any: Option<Expr> = None;
1313            for val in domain {
1314                let eq = bin(
1315                    BinOp::Eq,
1316                    var.clone(),
1317                    Expr::Const(v.ty, val.clone()),
1318                    Ty::BIT,
1319                );
1320                any = Some(match any {
1321                    None => eq,
1322                    Some(prev) => bin(BinOp::LogOr, prev, eq, Ty::BIT),
1323                });
1324            }
1325            if let Some(e) = any {
1326                domain_conjunct[i] = Some(self.conjuncts.len());
1327                self.conjuncts.push(e);
1328            }
1329        }
1330        // `rand int b[][4]` (LRM 7.4.5): every row is exactly 4 elements, as
1331        // `foreach (b[i]) b[i].size == 4` would say.
1332        for outer in 0..self.arrays.len() as u32 {
1333            let n = self.arrays[outer as usize].inner_fixed;
1334            if n == 0 {
1335                continue;
1336            }
1337            let int = Ty::signed(32);
1338            // `ts[k]` is the array dimension `k` loops (the array itself, then
1339            // the row template of each level); the last holds the fixed rows.
1340            let depth = self.arrays[outer as usize].row_depth as usize;
1341            let mut ts = vec![outer];
1342            for _ in 0..depth {
1343                let t = self.row_template(*ts.last().expect("one array"));
1344                ts.push(t);
1345            }
1346            let loops: Vec<u32> = (0..depth).map(|_| self.fresh_loop()).collect();
1347            let size = Expr::Array(ArrayNode::Size(ts[depth]));
1348            let mut e = bin(BinOp::Eq, size, Expr::Const(int, BigUint::from(n)), Ty::BIT);
1349            for k in (0..depth).rev() {
1350                e = Expr::Array(ArrayNode::RowScope {
1351                    template: ts[k + 1],
1352                    row: Box::new(Expr::Array(ArrayNode::Loop(loops[k], int))),
1353                    body: Box::new(e),
1354                });
1355                e = Expr::Array(ArrayNode::Foreach {
1356                    array: ts[k],
1357                    var: loops[k],
1358                    body: Box::new(e),
1359                });
1360            }
1361            self.conjuncts.push(e);
1362        }
1363        for c in &self.conjuncts {
1364            c.validate()?;
1365        }
1366        for d in &self.dists {
1367            d.validate()?;
1368        }
1369        order::levels(self.vars.len(), &self.before)?;
1370        let (real_vars, real) = self.finish_real()?;
1371        priority::apply_disables(&mut self.softs, &self.disables);
1372        Ok(ConstraintSet {
1373            vars: self.vars,
1374            states: self.states,
1375            conjuncts: self.conjuncts,
1376            domain_conjunct,
1377            before: self.before,
1378            dists: self.dists,
1379            real_vars,
1380            real,
1381            arrays: self.arrays,
1382            funcs: self.funcs,
1383            softs: self.softs,
1384            captures: self.captures,
1385            blocks: self.blocks,
1386            disables: self.disables.len(),
1387            disable_keys: self.disables.iter().map(|d| d.key).collect(),
1388        })
1389    }
1390
1391    /// How many of a `solve ... before` constraint's operands are on the
1392    /// `solve` side. The semantic node lists both sides' expressions in one
1393    /// run, so the split comes from the syntax.
1394    fn solve_side_len(&self, c: Constraint<'_>) -> Result<usize, BuildError> {
1395        for tree in self.trees {
1396            if let Some(node) = c.syntax(tree) {
1397                let s =
1398                    SolveBeforeConstraintSyntax::cast(node).ok_or_else(|| BuildError::Missing {
1399                        what: "`solve ... before` syntax for a SolveBefore constraint".into(),
1400                    })?;
1401                return Ok(s.before_expr().iter().count());
1402            }
1403        }
1404        Err(BuildError::Missing {
1405            what: "syntax node for a constraint (is its syntax tree passed to the builder?)".into(),
1406        })
1407    }
1408
1409    /// The random variable a `solve ... before` operand names (LRM 18.5.9:
1410    /// `rand` only, integral or real; a `randc` variable is always solved
1411    /// first and is not allowed here).
1412    fn solve_operand(&mut self, e: Expression<'_>) -> Result<SolveOperand, BuildError> {
1413        // A member of an unpacked struct or an element of an unpacked array
1414        // property, at constant indices.
1415        if let Some(chain) = aggregate::aggregate_chain(e, &self.forced_roots)
1416            && !chain.steps.is_empty()
1417        {
1418            let (leaf, declared) = self.declared_leaf(chain, "a `solve ... before` operand")?;
1419            if leaf.randc() {
1420                return Err(BuildError::BadSolveOperand {
1421                    what: format!(
1422                        "`{}` is `randc`, which is always solved before every `rand` variable",
1423                        leaf.name
1424                    ),
1425                });
1426            }
1427            return match declared {
1428                Some(Declared::Int(id)) => Ok(SolveOperand::Int(id)),
1429                Some(Declared::Real(id)) => Ok(SolveOperand::Real(id)),
1430                Some(Declared::Array(_)) => Err(BuildError::BadSolveOperand {
1431                    what: format!("`{}` is an array, not a scalar random variable", leaf.name),
1432                }),
1433                None => Err(BuildError::BadSolveOperand {
1434                    what: format!("`{}` is not a `rand` variable of this class", leaf.name),
1435                }),
1436            };
1437        }
1438        let sym = match e.kind() {
1439            ExpressionKind::NamedValue => e.referenced_symbol(),
1440            ExpressionKind::MemberAccess => {
1441                let base = Self::operand(e, "a member access")?;
1442                let is_this = base.kind() == ExpressionKind::NamedValue
1443                    && base.referenced_symbol().is_some_and(|s| s.name() == "this");
1444                if !is_this {
1445                    return Err(BuildError::ForeignObjectAccess {
1446                        what: format!(
1447                            "`solve ... before` operand `.{}`",
1448                            e.member_symbol()
1449                                .map_or(String::new(), |m| m.name().to_string())
1450                        ),
1451                    });
1452                }
1453                e.member_symbol()
1454            }
1455            ExpressionKind::Call => {
1456                let Some(arr) = Self::size_call_array(e) else {
1457                    return Err(unsupported(
1458                        "a call other than `array.size` as a `solve ... before` operand",
1459                        "C11",
1460                    ));
1461                };
1462                self.arrays_allowed("an array size")?;
1463                self.array_of(arr)?;
1464                return Ok(SolveOperand::Size);
1465            }
1466            other => {
1467                return Err(unsupported(
1468                    format!("a `{other:?}` expression as a `solve ... before` operand"),
1469                    "C11",
1470                ));
1471            }
1472        };
1473        let sym = sym.ok_or_else(|| BuildError::Missing {
1474            what: "symbol of a `solve ... before` operand".into(),
1475        })?;
1476        let name = sym.name().to_string();
1477        if sym.rand_mode() == RandMode::RandC {
1478            return Err(BuildError::BadSolveOperand {
1479                what: format!(
1480                    "`{name}` is `randc`, which is always solved before every `rand` variable"
1481                ),
1482            });
1483        }
1484        let uid = sym.uid();
1485        if let Some(&id) = self.var_ix.get(&uid) {
1486            return Ok(SolveOperand::Int(id));
1487        }
1488        match self.real_var_ix.get(&uid) {
1489            Some(&id) => Ok(SolveOperand::Real(id)),
1490            None => Err(BuildError::BadSolveOperand {
1491                what: format!("`{name}` is not a `rand` variable of this class"),
1492            }),
1493        }
1494    }
1495
1496    fn is_soft(&self, c: Constraint<'_>) -> Result<bool, BuildError> {
1497        for tree in self.trees {
1498            if let Some(node) = c.syntax(tree) {
1499                return Ok(
1500                    ExpressionConstraintSyntax::cast(node).is_some_and(|s| s.soft().is_some())
1501                );
1502            }
1503        }
1504        Err(BuildError::Missing {
1505            what: "syntax node for a constraint (is its syntax tree passed to the builder?)".into(),
1506        })
1507    }
1508
1509    fn constraint(&mut self, c: Constraint<'_>) -> Result<Vec<Expr>, BuildError> {
1510        fn kids<'d>(c: Constraint<'d>) -> Vec<Constraint<'d>> {
1511            c.children()
1512                .into_iter()
1513                .filter_map(|n| n.as_constraint())
1514                .collect()
1515        }
1516        match c.kind() {
1517            ConstraintKind::List => {
1518                let mut out = Vec::new();
1519                for k in kids(c) {
1520                    out.extend(self.constraint(k)?);
1521                }
1522                Ok(out)
1523            }
1524            ConstraintKind::Expression => {
1525                let soft = self.is_soft(c)?;
1526                let e = c.expr().ok_or_else(|| BuildError::Missing {
1527                    what: "expression of an expression constraint".into(),
1528                })?;
1529                if soft
1530                    && (real::reads_real(e)
1531                        || (e.kind() == ExpressionKind::Dist && real::dist_is_real(e)))
1532                {
1533                    return Err(BuildError::BadSoft {
1534                        what: "a `soft` constraint over a real value is not supported (soft \
1535                               constraints are relaxed over integral variables only)"
1536                            .into(),
1537                    });
1538                }
1539                let dists_from = self.dists.len();
1540                let built = if e.kind() == ExpressionKind::Dist {
1541                    if real::dist_is_real(e) {
1542                        self.real_dist(e)?;
1543                        return Ok(Vec::new());
1544                    }
1545                    self.dist(e)?
1546                } else {
1547                    if real::reads_real(e) {
1548                        let cond = self.real_cond(e)?;
1549                        self.real_conds.push(cond);
1550                        return Ok(Vec::new());
1551                    }
1552                    to_bool(self.expr(e)?)
1553                };
1554                if soft {
1555                    let dists = dists_from..self.dists.len();
1556                    return Ok(vec![self.make_soft(built, dists)?]);
1557                }
1558                Ok(vec![built])
1559            }
1560            // A guard that reads a real value is built whole over the real
1561            // namespace (its arms may not mix in integral variables).
1562            ConstraintKind::Implication | ConstraintKind::Conditional
1563                if real::constraint_reads_real(c) =>
1564            {
1565                let cond = self.real_constraint(c)?;
1566                self.real_conds.push(cond);
1567                Ok(Vec::new())
1568            }
1569            ConstraintKind::Implication => {
1570                let p = c.predicate().ok_or_else(|| BuildError::Missing {
1571                    what: "predicate of an implication".into(),
1572                })?;
1573                let pred = to_bool(self.expr(p)?);
1574                let mut body = Vec::new();
1575                self.guard_depth += 1;
1576                for k in kids(c) {
1577                    match self.constraint(k) {
1578                        Ok(b) => body.extend(b),
1579                        Err(e) => {
1580                            self.guard_depth -= 1;
1581                            return Err(e);
1582                        }
1583                    }
1584                }
1585                self.guard_depth -= 1;
1586                // A guard, not a plain `->`: its predicate follows the
1587                // LRM 18.5.12 three-valued evaluation.
1588                Ok(vec![Expr::Guard(
1589                    Box::new(pred),
1590                    Box::new(and_all(body)),
1591                    Box::new(Expr::Const(Ty::BIT, BigUint::one())),
1592                    Ty::BIT,
1593                )])
1594            }
1595            ConstraintKind::Conditional => {
1596                let p = c.predicate().ok_or_else(|| BuildError::Missing {
1597                    what: "predicate of an if constraint".into(),
1598                })?;
1599                let pred = to_bool(self.expr(p)?);
1600                let arms = kids(c);
1601                if arms.is_empty() || arms.len() > 2 {
1602                    return Err(BuildError::Missing {
1603                        what: format!("one or two arms of an if constraint (got {})", arms.len()),
1604                    });
1605                }
1606                self.guard_depth += 1;
1607                let arms_built = arms
1608                    .iter()
1609                    .map(|a| self.constraint(*a).map(and_all))
1610                    .collect::<Result<Vec<_>, _>>();
1611                self.guard_depth -= 1;
1612                let mut arms_built = arms_built?.into_iter();
1613                let then_c = arms_built.next().ok_or_else(|| BuildError::Missing {
1614                    what: "the then arm of an if constraint".into(),
1615                })?;
1616                let else_c = arms_built
1617                    .next()
1618                    .unwrap_or_else(|| Expr::Const(Ty::BIT, BigUint::one()));
1619                Ok(vec![Expr::Guard(
1620                    Box::new(pred),
1621                    Box::new(then_c),
1622                    Box::new(else_c),
1623                    Ty::BIT,
1624                )])
1625            }
1626            ConstraintKind::Uniqueness => self.unique(c),
1627            ConstraintKind::SolveBefore => {
1628                // No boolean constraint: only the solve order (LRM 18.5.9:
1629                // ordering never changes the solution space).
1630                let split = self.solve_side_len(c)?;
1631                let mut ids = Vec::new();
1632                for k in c.children() {
1633                    let e = k.as_expression().ok_or_else(|| BuildError::Missing {
1634                        what: "expression operand of `solve ... before`".into(),
1635                    })?;
1636                    ids.push(self.solve_operand(e)?);
1637                }
1638                if split > ids.len() {
1639                    return Err(BuildError::Missing {
1640                        what: "operands of `solve ... before` matching its syntax".into(),
1641                    });
1642                }
1643                let (first, later) = ids.split_at(split);
1644                for &a in first {
1645                    for &b in later {
1646                        match (a, b) {
1647                            (SolveOperand::Int(a), SolveOperand::Int(b)) => {
1648                                self.before.push((a, b));
1649                            }
1650                            (SolveOperand::Real(a), SolveOperand::Real(b)) => {
1651                                self.real_before.push((a, b));
1652                            }
1653                            // LRM 18.4: the sizes are decided first, as one
1654                            // sub-problem (`array::solve_sizes`), before any
1655                            // element or scalar is drawn. `solve A.size before
1656                            // x` is therefore the order the solver already
1657                            // has; the reverse, and an order between two
1658                            // sizes, would change the distribution and is not
1659                            // modelled, so it fails loudly.
1660                            (SolveOperand::Size, SolveOperand::Int(_) | SolveOperand::Real(_)) => {}
1661                            (SolveOperand::Int(_) | SolveOperand::Real(_), SolveOperand::Size)
1662                            | (SolveOperand::Size, SolveOperand::Size) => {
1663                                return Err(unsupported(
1664                                    "`solve ... before` placing a variable or another array size \
1665                                     before an array `.size()`",
1666                                    "array sizes are always solved first, as one joint sub-problem",
1667                                ));
1668                            }
1669                            // The integral variables are always solved first
1670                            // and nothing couples the two namespaces, so an
1671                            // order between them changes nothing (18.5.9).
1672                            _ => {}
1673                        }
1674                    }
1675                }
1676                Ok(Vec::new())
1677            }
1678            ConstraintKind::Invalid => Err(BuildError::Missing {
1679                what: "well-formed constraint (the frontend diagnosed an error in it)".into(),
1680            }),
1681            ConstraintKind::DisableSoft => {
1682                // No boolean constraint: it discards softs (see `finish`).
1683                self.disable_soft(c)?;
1684                Ok(Vec::new())
1685            }
1686            ConstraintKind::Foreach => self.foreach(c),
1687            other => Err(unsupported(format!("constraint kind {other:?}"), "C2")),
1688        }
1689    }
1690
1691    fn symbol_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Result<Expr, BuildError> {
1692        // A property read by a function called through a handle belongs to the
1693        // object that handle names, never to the randomized one.
1694        if self.frame.is_some()
1695            && sym.kind() == SymbolKind::ClassProperty
1696            && let Some(base) = self.fn_base.clone()
1697        {
1698            return self.based_state(&base, &[sym], ty);
1699        }
1700        let uid = sym.uid();
1701        if let Some(x) = self.pattern_binds.get(&uid) {
1702            return Ok(x.clone());
1703        }
1704        if let Some(&id) = self.var_ix.get(&uid) {
1705            if self.frame.is_some() {
1706                return Err(self.rand_read_in_function(sym.name()));
1707            }
1708            return Ok(Expr::Var(id, self.vars[id as usize].ty));
1709        }
1710        self.state_ref(sym, &[], 0, ty)
1711    }
1712
1713    /// The state variable for `root` (a property of the randomized class)
1714    /// followed through the handle members `hops`, registered on first use.
1715    fn state_ref(
1716        &mut self,
1717        root: Symbol<'_>,
1718        hops: &[Symbol<'_>],
1719        root_bit: u32,
1720        ty: Ty,
1721    ) -> Result<Expr, BuildError> {
1722        let key: Vec<SymbolId> = std::iter::once(root.uid())
1723            .chain(hops.iter().map(|h| h.uid()))
1724            .collect();
1725        let key = (key, root_bit);
1726        if let Some(&id) = self.state_ix.get(&key) {
1727            return Ok(Expr::State(id, self.states[id as usize].ty));
1728        }
1729        let mut name = root.name().to_string();
1730        let mut via = Vec::with_capacity(hops.len());
1731        for h in hops {
1732            name = format!("{name}.{}", h.name());
1733            let resolver =
1734                self.slot_of
1735                    .as_ref()
1736                    .ok_or_else(|| BuildError::ForeignObjectAccess {
1737                        what: format!(
1738                            "read of `{name}` (this builder was given no object layout to place \
1739                         another object's members)"
1740                        ),
1741                    })?;
1742            let slot = resolver(*h).map_err(|why| BuildError::Missing {
1743                what: format!("field slot of `{name}`: {why}"),
1744            })?;
1745            via.push(StateHop {
1746                symbol: h.uid(),
1747                slot,
1748            });
1749        }
1750        let id = self.states.len() as u32;
1751        self.states.push(StateInfo {
1752            symbol: root.uid(),
1753            name,
1754            ty,
1755            via,
1756            window: None,
1757            root_bit,
1758            class_of: false,
1759        });
1760        self.state_ix.insert(key, id);
1761        Ok(Expr::State(id, ty))
1762    }
1763
1764    /// The named value a handle chain starts at (`o` in `o.a.b`), unless it
1765    /// is `this`.
1766    fn local_handle_root<'d>(mut e: Expression<'d>) -> Option<Expression<'d>> {
1767        // A predicate, not a lowering: any other expression kind is simply not
1768        // a handle chain with a named root.
1769        loop {
1770            let kind = e.kind();
1771            if kind == ExpressionKind::MemberAccess {
1772                e = e.left()?;
1773            } else if kind == ExpressionKind::Conversion {
1774                e = e.conversion_operand()?;
1775            } else if kind == ExpressionKind::NamedValue
1776                || kind == ExpressionKind::HierarchicalValue
1777            {
1778                return e
1779                    .referenced_symbol()
1780                    .is_some_and(|s| s.name() != "this")
1781                    .then_some(e);
1782            } else {
1783                return None;
1784            }
1785        }
1786    }
1787
1788    /// The members from a handle chain's root property to its end:
1789    /// `a.b.c` is `[a, b, c]`; a leading `this.` is transparent (LRM 8.11).
1790    fn handle_path<'d>(e: Expression<'d>) -> Result<Vec<Symbol<'d>>, BuildError> {
1791        match e.kind() {
1792            ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => {
1793                let sym = e.referenced_symbol().ok_or_else(|| BuildError::Missing {
1794                    what: "symbol of a named value".into(),
1795                })?;
1796                Ok(if sym.name() == "this" {
1797                    Vec::new()
1798                } else {
1799                    vec![sym]
1800                })
1801            }
1802            ExpressionKind::MemberAccess => {
1803                let mut path = Self::handle_path(Self::operand(e, "a member access")?)?;
1804                path.push(e.member_symbol().ok_or_else(|| BuildError::Missing {
1805                    what: "member symbol of a member access".into(),
1806                })?);
1807                Ok(path)
1808            }
1809            ExpressionKind::Conversion => {
1810                let inner = e.conversion_operand().ok_or_else(|| BuildError::Missing {
1811                    what: "operand of a conversion".into(),
1812                })?;
1813                Self::handle_path(inner)
1814            }
1815            // An element of a fixed array of handles (`es[i]`): the array
1816            // property is the root; [`Self::handle_root_element`] places the element.
1817            ExpressionKind::ElementSelect => {
1818                let arr = e.select_value().ok_or_else(|| BuildError::Missing {
1819                    what: "array of an element select".into(),
1820                })?;
1821                match arr.kind() {
1822                    ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => {
1823                        Self::handle_path(arr)
1824                    }
1825                    other => Err(unsupported(
1826                        format!(
1827                            "an element of a `{other:?}` array of handles as the object of a member access"
1828                        ),
1829                        "C17 follow-up",
1830                    )),
1831                }
1832            }
1833            other => Err(unsupported(
1834                format!("a `{other:?}` expression as the object of a member access"),
1835                "C17 follow-up",
1836            )),
1837        }
1838    }
1839
1840    /// The element of a fixed array of handles a handle chain starts at
1841    /// (`es[i]` in `es[i].v`), as the bit of the array property it sits at and
1842    /// the property's width; `None` when the chain starts at a plain handle.
1843    /// The index must be constant here (a literal, or an unrolled `foreach`
1844    /// variable): a computed index would pick among objects.
1845    fn handle_root_element(&mut self, e: Expression<'_>) -> Result<Option<(u32, u32)>, BuildError> {
1846        let mut cur = e;
1847        // Any other kind is a plain handle (no element of an array): a
1848        // predicate answer, not a lowering.
1849        loop {
1850            let kind = cur.kind();
1851            if kind == ExpressionKind::MemberAccess {
1852                cur = Self::operand(cur, "a member access")?;
1853            } else if kind == ExpressionKind::Conversion {
1854                cur = cur
1855                    .conversion_operand()
1856                    .ok_or_else(|| BuildError::Missing {
1857                        what: "operand of a conversion".into(),
1858                    })?;
1859            } else if kind == ExpressionKind::ElementSelect {
1860                break;
1861            } else {
1862                return Ok(None);
1863            }
1864        }
1865        let arr = cur.select_value().ok_or_else(|| BuildError::Missing {
1866            what: "array of an element select".into(),
1867        })?;
1868        let at = arr
1869            .expr_type()
1870            .map(|t| t.canonical())
1871            .ok_or_else(|| BuildError::Missing {
1872                what: "type of an array of handles".into(),
1873            })?;
1874        if !at.is_unpacked_array() || at.fixed_unpacked_array_range().width() == 0 {
1875            return Err(unsupported(
1876                "an element of a dynamic array, queue or associative array of handles in a constraint",
1877                "C17 follow-up",
1878            ));
1879        }
1880        let sel = cur.selector().ok_or_else(|| BuildError::Missing {
1881            what: "index of an element select".into(),
1882        })?;
1883        let ix = self.expr(sel)?;
1884        let i = Self::constant_index(&ix).ok_or_else(|| {
1885            unsupported(
1886                "an element of an array of handles at a computed index",
1887                "C17 follow-up",
1888            )
1889        })?;
1890        let r = at.fixed_unpacked_array_range();
1891        let lo = i64::from(r.left).min(i64::from(r.right));
1892        let n = i64::from(r.width());
1893        if !(0..n).contains(&(i - lo)) {
1894            return Err(BuildError::OutOfRangeSelect {
1895                what: format!("handle array index {i} outside [{}:{}]", r.left, r.right),
1896            });
1897        }
1898        Ok(Some((((i - lo) * 64) as u32, (n * 64) as u32)))
1899    }
1900
1901    /// A value read through object handles (`a.x`, `next.next.n`, `h`): a
1902    /// state variable per LRM 18.5.12 (a handle that is not `rand` is not
1903    /// randomized, so what it designates is fixed during the solve).
1904    fn handle_state(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
1905        let path = Self::handle_path(e)?;
1906        if path.is_empty() {
1907            return Err(unsupported("a bare `this` as a value", "C17 follow-up"));
1908        }
1909        if self.frame.is_some()
1910            && let Some(base) = self.fn_base.clone()
1911        {
1912            // `this.p.q` in a function called through a handle: relative to
1913            // that handle's object.
1914            return self.based_state(&base, &path, ty);
1915        }
1916        let Some((root, hops)) = path.split_first() else {
1917            return Err(unsupported("a bare `this` as a value", "C17 follow-up"));
1918        };
1919        if self.var_ix.contains_key(&root.uid()) {
1920            return Err(unsupported(
1921                format!("the randomized handle `{}` in a constraint", root.name()),
1922                "C17 follow-up",
1923            ));
1924        }
1925        // Every step but the last must itself be a handle, or the path is
1926        // not a chain of objects.
1927        let elem = self.handle_root_element(e)?;
1928        for (i, step) in path[..path.len() - 1].iter().enumerate() {
1929            let is_handle = (i == 0 && elem.is_some())
1930                || step.value_type().is_some_and(|t| t.canonical().is_class());
1931            if !is_handle {
1932                return Err(unsupported(
1933                    format!(
1934                        "a member access through `{}` (step {i}), which is not a class handle",
1935                        step.name()
1936                    ),
1937                    "C17 follow-up",
1938                ));
1939            }
1940        }
1941        let bit = elem.map_or(0, |(b, _)| b);
1942        let out = self.state_ref(*root, hops, bit, ty)?;
1943        if let (Some((bit, whole)), true, Expr::State(id, _)) = (elem, hops.is_empty(), &out) {
1944            // The handle itself (`es[1] == null`) is one window of the array.
1945            self.states[*id as usize].window = Some(Window {
1946                bit,
1947                width: 64,
1948                field_width: whole,
1949            });
1950        }
1951        Ok(out)
1952    }
1953
1954    /// A class-handle expression (LRM 18.5.12 compares handles only): `null`
1955    /// is `0`, a handle property or member a 64-bit state variable.
1956    fn handle_value(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
1957        match e.kind() {
1958            ExpressionKind::NullLiteral => Ok(Expr::Const(HANDLE_TY, BigUint::zero())),
1959            // `null` or a derived handle converted to the compared type: the
1960            // handle value itself is unchanged.
1961            ExpressionKind::Conversion => {
1962                let inner = e.conversion_operand().ok_or_else(|| BuildError::Missing {
1963                    what: "operand of a conversion".into(),
1964                })?;
1965                self.handle_value(inner)
1966            }
1967            ExpressionKind::NamedValue
1968            | ExpressionKind::HierarchicalValue
1969            | ExpressionKind::ElementSelect
1970            | ExpressionKind::MemberAccess => self.handle_state(e, HANDLE_TY),
1971            other => Err(unsupported(
1972                format!("a `{other:?}` class-handle expression"),
1973                "C17 follow-up",
1974            )),
1975        }
1976    }
1977
1978    /// Builds the IR for one integral expression (values read are random or
1979    /// state variables per the module docs).
1980    pub fn build_expr(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
1981        let out = self.expr(e)?;
1982        out.validate()?;
1983        Ok(out)
1984    }
1985
1986    fn operand<'d>(e: Expression<'d>, what: &str) -> Result<Expression<'d>, BuildError> {
1987        e.left().ok_or_else(|| BuildError::Missing {
1988            what: format!("operand of {what}"),
1989        })
1990    }
1991
1992    fn expr(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
1993        let kind = e.kind();
1994        if is_handle_type(e.expr_type()) {
1995            return self.handle_value(e);
1996        }
1997        let ty = ty_of(e.expr_type(), &format!("a `{kind:?}` expression"))?;
1998        // slang folds `rand_var matches p ? t : f` to its false arm (the match
1999        // of a non-constant subject "fails" at constant-evaluation time), so a
2000        // conditional with a pattern is never read through its constant value.
2001        let pattern_cond = kind == ExpressionKind::ConditionalOp
2002            && e.conditions().iter().any(|c| c.pattern().is_some());
2003        if !pattern_cond
2004            && let Some(cv) = e.constant_value()
2005            && let Some(i) = cv.as_integer()
2006        {
2007            return Ok(Expr::Const(ty, svint_to_biguint(i, ty)?));
2008        }
2009        match kind {
2010            ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => {
2011                let sym = e.referenced_symbol().ok_or_else(|| BuildError::Missing {
2012                    what: "symbol of a named value".into(),
2013                })?;
2014                if let Some(local) = self.frame_var(sym) {
2015                    return Ok(local);
2016                }
2017                if sym.kind() == SymbolKind::Iterator {
2018                    self.not_in_function("an iterator variable")?;
2019                    return self.loop_ref(sym, ty);
2020                }
2021                if self.is_call_local(e) {
2022                    self.not_in_function("a local of an inline constraint's call site")?;
2023                    return Ok(self.capture_ref(sym, ty));
2024                }
2025                self.symbol_ref(sym, ty)
2026            }
2027            ExpressionKind::MemberAccess => {
2028                // A member of an unpacked struct property (LRM 18.4), or of an
2029                // element of an unpacked array property.
2030                if let Some(chain) = aggregate::aggregate_chain(e, &self.forced_roots)
2031                    && !chain.steps.is_empty()
2032                {
2033                    return self.aggregate_value(chain, ty);
2034                }
2035                let base = Self::operand(e, "a member access")?;
2036                let is_this = base.kind() == ExpressionKind::NamedValue
2037                    && base.referenced_symbol().is_some_and(|s| s.name() == "this");
2038                let member = e.member_symbol().ok_or_else(|| BuildError::Missing {
2039                    what: "member symbol of a member access".into(),
2040                })?;
2041                if !is_this {
2042                    if is_handle_type(base.expr_type()) {
2043                        // A member read through a handle that is a local of the
2044                        // call site is one constant of the call (LRM 18.7).
2045                        if let Some(root) = Self::local_handle_root(base)
2046                            && self.is_call_local(root)
2047                        {
2048                            self.not_in_function("a local of an inline constraint's call site")?;
2049                            let path = Self::handle_path(e)?;
2050                            let (r, hops) =
2051                                path.split_first().ok_or_else(|| BuildError::Missing {
2052                                    what: "handle path of a member read".into(),
2053                                })?;
2054                            return Ok(self.capture_path(*r, hops, ty));
2055                        }
2056                        // A member read through an object handle (LRM 18.5.12).
2057                        return self.handle_state(e, ty);
2058                    }
2059                    // A packed struct or union is one integral variable (LRM
2060                    // 18.4): a member read is a window of the whole value.
2061                    let base_ty = base.expr_type();
2062                    if !base_ty.is_some_and(|t| t.is_integral() && (t.is_struct() || t.is_union()))
2063                    {
2064                        let what = format!("member access `.{}`", member.name());
2065                        return Err(unsupported(
2066                            format!(
2067                                "{what} on a value that is neither a class handle, a packed \
2068                                 struct or union, nor a member of an unpacked struct property \
2069                                 of this class"
2070                            ),
2071                            "C14",
2072                        ));
2073                    }
2074                    let whole = self.expr(base)?;
2075                    let low = u32::try_from(member.field_bit_offset()).map_err(|_| {
2076                        BuildError::Missing {
2077                            what: format!("bit offset of packed member `{}`", member.name()),
2078                        }
2079                    })?;
2080                    let slice = Expr::Slice(Box::new(whole), low, Ty::unsigned(ty.width));
2081                    return Ok(if slice.ty() == ty {
2082                        slice
2083                    } else {
2084                        Expr::Resize(Box::new(slice), ty)
2085                    });
2086                }
2087                self.symbol_ref(member, ty)
2088            }
2089            ExpressionKind::UnaryOp => {
2090                let op = e.unary_op().ok_or_else(|| BuildError::Missing {
2091                    what: "operator of a unary expression".into(),
2092                })?;
2093                let a = self.expr(Self::operand(e, "a unary operator")?)?;
2094                let (uop, rty) = match op {
2095                    UnaryOp::Plus => return Ok(a),
2096                    UnaryOp::Minus => (UnOp::Neg, ty),
2097                    UnaryOp::BitwiseNot => (UnOp::BitNot, ty),
2098                    UnaryOp::BitwiseAnd => (UnOp::RedAnd, Ty::BIT),
2099                    UnaryOp::BitwiseOr => (UnOp::RedOr, Ty::BIT),
2100                    UnaryOp::BitwiseXor => (UnOp::RedXor, Ty::BIT),
2101                    UnaryOp::BitwiseNand => (UnOp::RedNand, Ty::BIT),
2102                    UnaryOp::BitwiseNor => (UnOp::RedNor, Ty::BIT),
2103                    UnaryOp::BitwiseXnor => (UnOp::RedXnor, Ty::BIT),
2104                    UnaryOp::LogicalNot => (UnOp::LogNot, Ty::BIT),
2105                    other => {
2106                        return Err(unsupported(
2107                            format!("the side-effecting operator {other:?}"),
2108                            "C2",
2109                        ));
2110                    }
2111                };
2112                Ok(Expr::Unary(uop, Box::new(a), rty))
2113            }
2114            ExpressionKind::BinaryOp => self.binary(e, ty),
2115            ExpressionKind::ConditionalOp => {
2116                let mut bound = Vec::new();
2117                let r = self.conditional(e, ty, &mut bound);
2118                for uid in bound {
2119                    self.pattern_binds.remove(&uid);
2120                }
2121                r
2122            }
2123            ExpressionKind::Conversion => self.conversion(e, ty),
2124            ExpressionKind::Concatenation => {
2125                let mut parts = Vec::new();
2126                let mut w = 0u32;
2127                for o in e.operands() {
2128                    let p = self.expr(o)?;
2129                    w += p.ty().width;
2130                    parts.push(p);
2131                }
2132                Ok(Expr::Concat(parts, Ty::unsigned(w)))
2133            }
2134            ExpressionKind::Replication => {
2135                let n = e
2136                    .replication_count()
2137                    .and_then(|c| c.constant_value())
2138                    .and_then(|c| c.as_i64())
2139                    .ok_or_else(|| BuildError::Missing {
2140                        what: "constant count of a replication".into(),
2141                    })?;
2142                if n < 1 {
2143                    return Err(unsupported("a replication with a count below 1", "C2"));
2144                }
2145                let inner =
2146                    self.expr(e.replication_concat().ok_or_else(|| BuildError::Missing {
2147                        what: "operand of a replication".into(),
2148                    })?)?;
2149                let w = inner.ty().width * n as u32;
2150                Ok(Expr::Concat(vec![inner; n as usize], Ty::unsigned(w)))
2151            }
2152            ExpressionKind::Inside => self.inside(e),
2153            ExpressionKind::ElementSelect => self.element_select(e, ty),
2154            ExpressionKind::RangeSelect => self.range_select(e, ty),
2155            ExpressionKind::Call => {
2156                if e.is_system_call() == Some(true)
2157                    && self.frame.is_none()
2158                    && let Some(r) = self.array_call(e, ty)
2159                {
2160                    return r;
2161                }
2162                let name = e.call_subroutine_name().unwrap_or_default();
2163                if e.is_system_call() == Some(true) && (name == "$signed" || name == "$unsigned") {
2164                    let args = e.operands();
2165                    let [arg] = args.as_slice() else {
2166                        return Err(BuildError::Missing {
2167                            what: format!("single argument of {name}"),
2168                        });
2169                    };
2170                    let a = self.expr(*arg)?;
2171                    return Ok(Expr::Resize(Box::new(a), ty));
2172                }
2173                if e.is_system_call() == Some(true)
2174                    && let Some(r) = self.bit_count_call(e, ty)
2175                {
2176                    return r;
2177                }
2178                if e.is_system_call() == Some(true) {
2179                    return Err(unsupported(format!("a call to `{name}`"), "C13"));
2180                }
2181                self.user_call(e, ty)
2182            }
2183            ExpressionKind::LValueReference => self.lvalue_reference(),
2184            ExpressionKind::Dist => Err(BuildError::BadDist {
2185                what: "a `dist` nested inside another expression (it is a constraint item, \
2186                       never an operand)"
2187                    .into(),
2188            }),
2189            ExpressionKind::UnboundedLiteral => Err(unsupported(
2190                "an unbounded `$` outside an `inside` range",
2191                "C2",
2192            )),
2193            other => Err(unsupported(format!("a `{other:?}` expression"), "C2")),
2194        }
2195    }
2196
2197    /// `c1 &&& c2 ... ? t : f` (LRM 11.4.11, 12.6): every condition is a
2198    /// plain boolean or `expr matches pattern`, tested in order; a pattern
2199    /// variable bound by one is visible to the later conditions and to the
2200    /// true arm only. The variables bound are appended to `bound` so the
2201    /// caller can unbind them.
2202    fn conditional(
2203        &mut self,
2204        e: Expression<'_>,
2205        ty: Ty,
2206        bound: &mut Vec<SymbolId>,
2207    ) -> Result<Expr, BuildError> {
2208        let mut tests = Vec::new();
2209        for c in e.conditions() {
2210            let subject = self.expr(c.expr())?;
2211            tests.push(match c.pattern() {
2212                None => to_bool(subject),
2213                Some(p) => self.pattern_test(&p, subject, bound)?,
2214            });
2215        }
2216        let t = self.expr(e.true_value().ok_or_else(|| BuildError::Missing {
2217            what: "true arm of a conditional".into(),
2218        })?)?;
2219        // The false arm sees none of the bindings.
2220        for uid in bound.drain(..) {
2221            self.pattern_binds.remove(&uid);
2222        }
2223        let f = self.expr(e.false_value().ok_or_else(|| BuildError::Missing {
2224            what: "false arm of a conditional".into(),
2225        })?)?;
2226        Ok(Expr::Ite(
2227            Box::new(and_all(tests)),
2228            Box::new(t),
2229            Box::new(f),
2230            ty,
2231        ))
2232    }
2233
2234    /// Whether `subject` matches `pat` (LRM 12.6.1), a one-bit test. A
2235    /// constant pattern is `==` at the common type of the two (the LRM makes
2236    /// them the same type); `.*` always matches; `.n` always matches and binds
2237    /// `n` to the subject. Tagged-union and structure patterns need a
2238    /// subject that is a tagged union or an unpacked struct, neither of which
2239    /// the solver can take as a random variable, so they fail by name.
2240    fn pattern_test(
2241        &mut self,
2242        pat: &sv_lang::Pattern<'_>,
2243        subject: Expr,
2244        bound: &mut Vec<SymbolId>,
2245    ) -> Result<Expr, BuildError> {
2246        match pat.kind() {
2247            PatternKind::Constant => {
2248                let v = self.expr(pat.value_expr().ok_or_else(|| BuildError::Missing {
2249                    what: "value of a constant pattern".into(),
2250                })?)?;
2251                let (a, b) = (subject.ty(), v.ty());
2252                let ct = Ty {
2253                    width: a.width.max(b.width),
2254                    signed: a.signed && b.signed,
2255                };
2256                Ok(bin(BinOp::Eq, coerce(subject, ct), coerce(v, ct), Ty::BIT))
2257            }
2258            PatternKind::Wildcard => Ok(Expr::Const(Ty::BIT, BigUint::one())),
2259            PatternKind::Variable => {
2260                if let Some(var) = pat.variable()
2261                    && !var.name().is_empty()
2262                {
2263                    let uid = var.uid();
2264                    self.pattern_binds.insert(uid.clone(), subject);
2265                    bound.push(uid);
2266                }
2267                Ok(Expr::Const(Ty::BIT, BigUint::one()))
2268            }
2269            other => Err(unsupported(
2270                format!(
2271                    "a {other:?} pattern in a constraint (it matches a tagged union or an \
2272                     unpacked struct, which is never a random variable)"
2273                ),
2274                "C2",
2275            )),
2276        }
2277    }
2278
2279    fn binary(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
2280        let op = e.binary_op().ok_or_else(|| BuildError::Missing {
2281            what: "operator of a binary expression".into(),
2282        })?;
2283        let (lhs, rhs) = match (e.left(), e.right()) {
2284            (Some(l), Some(r)) => (l, r),
2285            _ => {
2286                return Err(BuildError::Missing {
2287                    what: "operands of a binary expression".into(),
2288                });
2289            }
2290        };
2291        let (bop, rty) = match op {
2292            BinaryOp::CaseEquality => return Err(BuildError::FourStateOperator { op: "===" }),
2293            BinaryOp::CaseInequality => return Err(BuildError::FourStateOperator { op: "!==" }),
2294            BinaryOp::WildcardEquality => return Err(BuildError::FourStateOperator { op: "==?" }),
2295            BinaryOp::WildcardInequality => {
2296                return Err(BuildError::FourStateOperator { op: "!=?" });
2297            }
2298            BinaryOp::Power => {
2299                let l = self.expr(lhs)?;
2300                let r = self.expr(rhs)?;
2301                return Ok(power(l, r, ty));
2302            }
2303            BinaryOp::Add => (BinOp::Add, ty),
2304            BinaryOp::Subtract => (BinOp::Sub, ty),
2305            BinaryOp::Multiply => (BinOp::Mul, ty),
2306            BinaryOp::Divide => (BinOp::Div, ty),
2307            BinaryOp::Mod => (BinOp::Mod, ty),
2308            BinaryOp::BinaryAnd => (BinOp::And, ty),
2309            BinaryOp::BinaryOr => (BinOp::Or, ty),
2310            BinaryOp::BinaryXor => (BinOp::Xor, ty),
2311            BinaryOp::BinaryXnor => (BinOp::Xnor, ty),
2312            BinaryOp::Equality => (BinOp::Eq, Ty::BIT),
2313            BinaryOp::Inequality => (BinOp::Ne, Ty::BIT),
2314            BinaryOp::GreaterThanEqual => (BinOp::Ge, Ty::BIT),
2315            BinaryOp::GreaterThan => (BinOp::Gt, Ty::BIT),
2316            BinaryOp::LessThanEqual => (BinOp::Le, Ty::BIT),
2317            BinaryOp::LessThan => (BinOp::Lt, Ty::BIT),
2318            BinaryOp::LogicalAnd => (BinOp::LogAnd, Ty::BIT),
2319            BinaryOp::LogicalOr => (BinOp::LogOr, Ty::BIT),
2320            BinaryOp::LogicalImplication => (BinOp::LogImpl, Ty::BIT),
2321            BinaryOp::LogicalEquivalence => (BinOp::LogEquiv, Ty::BIT),
2322            BinaryOp::LogicalShiftLeft | BinaryOp::ArithmeticShiftLeft => (BinOp::Shl, ty),
2323            BinaryOp::LogicalShiftRight => (BinOp::Shr, ty),
2324            BinaryOp::ArithmeticShiftRight => (BinOp::Ashr, ty),
2325        };
2326        // `a == b` / `a != b` over whole fixed-size unpacked arrays (LRM 11.4.5:
2327        // equivalent array types compare element by element): both operands'
2328        // leaves as one bit-stream each, so the comparison is equal exactly
2329        // when every element pair is (the element types are equivalent, so
2330        // the streams are laid out alike).
2331        let whole = |t: Option<Type<'_>>| {
2332            t.is_some_and(|t| {
2333                let c = t.canonical();
2334                c.is_unpacked_array() || c.is_unpacked_struct()
2335            })
2336        };
2337        if matches!(bop, BinOp::Eq | BinOp::Ne) && whole(lhs.expr_type()) && whole(rhs.expr_type())
2338        {
2339            let chains = aggregate::aggregate_chain(lhs, &self.forced_roots)
2340                .zip(aggregate::aggregate_chain(rhs, &self.forced_roots));
2341            let Some((lc, rc)) = chains else {
2342                return Err(unsupported(
2343                    "an array comparison whose operand is not a property (or a member of a \
2344                     struct property) of this class",
2345                    "C14",
2346                ));
2347            };
2348            let l = self.aggregate_bitstream(lc)?;
2349            let r = self.aggregate_bitstream(rc)?;
2350            return Ok(bin(bop, l, r, rty));
2351        }
2352        // `s == t` / `s != t` over strings (LRM 11.4.5, 6.16.6): strings are
2353        // interned, so equal contents are equal ids; a string is only ever a
2354        // state variable or a constant in a constraint.
2355        let is_string =
2356            |e: Expression<'_>| e.expr_type().is_some_and(|t| t.canonical().is_string());
2357        if is_string(lhs) || is_string(rhs) {
2358            if !matches!(bop, BinOp::Eq | BinOp::Ne) {
2359                return Err(unsupported(
2360                    format!("the string operator {op:?} (only `==` and `!=` compare strings)"),
2361                    "C13",
2362                ));
2363            }
2364            let l = self.string_id(lhs)?;
2365            let r = self.string_id(rhs)?;
2366            return Ok(bin(bop, l, r, rty));
2367        }
2368        let l = self.expr(lhs)?;
2369        let r = self.expr(rhs)?;
2370        Ok(bin(bop, l, r, rty))
2371    }
2372
2373    /// A string operand of a comparison as its interned id: a constant is
2374    /// interned now, a string property is a 64-bit state variable (the
2375    /// property holds the id).
2376    fn string_id(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
2377        if let Some(cv) = e.constant_value()
2378            && let Some(s) = cv.as_str()
2379        {
2380            let id = crate::strings::intern(s.as_bytes());
2381            return Ok(Expr::Const(HANDLE_TY, BigUint::from(id)));
2382        }
2383        match e.kind() {
2384            ExpressionKind::Conversion => {
2385                let inner = e.conversion_operand().ok_or_else(|| BuildError::Missing {
2386                    what: "operand of a conversion".into(),
2387                })?;
2388                self.string_id(inner)
2389            }
2390            ExpressionKind::NamedValue
2391            | ExpressionKind::HierarchicalValue
2392            | ExpressionKind::MemberAccess => {
2393                let sym = e.referenced_symbol().or_else(|| e.member_symbol());
2394                if sym.is_some_and(|s| self.var_ix.contains_key(&s.uid())) {
2395                    return Err(unsupported("a `rand` string", "C13"));
2396                }
2397                self.handle_state(e, HANDLE_TY)
2398            }
2399            other => Err(unsupported(
2400                format!("a `{other:?}` string operand of a comparison"),
2401                "C13",
2402            )),
2403        }
2404    }
2405
2406    fn conversion(&mut self, e: Expression<'_>, to: Ty) -> Result<Expr, BuildError> {
2407        let operand = e.conversion_operand().ok_or_else(|| BuildError::Missing {
2408            what: "operand of a conversion".into(),
2409        })?;
2410        let kind = e.conversion_kind().ok_or_else(|| BuildError::Missing {
2411            what: "kind of a conversion".into(),
2412        })?;
2413        // A bit-stream cast (LRM 6.24.3) of an unpacked struct or fixed-size
2414        // array property: its leaves concatenated. (A streaming concatenation
2415        // operand never reaches here: slang rejects one in a constraint.)
2416        if operand.expr_type().is_some_and(|t| !t.is_integral())
2417            && let Some(chain) = aggregate::aggregate_chain(operand, &self.forced_roots)
2418        {
2419            let x = self.aggregate_bitstream(chain)?;
2420            return Ok(if x.ty() == to {
2421                x
2422            } else {
2423                Expr::Resize(Box::new(x), to)
2424            });
2425        }
2426        let x = self.expr(operand)?;
2427        match kind {
2428            // Propagated: the context's signedness decides the extension.
2429            1 => Ok(coerce(x, to)),
2430            // Implicit / Explicit: assignment semantics, the operand's own.
2431            0 | 3 => Ok(if x.ty() == to {
2432                x
2433            } else {
2434                Expr::Resize(Box::new(x), to)
2435            }),
2436            other => Err(unsupported(
2437                format!("a conversion of kind {other} (streaming or bit-stream cast)"),
2438                "C2",
2439            )),
2440        }
2441    }
2442
2443    /// How the value range `item` combines its operands, from its syntax (the
2444    /// semantic node does not keep the operator).
2445    fn range_op(&self, item: Expression<'_>) -> Result<RangeOp, BuildError> {
2446        for tree in self.trees {
2447            if let Some(node) = item.syntax(tree) {
2448                let r =
2449                    ValueRangeExpressionSyntax::cast(node).ok_or_else(|| BuildError::Missing {
2450                        what: "value-range syntax for a ValueRange expression".into(),
2451                    })?;
2452                let op = r.op().ok_or_else(|| BuildError::Missing {
2453                    what: "operator of a value range".into(),
2454                })?;
2455                return match op.text() {
2456                    ":" => Ok(RangeOp::Span),
2457                    "+/-" => Ok(RangeOp::Abs),
2458                    "+%-" => Ok(RangeOp::Pct),
2459                    other => Err(unsupported(
2460                        format!("the value-range operator `{other}`"),
2461                        "C7",
2462                    )),
2463                };
2464            }
2465        }
2466        Err(BuildError::Missing {
2467            what: "syntax node for a value range (is its syntax tree passed to the builder?)"
2468                .into(),
2469        })
2470    }
2471
2472    /// The inclusive bounds of the value range `item`, for a value of type
2473    /// `subject`. `[a +/- b]` and `[a +%- b]` are computed in a signed type
2474    /// wide enough that the arithmetic cannot wrap, and compared there.
2475    fn value_range(&mut self, item: Expression<'_>, subject: Ty) -> Result<Bounds, BuildError> {
2476        let (l, r) = match (item.left(), item.right()) {
2477            (Some(l), Some(r)) => (l, r),
2478            _ => {
2479                return Err(BuildError::Missing {
2480                    what: "bounds of a value range".into(),
2481                });
2482            }
2483        };
2484        let op = self.range_op(item)?;
2485        // slang may wrap a `$` in a conversion to the range type.
2486        let is_unbounded = |b: Expression<'_>| {
2487            let mut peeled = b;
2488            while peeled.kind() == ExpressionKind::Conversion {
2489                match peeled.conversion_operand() {
2490                    Some(inner) => peeled = inner,
2491                    None => break,
2492                }
2493            }
2494            peeled.kind() == ExpressionKind::UnboundedLiteral
2495        };
2496        if op == RangeOp::Span {
2497            let mut side = |b: Expression<'_>| -> Result<Option<(Ty, Expr)>, BuildError> {
2498                if is_unbounded(b) {
2499                    return Ok(None);
2500                }
2501                let bx = self.expr(b)?;
2502                let t = common_ty(subject, bx.ty());
2503                Ok(Some((t, coerce(bx, t))))
2504            };
2505            let lo = side(l)?;
2506            let hi = side(r)?;
2507            return Ok(Bounds { lo, hi });
2508        }
2509        if is_unbounded(l) || is_unbounded(r) {
2510            return Err(BuildError::BadDist {
2511                what: "`$` as an operand of a `+/-` or `+%-` range".into(),
2512            });
2513        }
2514        let a = self.expr(l)?;
2515        let b = self.expr(r)?;
2516        let wide = Ty::signed(2 * subject.width.max(a.ty().width).max(b.ty().width) + 8);
2517        let (a, b) = (coerce(a, wide), coerce(b, wide));
2518        let delta = match op {
2519            RangeOp::Abs => b,
2520            _ => {
2521                let hundred = iconst(wide, 100);
2522                bin(
2523                    BinOp::Div,
2524                    bin(BinOp::Mul, a.clone(), b, wide),
2525                    hundred,
2526                    wide,
2527                )
2528            }
2529        };
2530        let lo = bin(BinOp::Sub, a.clone(), delta.clone(), wide);
2531        let hi = bin(BinOp::Add, a, delta, wide);
2532        Ok(Bounds {
2533            lo: Some((wide, lo)),
2534            hi: Some((wide, hi)),
2535        })
2536    }
2537
2538    /// `x dist { ... }` as a constraint item (LRM 18.5.3; module docs of
2539    /// [`super::dist`]): registers the [`Dist`] and returns its membership
2540    /// conjunct over the dist's hidden selector variable.
2541    fn dist(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
2542        // Inside a `foreach` over a dynamic array or queue the dist is a template
2543        // that `array::expand` instances once per element (module docs of
2544        // `super::dist`), so its subject may name the loop variable and elements.
2545        let runtime = self.in_runtime_loop();
2546        let saved = if runtime {
2547            self.no_arrays
2548        } else {
2549            self.no_arrays.replace("a `dist` constraint")
2550        };
2551        let r = self.dist_item(e, runtime);
2552        self.no_arrays = saved;
2553        r
2554    }
2555
2556    fn dist_item(&mut self, e: Expression<'_>, runtime: bool) -> Result<Expr, BuildError> {
2557        let bad = |what: &str| BuildError::BadDist { what: what.into() };
2558        let left = e.dist_left().ok_or_else(|| BuildError::Missing {
2559            what: "distributed expression of a `dist`".into(),
2560        })?;
2561        // slang converts the distributed expression to the comparison type of
2562        // the whole `dist`; the weights are over the expression's own values
2563        // (a `default` covers the values of *its* type), and every comparison
2564        // below re-converts to its own common type, so the conversion is dropped.
2565        let mut left = left;
2566        while left.kind() == ExpressionKind::Conversion {
2567            match left.conversion_operand() {
2568                Some(inner) => left = inner,
2569                None => break,
2570            }
2571        }
2572        let subject = self.expr(left)?;
2573        let mut vars: Vec<VarId> = Vec::new();
2574        let mut randc: Option<VarId> = None;
2575        subject.for_each_var(&mut |v, _| {
2576            vars.push(v);
2577            if self.vars[v as usize].randc {
2578                randc = Some(v);
2579            }
2580        });
2581        vars.sort_unstable();
2582        vars.dedup();
2583        if let Some(v) = randc {
2584            return Err(BuildError::BadDist {
2585                what: format!(
2586                    "`randc` variable `{}` in a `dist` (randc variables take no weights)",
2587                    self.vars[v as usize].name
2588                ),
2589            });
2590        }
2591        let plain_var = match &subject {
2592            Expr::Var(v, _) => Some(*v),
2593            _ => None,
2594        };
2595        let sty = subject.ty();
2596        let state_only = |x: &Expr, what: &str| -> Result<(), BuildError> {
2597            let mut has_var = false;
2598            x.for_each_var(&mut |_, _| has_var = true);
2599            if has_var {
2600                return Err(BuildError::BadDist {
2601                    what: format!(
2602                        "a random variable in {what}; weights and bounds are fixed values"
2603                    ),
2604                });
2605            }
2606            Ok(())
2607        };
2608        let weight_of = |this: &mut Self,
2609                         w: Option<sv_lang::DistWeight<'_>>|
2610         -> Result<(Expr, bool), BuildError> {
2611            match w {
2612                // LRM 18.5.3: an item without a weight is `:= 1`.
2613                None => Ok((Expr::constant(Ty::unsigned(32), 1u32), false)),
2614                Some(w) => {
2615                    let x = this.expr(w.expr)?;
2616                    state_only(&x, "a `dist` weight")?;
2617                    Ok((x, w.kind == DistWeightKind::PerRange))
2618                }
2619            }
2620        };
2621        let mut items = Vec::new();
2622        for it in e.dist_items() {
2623            let v = it.value();
2624            let (member, ty, span) = if v.kind() == ExpressionKind::ValueRange {
2625                let b = self.value_range(v, sty)?;
2626                // Bounds of different types are each compared in their own
2627                // common type; `Dist::weights` reads the types off the bounds.
2628                let ty = match (&b.lo, &b.hi) {
2629                    (Some((t, _)), _) | (_, Some((t, _))) => *t,
2630                    (None, None) => sty,
2631                };
2632                let mut conj = Vec::new();
2633                for (bound, cmp) in [(&b.lo, BinOp::Ge), (&b.hi, BinOp::Le)] {
2634                    if let Some((t, x)) = bound {
2635                        state_only(x, "a `dist` range bound")?;
2636                        conj.push(bin(cmp, coerce(subject.clone(), *t), x.clone(), Ty::BIT));
2637                    }
2638                }
2639                (
2640                    and_all(conj),
2641                    ty,
2642                    Span::Range {
2643                        lo: b.lo.map(|(_, x)| x),
2644                        hi: b.hi.map(|(_, x)| x),
2645                    },
2646                )
2647            } else {
2648                let x = self.expr(v)?;
2649                state_only(&x, "a `dist` value")?;
2650                let t = common_ty(sty, x.ty());
2651                let x = coerce(x, t);
2652                let m = bin(BinOp::Eq, coerce(subject.clone(), t), x.clone(), Ty::BIT);
2653                (m, t, Span::Point(x))
2654            };
2655            let (w, per_range) = weight_of(self, it.weight())?;
2656            items.push(Item {
2657                member,
2658                ty,
2659                span,
2660                weight: w,
2661                per_range,
2662            });
2663        }
2664        let default = match e.dist_default_weight() {
2665            Some(w) => {
2666                let (weight, per_range) = weight_of(self, Some(w))?;
2667                Some(DefaultWeight { weight, per_range })
2668            }
2669            None => None,
2670        };
2671        if items.is_empty() && default.is_none() {
2672            return Err(bad("a `dist` with no items"));
2673        }
2674        let d = Dist {
2675            subject_ty: sty,
2676            plain_var,
2677            vars,
2678            items,
2679            default,
2680            subject: runtime.then_some(subject),
2681        };
2682        let sel = SELECTOR_BASE + self.dists.len() as u32;
2683        let conjunct = d.conjunct(sel);
2684        self.dists.push(d);
2685        Ok(conjunct)
2686    }
2687
2688    fn inside(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
2689        let left = self.expr(e.inside_left().ok_or_else(|| BuildError::Missing {
2690            what: "left operand of `inside`".into(),
2691        })?)?;
2692        let items = e.inside_range_list();
2693        if items.is_empty() {
2694            return Err(BuildError::Missing {
2695                what: "candidates of `inside`".into(),
2696            });
2697        }
2698        // LRM 11.4.13 / 18.3: the set is a plain disjunction of equality and
2699        // range tests, so every member is one value-region of the BDD and the
2700        // solver picks uniformly over the satisfying *values*; unlike `dist`
2701        // (18.5.3) no member carries a weight.
2702        let mut acc: Option<Expr> = None;
2703        for item in items {
2704            let term = if item.kind() == ExpressionKind::ValueRange {
2705                // `[a:b]`, `[a +/- b]` and `[a +%- b]` (11.4.13); a bound is
2706                // compared in the common type of it and the tested value.
2707                let b = self.value_range(item, left.ty())?;
2708                let mut conj: Vec<Expr> = Vec::new();
2709                for (bound, cmp) in [(b.lo, BinOp::Ge), (b.hi, BinOp::Le)] {
2710                    if let Some((t, x)) = bound {
2711                        conj.push(bin(cmp, coerce(left.clone(), t), x, Ty::BIT));
2712                    }
2713                }
2714                and_all(conj)
2715            } else if item
2716                .expr_type()
2717                .is_some_and(|t| arrays::array_shape(t).is_some())
2718            {
2719                self.inside_array(&left, item)?
2720            } else {
2721                let ix = self.expr(item)?;
2722                let t = common_ty(left.ty(), ix.ty());
2723                bin(BinOp::Eq, coerce(left.clone(), t), coerce(ix, t), Ty::BIT)
2724            };
2725            acc = Some(match acc {
2726                None => term,
2727                Some(prev) => bin(BinOp::LogOr, prev, term, Ty::BIT),
2728            });
2729        }
2730        Ok(acc.expect("candidates non-empty, checked above"))
2731    }
2732
2733    /// The selected value's expression and geometry, shared by both selects.
2734    fn select_base(&mut self, e: Expression<'_>) -> Result<(Expr, Geom), BuildError> {
2735        let base = e.select_value().ok_or_else(|| BuildError::Missing {
2736            what: "base of a select".into(),
2737        })?;
2738        let bt = base.expr_type().ok_or_else(|| BuildError::Missing {
2739            what: "type of a select base".into(),
2740        })?;
2741        let bx = self.expr(base)?;
2742        let geom = Geom::of(bt, bx.ty().width, "a select base")?;
2743        Ok((bx, geom))
2744    }
2745
2746    /// Widens a dynamic index so offset arithmetic cannot wrap, as a signed
2747    /// value (an unsigned index zero-extends).
2748    fn wide_index(&mut self, idx: Expression<'_>) -> Result<(Expr, Ty), BuildError> {
2749        let ix = self.expr(idx)?;
2750        let wide = Ty::signed(ix.ty().width.max(32) + 2);
2751        Ok((Expr::Resize(Box::new(ix), wide), wide))
2752    }
2753
2754    /// `base >> (pos * ew)` truncated to `ty`: the dynamic-window gadget.
2755    fn dyn_window(base: Expr, pos: Expr, wide: Ty, ew: u32, out_width: u32, ty: Ty) -> Expr {
2756        let off = if ew > 1 {
2757            bin(BinOp::Mul, pos, iconst(wide, i64::from(ew)), wide)
2758        } else {
2759            pos
2760        };
2761        let bty = base.ty();
2762        let shifted = bin(BinOp::Shr, base, off, bty);
2763        Expr::Resize(
2764            Box::new(Expr::Resize(Box::new(shifted), Ty::unsigned(out_width))),
2765            ty,
2766        )
2767    }
2768
2769    fn static_window(
2770        base: Expr,
2771        geom: &Geom,
2772        lo_idx: i64,
2773        hi_idx: i64,
2774        what: &str,
2775        ty: Ty,
2776    ) -> Result<Expr, BuildError> {
2777        let (pl, ph) = (geom.pos(lo_idx), geom.pos(hi_idx));
2778        let (low, high) = (pl.min(ph), pl.max(ph));
2779        if low < 0 || high >= geom.elems() {
2780            return Err(BuildError::OutOfRangeSelect {
2781                what: format!(
2782                    "{what}[{lo_idx}..{hi_idx}] outside [{}:{}]",
2783                    geom.left, geom.right
2784                ),
2785            });
2786        }
2787        let width = (high - low + 1) as u32 * geom.ew;
2788        let slice = Expr::Slice(Box::new(base), low as u32 * geom.ew, Ty::unsigned(width));
2789        Ok(if slice.ty() == ty {
2790            slice
2791        } else {
2792            Expr::Resize(Box::new(slice), ty)
2793        })
2794    }
2795
2796    fn element_select(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
2797        // An element of a fixed-size unpacked array property (LRM 18.4).
2798        if let Some(chain) = aggregate::aggregate_chain(e, &self.forced_roots)
2799            && !chain.steps.is_empty()
2800        {
2801            return self.aggregate_value(chain, ty);
2802        }
2803        if let Some(arr) = e.select_value()
2804            && arr
2805                .expr_type()
2806                .is_some_and(|t| arrays::array_shape(t).is_some())
2807        {
2808            self.not_in_function("an array element")?;
2809            return self.array_element(e, arr, ty);
2810        }
2811        let (base, geom) = self.select_base(e)?;
2812        let sel = e.selector().ok_or_else(|| BuildError::Missing {
2813            what: "index of an element select".into(),
2814        })?;
2815        if let Some(i) = sel.constant_value().and_then(|c| c.as_i64()) {
2816            return Self::static_window(base, &geom, i, i, "element select", ty);
2817        }
2818        let (idx, wide) = self.wide_index(sel)?;
2819        let pos = if geom.descending() {
2820            bin(BinOp::Sub, idx, iconst(wide, geom.right), wide)
2821        } else {
2822            bin(BinOp::Sub, iconst(wide, geom.right), idx, wide)
2823        };
2824        Ok(Self::dyn_window(base, pos, wide, geom.ew, ty.width, ty))
2825    }
2826
2827    fn range_select(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
2828        let (base, geom) = self.select_base(e)?;
2829        let kind = e
2830            .range_selection_kind()
2831            .ok_or_else(|| BuildError::Missing {
2832                what: "kind of a range select".into(),
2833            })?;
2834        let (l, r) = match (e.range_left(), e.range_right()) {
2835            (Some(l), Some(r)) => (l, r),
2836            _ => {
2837                return Err(BuildError::Missing {
2838                    what: "bounds of a range select".into(),
2839                });
2840            }
2841        };
2842        let const_of = |x: Expression<'_>| x.constant_value().and_then(|c| c.as_i64());
2843        if kind == 0 {
2844            let (Some(a), Some(b)) = (const_of(l), const_of(r)) else {
2845                return Err(BuildError::Missing {
2846                    what: "constant bounds of a `[l:r]` select".into(),
2847                });
2848            };
2849            return Self::static_window(base, &geom, a.min(b), a.max(b), "range select", ty);
2850        }
2851        let w = const_of(r)
2852            .filter(|w| *w >= 1)
2853            .ok_or_else(|| BuildError::Missing {
2854                what: "constant width of an indexed part select".into(),
2855            })?;
2856        // Lowest and highest declared index relative to the base index.
2857        let (dlo, dhi) = if kind == 1 { (0, w - 1) } else { (-(w - 1), 0) };
2858        if let Some(b) = const_of(l) {
2859            return Self::static_window(base, &geom, b + dlo, b + dhi, "part select", ty);
2860        }
2861        let (idx, wide) = self.wide_index(l)?;
2862        let pos = if geom.descending() {
2863            bin(BinOp::Add, idx, iconst(wide, dlo - geom.right), wide)
2864        } else {
2865            bin(BinOp::Sub, iconst(wide, geom.right - dhi), idx, wide)
2866        };
2867        Ok(Self::dyn_window(base, pos, wide, geom.ew, ty.width, ty))
2868    }
2869}