1use super::real::strip_conversions;
43use super::*;
44use crate::constraints::ir::{ArrayId, ArrayNode, LoopId, ReduceOp};
45use sv_lang::KnownSystemName;
46use sv_lang::kinds::SymbolKind;
47use sv_lang::nodes::LoopConstraintSyntax;
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct ArrayInfo {
52 pub symbol: SymbolId,
54 pub name: String,
56 pub elem_ty: Ty,
58 pub rand: bool,
62 pub elem_domain: Option<Vec<BigUint>>,
65 pub assoc: bool,
68 pub nested: bool,
72 pub template_of: Option<ArrayId>,
76}
77
78#[derive(Clone, Debug)]
80pub(super) enum LoopBind {
81 Runtime(LoopId, Ty),
83 Const(i64),
85 Elem(Expr),
88}
89
90#[derive(Clone, Debug)]
92pub(super) struct LoopScope {
93 name: String,
94 value: LoopBind,
97 index: Option<LoopBind>,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub(super) enum ArrayShape {
104 Collection,
106 Assoc,
108 Fixed,
110}
111
112pub(super) fn array_shape(t: Type<'_>) -> Option<ArrayShape> {
113 let c = t.canonical();
114 if c.is_associative_array() {
115 Some(ArrayShape::Assoc)
116 } else if c.is_dynamically_sized_array() || c.is_queue() {
117 Some(ArrayShape::Collection)
118 } else if c.is_unpacked_array() {
119 Some(ArrayShape::Fixed)
120 } else {
121 None
122 }
123}
124
125fn bad_array(what: impl Into<String>) -> BuildError {
126 BuildError::BadArray { what: what.into() }
127}
128
129pub(super) fn const_i64(ty: Ty, v: &BigUint) -> Option<i64> {
131 let signed = BigInt::from(v.clone());
132 let signed = if ty.signed && v.bit(u64::from(ty.width) - 1) {
133 signed - (BigInt::one() << ty.width)
134 } else {
135 signed
136 };
137 i64::try_from(signed).ok()
138}
139
140impl Builder<'_> {
141 pub(super) fn arrays_allowed(&self, what: &str) -> Result<(), BuildError> {
142 match self.no_arrays {
143 None => Ok(()),
144 Some(inside) => Err(unsupported(
145 format!("{what} inside {inside}"),
146 "C14 (array-level values have no weight or bound at build time)",
147 )),
148 }
149 }
150
151 pub fn declare_rand_array(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
155 let uid = sym.uid();
156 let name = sym.name().to_string();
157 match sym.rand_mode() {
158 RandMode::None => return Err(BuildError::NotRand { name }),
159 RandMode::RandC => {
160 return Err(unsupported(
161 format!("the `randc` array `{name}`: per-element cycling"),
162 "C14",
163 ));
164 }
165 RandMode::Rand => {}
166 }
167 if let Some(&id) = self.array_ix.get(&uid) {
168 self.arrays[id as usize].rand = true;
169 return Ok(id);
170 }
171 let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
172 what: format!("value type for `{name}`"),
173 })?;
174 let info = self.array_info(uid.clone(), name, vt, true)?;
175 let id = self.arrays.len() as u32;
176 self.arrays.push(info);
177 self.array_ix.insert(uid, id);
178 Ok(id)
179 }
180
181 pub fn declare_array_forced(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
186 let uid = sym.uid();
187 let name = sym.name().to_string();
188 if matches!(sym.rand_mode(), RandMode::RandC) {
189 return Err(unsupported(
190 format!("the `randc` array `{name}`: per-element cycling"),
191 "C14",
192 ));
193 }
194 if let Some(&id) = self.array_ix.get(&uid) {
195 self.arrays[id as usize].rand = true;
196 return Ok(id);
197 }
198 let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
199 what: format!("value type for `{name}`"),
200 })?;
201 let info = self.array_info(uid.clone(), name, vt, true)?;
202 let id = self.arrays.len() as u32;
203 self.arrays.push(info);
204 self.array_ix.insert(uid, id);
205 Ok(id)
206 }
207
208 fn array_info(
209 &self,
210 symbol: SymbolId,
211 name: String,
212 vt: Type<'_>,
213 rand: bool,
214 ) -> Result<ArrayInfo, BuildError> {
215 match array_shape(vt) {
216 Some(ArrayShape::Collection) => {}
217 Some(ArrayShape::Assoc) if rand => {}
221 Some(ArrayShape::Assoc) => return Err(Self::assoc_operand(&name)),
222 Some(ArrayShape::Fixed) => {
226 return Err(unsupported(
227 format!(
228 "the fixed-size unpacked array `{name}` as a whole-array constraint \
229 operand (name its elements, or use a reduction method or `foreach`)"
230 ),
231 "C14",
232 ));
233 }
234 None => {
235 return Err(BuildError::NonIntegral {
236 what: format!(
237 "`{name}` of type `{}`, which is not an array",
238 vt.to_sv_string()
239 ),
240 });
241 }
242 }
243 let et = vt
244 .canonical()
245 .element_type()
246 .ok_or_else(|| BuildError::Missing {
247 what: format!("element type of the array `{name}`"),
248 })?;
249 let mut nested = false;
252 let mut leaf = et;
253 if array_shape(vt) == Some(ArrayShape::Collection)
254 && let Some(inner) = array_shape(et)
255 {
256 let row = et.canonical().element_type();
257 match (inner, row) {
258 (ArrayShape::Collection, Some(r)) if array_shape(r).is_none() => {
259 nested = true;
260 leaf = r;
261 }
262 (ArrayShape::Collection, Some(_)) => {
263 return Err(unsupported(
264 format!("`{name}`: a dynamic array nested more than two deep"),
265 "C14",
266 ));
267 }
268 _ => {
269 return Err(unsupported(
270 format!(
271 "`{name}`: a dynamic array or queue whose elements are fixed-size or \
272 associative arrays"
273 ),
274 "C14",
275 ));
276 }
277 }
278 }
279 let elem_ty = ty_of(Some(leaf), &format!("an element of the array `{name}`"))?;
280 let elem_domain = Self::enum_values(leaf, elem_ty)?;
281 Ok(ArrayInfo {
282 symbol,
283 name,
284 elem_ty,
285 rand,
286 elem_domain,
287 assoc: array_shape(vt) == Some(ArrayShape::Assoc),
288 nested,
289 template_of: None,
290 })
291 }
292
293 fn assoc_operand(name: &str) -> BuildError {
294 bad_array(format!(
295 "`{name}` is an associative array: its size and indices are not randomizable and \
296 its elements are not constraint operands (IEEE 1800-2023 18.4)"
297 ))
298 }
299
300 pub(super) fn array_of(&mut self, e: Expression<'_>) -> Result<ArrayId, BuildError> {
304 match self.array_ref(e)? {
305 (id, None) if self.arrays[id as usize].nested => Err(unsupported(
306 format!(
307 "the array of arrays `{}` as a whole-array operand here (supported: `.size`, \
308 `foreach`, and its rows' sizes, elements and reductions)",
309 self.arrays[id as usize].name
310 ),
311 "C14",
312 )),
313 (id, None) => Ok(id),
314 (id, Some(_)) => Err(unsupported(
315 format!(
316 "a row of the array of arrays `{}` as a whole-array operand (name its \
317 elements `a[i][j]`, or use `.size`, a reduction method, `inside` or \
318 `foreach` on the row)",
319 self.arrays[self.arrays[id as usize].template_of.unwrap_or(id) as usize].name
320 ),
321 "C14",
322 )),
323 }
324 }
325
326 fn row_template(&mut self, outer: ArrayId) -> ArrayId {
328 if let Some(&t) = self.templates.get(&outer) {
329 return t;
330 }
331 let o = &self.arrays[outer as usize];
332 let info = ArrayInfo {
333 symbol: o.symbol.clone(),
334 name: format!("{}[]", o.name),
335 elem_ty: o.elem_ty,
336 rand: false,
337 elem_domain: o.elem_domain.clone(),
338 assoc: false,
339 nested: false,
340 template_of: Some(outer),
341 };
342 let id = self.arrays.len() as ArrayId;
343 self.arrays.push(info);
344 self.templates.insert(outer, id);
345 id
346 }
347
348 pub(super) fn array_ref(
351 &mut self,
352 e: Expression<'_>,
353 ) -> Result<(ArrayId, Option<Expr>), BuildError> {
354 let e = strip_conversions(e);
355 if e.kind() == ExpressionKind::ElementSelect {
356 let base = e.select_value().ok_or_else(|| BuildError::Missing {
357 what: "base of a row select".into(),
358 })?;
359 let sel = e.selector().ok_or_else(|| BuildError::Missing {
360 what: "index of a row select".into(),
361 })?;
362 let (outer, inner_row) = self.array_ref(base)?;
363 if inner_row.is_some() || !self.arrays[outer as usize].nested {
364 return Err(unsupported(
365 format!(
366 "an element select `{}[..]` as an array operand: it is not an array of \
367 arrays",
368 self.arrays[outer as usize].name
369 ),
370 "C14",
371 ));
372 }
373 self.arrays_allowed("a row of an array of arrays")?;
374 let row = self.expr(sel)?;
375 let mut has_var = false;
376 row.for_each_var(&mut |_, _| has_var = true);
377 if has_var {
378 return Err(bad_array(format!(
379 "a random variable in the row index of the array `{}`: an index is a \
380 constant, a state variable, a loop variable or the size (IEEE 1800-2023 \
381 18.5.7.1)",
382 self.arrays[outer as usize].name
383 )));
384 }
385 let t = self.row_template(outer);
386 return Ok((t, Some(row)));
387 }
388 Ok((self.array_named(e)?, None))
389 }
390
391 fn reject_nested(&self, a: ArrayId, what: &str) -> Result<(), BuildError> {
392 if self.arrays[a as usize].nested {
393 return Err(unsupported(
394 format!(
395 "{what} over the array of arrays `{}` itself (name a row `a[i]`)",
396 self.arrays[a as usize].name
397 ),
398 "C14",
399 ));
400 }
401 Ok(())
402 }
403
404 fn row_scoped(t: ArrayId, row: Option<Expr>, body: Expr) -> Expr {
406 match row {
407 None => body,
408 Some(row) => Expr::Array(ArrayNode::RowScope {
409 template: t,
410 row: Box::new(row),
411 body: Box::new(body),
412 }),
413 }
414 }
415
416 fn array_named(&mut self, e: Expression<'_>) -> Result<ArrayId, BuildError> {
417 let sym = match e.kind() {
418 ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => e.referenced_symbol(),
419 ExpressionKind::MemberAccess => {
420 let base = Self::operand(e, "a member access")?;
421 let is_this = base.kind() == ExpressionKind::NamedValue
422 && base.referenced_symbol().is_some_and(|s| s.name() == "this");
423 if !is_this {
424 return Err(BuildError::ForeignObjectAccess {
425 what: format!(
426 "the array `.{}` of another object",
427 e.member_symbol()
428 .map_or(String::new(), |m| m.name().to_string())
429 ),
430 });
431 }
432 e.member_symbol()
433 }
434 other => {
435 return Err(unsupported(
436 format!("a `{other:?}` expression as an array operand"),
437 "C14",
438 ));
439 }
440 };
441 let sym = sym.ok_or_else(|| BuildError::Missing {
442 what: "symbol of an array operand".into(),
443 })?;
444 let uid = sym.uid();
445 if let Some(&id) = self.array_ix.get(&uid) {
446 if self.arrays[id as usize].assoc {
447 return Err(Self::assoc_operand(&self.arrays[id as usize].name));
448 }
449 return Ok(id);
450 }
451 let vt = e.expr_type().ok_or_else(|| BuildError::Missing {
452 what: format!("type of the array `{}`", sym.name()),
453 })?;
454 let info = self.array_info(uid.clone(), sym.name().to_string(), vt, false)?;
455 let id = self.arrays.len() as u32;
456 self.arrays.push(info);
457 self.array_ix.insert(uid, id);
458 Ok(id)
459 }
460
461 pub(super) fn size_call_array<'d>(e: Expression<'d>) -> Option<Expression<'d>> {
464 let sub = e.system_subroutine()?;
465 if !matches!(
466 sub.known_name_id(),
467 KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num
468 ) {
469 return None;
470 }
471 let arr = *e.operands().first()?;
472 array_shape(arr.expr_type()?).map(|_| arr)
473 }
474
475 pub(super) fn array_size(&mut self, arr: Expression<'_>) -> Result<Expr, BuildError> {
477 self.arrays_allowed("an array size")?;
478 let (id, row) = self.array_ref(arr)?;
479 Ok(Self::row_scoped(id, row, Expr::Array(ArrayNode::Size(id))))
480 }
481
482 pub(super) fn array_element(
484 &mut self,
485 e: Expression<'_>,
486 base: Expression<'_>,
487 ty: Ty,
488 ) -> Result<Expr, BuildError> {
489 let shape = base.expr_type().and_then(array_shape);
490 let sel = e.selector().ok_or_else(|| BuildError::Missing {
491 what: "index of an element select".into(),
492 })?;
493 match shape {
494 Some(ArrayShape::Collection) => {
495 self.arrays_allowed("an array element")?;
496 let (id, row) = self.array_ref(base)?;
497 let index = self.expr(sel)?;
498 let mut has_var = false;
499 index.for_each_var(&mut |_, _| has_var = true);
500 if has_var {
501 return Err(bad_array(format!(
502 "a random variable in the index of the array `{}`: an index is a \
503 constant, a state variable, a loop variable or the size (IEEE \
504 1800-2023 18.5.7.1)",
505 self.arrays[id as usize].name
506 )));
507 }
508 let elem_ty = self.arrays[id as usize].elem_ty;
509 if self.arrays[id as usize].nested {
510 return Err(unsupported(
511 format!(
512 "a row `{}[..]` of an array of arrays as a value (name an element \
513 `a[i][j]`)",
514 self.arrays[id as usize].name
515 ),
516 "C14",
517 ));
518 }
519 let el = Self::row_scoped(
520 id,
521 row,
522 Expr::Array(ArrayNode::Elem {
523 array: id,
524 index: Box::new(index),
525 ty: elem_ty,
526 }),
527 );
528 Ok(if elem_ty == ty {
529 el
530 } else {
531 Expr::Resize(Box::new(el), ty)
532 })
533 }
534 Some(ArrayShape::Assoc) => Err(bad_array(
535 "an element of an associative array: its indices are not randomizable and it \
536 has no constraint form (IEEE 1800-2023 18.4)",
537 )),
538 Some(ArrayShape::Fixed) => self.fixed_element(e),
539 None => Err(BuildError::Missing {
540 what: "array type of an element select base".into(),
541 }),
542 }
543 }
544
545 fn fixed_element(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
550 let what = e
551 .select_value()
552 .map(strip_conversions)
553 .and_then(|b| b.referenced_symbol().or_else(|| b.member_symbol()))
554 .map_or_else(String::new, |s| s.name().to_string());
555 Err(unsupported(
556 format!(
557 "an element of the unpacked array `{what}`, which is not a property (or a \
558 member of a struct property) of this class"
559 ),
560 "C14",
561 ))
562 }
563
564 fn foreach_vars(&self, c: Constraint<'_>) -> Result<Vec<String>, BuildError> {
567 for tree in self.trees {
568 if let Some(node) = c.syntax(tree) {
569 let l = LoopConstraintSyntax::cast(node).ok_or_else(|| BuildError::Missing {
570 what: "`foreach` syntax for a Foreach constraint".into(),
571 })?;
572 return Ok(l
573 .loop_list()
574 .loop_variables()
575 .iter()
576 .map(|v| v.syntax().text().trim().to_string())
577 .collect());
578 }
579 }
580 Err(BuildError::Missing {
581 what: "syntax node for a constraint (is its syntax tree passed to the builder?)".into(),
582 })
583 }
584
585 fn fresh_loop(&mut self) -> LoopId {
586 let id = self.next_loop;
587 self.next_loop += 1;
588 id
589 }
590
591 fn in_scope(
593 &mut self,
594 scope: LoopScope,
595 body: Constraint<'_>,
596 ) -> Result<Vec<Expr>, BuildError> {
597 self.loops.push(scope);
598 let r = self.constraint(body);
599 self.loops.pop();
600 r
601 }
602
603 pub(super) fn foreach(&mut self, c: Constraint<'_>) -> Result<Vec<Expr>, BuildError> {
605 let names = self.foreach_vars(c)?;
606 let kids = c.children();
607 let arr =
608 kids.iter()
609 .find_map(|n| n.as_expression())
610 .ok_or_else(|| BuildError::Missing {
611 what: "array expression of a `foreach` constraint".into(),
612 })?;
613 let body =
614 kids.iter()
615 .find_map(|n| n.as_constraint())
616 .ok_or_else(|| BuildError::Missing {
617 what: "body of a `foreach` constraint".into(),
618 })?;
619 let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
620 what: "type of the `foreach` array".into(),
621 })?;
622 let shape = array_shape(at);
623 let nested_fixed = shape == Some(ArrayShape::Fixed)
628 && at
629 .canonical()
630 .element_type()
631 .is_some_and(|elem| array_shape(elem) == Some(ArrayShape::Fixed));
632 let nested_dyn = shape == Some(ArrayShape::Collection)
633 && at
634 .canonical()
635 .element_type()
636 .is_some_and(|elem| array_shape(elem) == Some(ArrayShape::Collection));
637 if nested_dyn {
638 return self.foreach_nested(arr, &names, body);
639 }
640 if names.len() > 1 || nested_fixed {
641 let dims = self.static_dims(at, &names)?;
642 return self.unroll_nd(&dims, body);
643 }
644 let [name] = names.as_slice() else {
645 return Err(BuildError::Missing {
646 what: "a loop variable of a `foreach` constraint".into(),
647 });
648 };
649 if shape.is_some()
650 && at
651 .canonical()
652 .element_type()
653 .is_some_and(|elem| array_shape(elem).is_some())
654 {
655 return Err(unsupported("a `foreach` over an array of arrays", "C11"));
656 }
657 let int = Ty::signed(32);
658 match shape {
659 Some(ArrayShape::Collection) => {
660 self.arrays_allowed("a `foreach`")?;
661 let array = self.array_of(arr)?;
662 let var = self.fresh_loop();
663 let (r0, b0, rb0) = (
666 self.real_conds.len() + self.real_dists.len(),
667 self.before.len(),
668 self.real_before.len(),
669 );
670 let parts = self.in_scope(
671 LoopScope {
672 name: name.clone(),
673 value: LoopBind::Runtime(var, int),
674 index: None,
675 },
676 body,
677 )?;
678 if self.real_conds.len() + self.real_dists.len() != r0
679 || self.before.len() != b0
680 || self.real_before.len() != rb0
681 {
682 return Err(unsupported(
683 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
684 `foreach` over a dynamic array or queue (it would have to hold once per \
685 element)",
686 "C11",
687 ));
688 }
689 Ok(vec![Expr::Array(ArrayNode::Foreach {
690 array,
691 var,
692 body: Box::new(and_all(parts)),
693 })])
694 }
695 Some(ArrayShape::Assoc) => Err(bad_array(
696 "a `foreach` over an associative array: its indices are not randomizable and \
697 it has no constraint form (IEEE 1800-2023 18.4)",
698 )),
699 Some(ArrayShape::Fixed) => {
700 let r = at.canonical().fixed_range();
701 self.unroll(name, i64::from(r.left), i64::from(r.right), body)
702 }
703 None => {
704 let width = u32::try_from(at.bit_width()).map_err(|_| BuildError::Missing {
706 what: "representable width for a `foreach` array".into(),
707 })?;
708 let g = Geom::of(at, width, "the `foreach` array")?;
709 self.unroll(name, g.left, g.right, body)
710 }
711 }
712 }
713
714 fn foreach_nested(
719 &mut self,
720 arr: Expression<'_>,
721 names: &[String],
722 body: Constraint<'_>,
723 ) -> Result<Vec<Expr>, BuildError> {
724 if names.is_empty() || names.len() > 2 {
725 return Err(unsupported(
726 format!(
727 "a `foreach` with {} loop variables over an array of dynamic arrays (at \
728 most two dimensions)",
729 names.len()
730 ),
731 "C11",
732 ));
733 }
734 self.arrays_allowed("a `foreach`")?;
735 let (outer, row) = self.array_ref(arr)?;
736 if row.is_some() || !self.arrays[outer as usize].nested {
737 return Err(unsupported(
738 "a `foreach` over an array whose elements are not dynamic arrays",
739 "C11",
740 ));
741 }
742 let int = Ty::signed(32);
743 let vi = self.fresh_loop();
744 let mut scopes = Vec::new();
745 if !names[0].is_empty() {
746 scopes.push(LoopScope {
747 name: names[0].clone(),
748 value: LoopBind::Runtime(vi, int),
749 index: None,
750 });
751 }
752 let vj = (names.len() == 2).then(|| self.fresh_loop());
753 if let Some(vj) = vj
754 && !names[1].is_empty()
755 {
756 scopes.push(LoopScope {
757 name: names[1].clone(),
758 value: LoopBind::Runtime(vj, int),
759 index: None,
760 });
761 }
762 let (r0, b0, rb0) = (
763 self.real_conds.len() + self.real_dists.len(),
764 self.before.len(),
765 self.real_before.len(),
766 );
767 let depth = self.loops.len();
768 self.loops.extend(scopes);
769 let parts = self.constraint(body);
770 self.loops.truncate(depth);
771 let parts = parts?;
772 if self.real_conds.len() + self.real_dists.len() != r0
773 || self.before.len() != b0
774 || self.real_before.len() != rb0
775 {
776 return Err(unsupported(
777 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
778 `foreach` over an array of dynamic arrays (it would have to hold once per \
779 element)",
780 "C11",
781 ));
782 }
783 let mut inner = and_all(parts);
784 if let Some(vj) = vj {
785 let t = self.row_template(outer);
786 inner = Expr::Array(ArrayNode::RowScope {
787 template: t,
788 row: Box::new(Expr::Array(ArrayNode::Loop(vi, int))),
789 body: Box::new(Expr::Array(ArrayNode::Foreach {
790 array: t,
791 var: vj,
792 body: Box::new(inner),
793 })),
794 });
795 }
796 Ok(vec![Expr::Array(ArrayNode::Foreach {
797 array: outer,
798 var: vi,
799 body: Box::new(inner),
800 })])
801 }
802
803 fn static_dims(
806 &self,
807 ty: sv_lang::Type<'_>,
808 names: &[String],
809 ) -> Result<Vec<(String, i64, i64)>, BuildError> {
810 let mut dims = Vec::new();
811 let mut cur = ty;
812 for (k, name) in names.iter().enumerate() {
813 let c = cur.canonical();
814 let (left, right) = match array_shape(cur) {
815 Some(ArrayShape::Fixed) => {
816 let r = c.fixed_range();
817 (i64::from(r.left), i64::from(r.right))
818 }
819 None if c.is_integral() => {
820 let width = u32::try_from(c.bit_width()).map_err(|_| BuildError::Missing {
821 what: "representable width for a `foreach` array".into(),
822 })?;
823 let g = Geom::of(cur, width, "the `foreach` array")?;
824 (g.left, g.right)
825 }
826 _ => {
827 return Err(unsupported(
828 format!(
829 "a multi-dimensional `foreach`: dimension {} is a dynamic array, queue or associative array (only fixed-size dimensions unroll)",
830 k + 1
831 ),
832 "C11",
833 ));
834 }
835 };
836 dims.push((name.clone(), left, right));
837 if k + 1 < names.len() {
838 cur = c.element_type().ok_or_else(|| {
839 unsupported(
840 format!(
841 "a `foreach` with {} loop variables over an array with fewer \
842 dimensions",
843 names.len()
844 ),
845 "C11",
846 )
847 })?;
848 }
849 }
850 Ok(dims)
851 }
852
853 fn unroll_nd(
856 &mut self,
857 dims: &[(String, i64, i64)],
858 body: Constraint<'_>,
859 ) -> Result<Vec<Expr>, BuildError> {
860 let Some((name, left, right)) = dims.first() else {
861 return self.constraint(body);
862 };
863 if name.is_empty() {
864 return self.unroll_nd(&dims[1..], body);
865 }
866 let idxs: Vec<i64> = if left >= right {
867 (*right..=*left).rev().collect()
868 } else {
869 (*left..=*right).collect()
870 };
871 let mut out = Vec::new();
872 for i in idxs {
873 self.loops.push(LoopScope {
874 name: name.clone(),
875 value: LoopBind::Const(i),
876 index: None,
877 });
878 let r = self.unroll_nd(&dims[1..], body);
879 self.loops.pop();
880 out.extend(r?);
881 }
882 Ok(out)
883 }
884
885 fn unroll(
888 &mut self,
889 name: &str,
890 left: i64,
891 right: i64,
892 body: Constraint<'_>,
893 ) -> Result<Vec<Expr>, BuildError> {
894 let idxs: Vec<i64> = if left >= right {
895 (right..=left).rev().collect()
896 } else {
897 (left..=right).collect()
898 };
899 let mut out = Vec::new();
900 for i in idxs {
901 out.extend(self.in_scope(
902 LoopScope {
903 name: name.to_string(),
904 value: LoopBind::Const(i),
905 index: None,
906 },
907 body,
908 )?);
909 }
910 Ok(out)
911 }
912
913 pub(super) fn in_runtime_loop(&self) -> bool {
917 self.loops
918 .iter()
919 .any(|s| matches!(s.value, LoopBind::Runtime(..)))
920 }
921
922 fn scope_of(&self, name: &str) -> Option<&LoopScope> {
923 self.loops.iter().rev().find(|s| s.name == name)
924 }
925
926 fn bind_expr(&self, b: LoopBind, ty: Ty) -> Result<Expr, BuildError> {
927 match b {
928 LoopBind::Runtime(id, lt) => {
929 let l = Expr::Array(ArrayNode::Loop(id, lt));
930 Ok(if lt == ty {
931 l
932 } else {
933 Expr::Resize(Box::new(l), ty)
934 })
935 }
936 LoopBind::Const(i) => Ok(iconst(ty, i)),
937 LoopBind::Elem(x) => Ok(if x.ty() == ty {
938 x
939 } else {
940 Expr::Resize(Box::new(x), ty)
941 }),
942 }
943 }
944
945 pub(super) fn loop_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Result<Expr, BuildError> {
947 debug_assert_eq!(sym.kind(), SymbolKind::Iterator);
948 let name = sym.name();
949 let b = self
950 .scope_of(name)
951 .ok_or_else(|| BuildError::Missing {
952 what: format!("an enclosing `foreach` or array method binding `{name}`"),
953 })?
954 .value
955 .clone();
956 if matches!(b, LoopBind::Runtime(..)) {
957 self.arrays_allowed("a loop variable")?;
958 }
959 self.bind_expr(b, ty)
960 }
961
962 pub(super) fn iterator_index(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
964 let this = *e.operands().first().ok_or_else(|| BuildError::Missing {
965 what: "iterator of `index`".into(),
966 })?;
967 let name = this
968 .referenced_symbol()
969 .map(|s| s.name().to_string())
970 .ok_or_else(|| BuildError::Missing {
971 what: "symbol of the iterator of `index`".into(),
972 })?;
973 let b = self
974 .scope_of(&name)
975 .and_then(|s| s.index.clone())
976 .ok_or_else(|| BuildError::Missing {
977 what: format!("an array method iterator `{name}` for `.index`"),
978 })?;
979 self.arrays_allowed("an iterator index")?;
980 self.bind_expr(b, ty)
981 }
982
983 pub(super) fn array_reduction(
986 &mut self,
987 e: Expression<'_>,
988 op: ReduceOp,
989 ty: Ty,
990 ) -> Result<Expr, BuildError> {
991 let arr = e
992 .operands()
993 .into_iter()
994 .find(|o| o.expr_type().is_some_and(|t| array_shape(t).is_some()))
995 .ok_or_else(|| BuildError::Missing {
996 what: "array operand of a reduction method".into(),
997 })?;
998 if arr.expr_type().and_then(array_shape) == Some(ArrayShape::Fixed) {
999 return self.fixed_reduction(e, arr, op, ty);
1000 }
1001 self.arrays_allowed("an array reduction")?;
1002 let (array, row) = self.array_ref(arr)?;
1003 self.reject_nested(array, "a reduction method")?;
1004 let elem_ty = self.arrays[array as usize].elem_ty;
1005 let var = self.fresh_loop();
1006 let index_var = self.fresh_loop();
1007 let body = match e.iterator_expr() {
1008 Some(w) => {
1009 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1010 what: "iterator variable of a reduction's `with` clause".into(),
1011 })?;
1012 self.loops.push(LoopScope {
1013 name: iter.name().to_string(),
1014 value: LoopBind::Runtime(var, elem_ty),
1015 index: Some(LoopBind::Runtime(index_var, Ty::signed(32))),
1016 });
1017 let b = self.expr(w);
1018 self.loops.pop();
1019 b?
1020 }
1021 None => Expr::Array(ArrayNode::Loop(var, elem_ty)),
1022 };
1023 let body = if body.ty() == ty {
1024 body
1025 } else {
1026 coerce(body, ty)
1027 };
1028 Ok(Self::row_scoped(
1029 array,
1030 row,
1031 Expr::Array(ArrayNode::Reduce {
1032 op,
1033 array,
1034 var,
1035 index_var,
1036 body: Box::new(body),
1037 }),
1038 ))
1039 }
1040
1041 fn fixed_reduction(
1046 &mut self,
1047 e: Expression<'_>,
1048 arr: Expression<'_>,
1049 op: ReduceOp,
1050 ty: Ty,
1051 ) -> Result<Expr, BuildError> {
1052 let at = arr
1053 .expr_type()
1054 .ok_or_else(|| BuildError::Missing {
1055 what: "type of the reduced array".into(),
1056 })?
1057 .canonical();
1058 let r = at.fixed_unpacked_array_range();
1059 let (lo, hi) = (
1060 i64::from(r.left).min(i64::from(r.right)),
1061 i64::from(r.left).max(i64::from(r.right)),
1062 );
1063 let et = at.element_type().ok_or_else(|| BuildError::Missing {
1064 what: "element type of the reduced array".into(),
1065 })?;
1066 let elem_ty = ty_of(Some(et), "an element of the reduced array")?;
1067 let mut acc: Option<Expr> = None;
1068 for i in lo..=hi {
1069 let item = self.aggregate_element(arr, i, elem_ty)?;
1070 let body = match e.iterator_expr() {
1071 Some(w) => {
1072 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1073 what: "iterator variable of a reduction's `with` clause".into(),
1074 })?;
1075 self.loops.push(LoopScope {
1076 name: iter.name().to_string(),
1077 value: LoopBind::Elem(item),
1078 index: Some(LoopBind::Const(i)),
1079 });
1080 let b = self.expr(w);
1081 self.loops.pop();
1082 b?
1083 }
1084 None => item,
1085 };
1086 let body = if body.ty() == ty {
1087 body
1088 } else {
1089 coerce(body, ty)
1090 };
1091 acc = Some(match acc {
1092 None => body,
1093 Some(a) => {
1094 let bop = match op {
1095 ReduceOp::Sum => BinOp::Add,
1096 ReduceOp::Product => BinOp::Mul,
1097 ReduceOp::And => BinOp::And,
1098 ReduceOp::Or => BinOp::Or,
1099 ReduceOp::Xor => BinOp::Xor,
1100 };
1101 bin(bop, a, body, ty)
1102 }
1103 });
1104 }
1105 acc.ok_or_else(|| BuildError::Missing {
1106 what: "elements of the reduced array".into(),
1107 })
1108 }
1109
1110 pub(super) fn inside_array(
1117 &mut self,
1118 left: &Expr,
1119 arr: Expression<'_>,
1120 ) -> Result<Expr, BuildError> {
1121 let eq = |left: &Expr, x: Expr| {
1122 let t = common_ty(left.ty(), x.ty());
1123 bin(BinOp::Eq, coerce(left.clone(), t), coerce(x, t), Ty::BIT)
1124 };
1125 let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
1126 what: "type of an `inside` array".into(),
1127 })?;
1128 match array_shape(at) {
1129 Some(ArrayShape::Fixed) => {
1130 let c = at.canonical();
1131 let r = c.fixed_unpacked_array_range();
1132 let (lo, hi) = (
1133 i64::from(r.left).min(i64::from(r.right)),
1134 i64::from(r.left).max(i64::from(r.right)),
1135 );
1136 let et = c.element_type().ok_or_else(|| BuildError::Missing {
1137 what: "element type of an `inside` array".into(),
1138 })?;
1139 let elem_ty = ty_of(Some(et), "an element of an `inside` array")?;
1140 let mut acc: Option<Expr> = None;
1141 for i in lo..=hi {
1142 let item = self.aggregate_element(arr, i, elem_ty)?;
1143 let term = eq(left, item);
1144 acc = Some(match acc {
1145 None => term,
1146 Some(prev) => bin(BinOp::LogOr, prev, term, Ty::BIT),
1147 });
1148 }
1149 acc.ok_or_else(|| BuildError::Missing {
1150 what: "elements of an `inside` array".into(),
1151 })
1152 }
1153 Some(ArrayShape::Collection) => {
1154 self.arrays_allowed("an `inside` over an array")?;
1155 let (array, row) = self.array_ref(arr)?;
1156 self.reject_nested(array, "an `inside` set")?;
1157 let elem_ty = self.arrays[array as usize].elem_ty;
1158 let var = self.fresh_loop();
1159 let index_var = self.fresh_loop();
1160 let body = eq(left, Expr::Array(ArrayNode::Loop(var, elem_ty)));
1161 Ok(Self::row_scoped(
1162 array,
1163 row,
1164 Expr::Array(ArrayNode::Reduce {
1165 op: ReduceOp::Or,
1166 array,
1167 var,
1168 index_var,
1169 body: Box::new(body),
1170 }),
1171 ))
1172 }
1173 Some(ArrayShape::Assoc) | None => Err(bad_array(
1174 "an associative array in an `inside` set: its elements are not constraint \
1175 operands (IEEE 1800-2023 18.4)",
1176 )),
1177 }
1178 }
1179
1180 pub(super) fn array_call(
1183 &mut self,
1184 e: Expression<'_>,
1185 ty: Ty,
1186 ) -> Option<Result<Expr, BuildError>> {
1187 let sub = e.system_subroutine()?;
1188 let op = match sub.known_name_id() {
1189 KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num => {
1190 let arr = Self::size_call_array(e)?;
1191 return Some(self.array_size(arr));
1192 }
1193 KnownSystemName::Index => return Some(self.iterator_index(e, ty)),
1194 KnownSystemName::Sum => ReduceOp::Sum,
1195 KnownSystemName::Product => ReduceOp::Product,
1196 KnownSystemName::And => ReduceOp::And,
1197 KnownSystemName::Or => ReduceOp::Or,
1198 KnownSystemName::XOr => ReduceOp::Xor,
1199 _ => return None,
1200 };
1201 Some(self.array_reduction(e, op, ty))
1202 }
1203}