Skip to main content

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