1use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor};
2use calyx_ir::{
3 self as ir, CellType, Component, GetAttributes, LibrarySignatures,
4 RESERVED_NAMES,
5};
6use calyx_utils::{CalyxResult, Error, WithPos};
7use ir::Nothing;
8use ir::StaticTiming;
9use itertools::Itertools;
10use linked_hash_map::LinkedHashMap;
11use std::collections::HashMap;
12use std::collections::HashSet;
13
14fn port_is_static_prim(port: &ir::Port) -> bool {
18 let parent_cell = match &port.parent {
20 ir::PortParent::Cell(cell_wref) => cell_wref.upgrade(),
21 ir::PortParent::Group(_) | ir::PortParent::StaticGroup(_) => {
22 return false
23 }
24 };
25 let res = match parent_cell.borrow().prototype {
31 ir::CellType::Primitive { latency, .. } => latency.is_some(),
32 ir::CellType::Component { .. }
33 | ir::CellType::ThisComponent
34 | ir::CellType::Constant { .. } => false,
35 };
36 res
37}
38
39#[derive(Default)]
40struct ActiveAssignments {
41 assigns: Vec<ir::Assignment<Nothing>>,
43 num_assigns: Vec<usize>,
45}
46impl ActiveAssignments {
47 pub fn push(&mut self, assign: &[ir::Assignment<Nothing>]) {
49 let prev_size = self.assigns.len();
50 self.assigns.extend(assign.iter().cloned());
51 self.num_assigns.push(self.assigns.len() - prev_size);
53 }
54
55 pub fn pop(&mut self) {
57 let num_assigns = self.num_assigns.pop().unwrap();
58 self.assigns.truncate(self.assigns.len() - num_assigns);
59 }
60
61 pub fn iter(&self) -> impl Iterator<Item = &ir::Assignment<Nothing>> {
62 self.assigns.iter()
63 }
64}
65
66pub struct WellFormed {
77 reserved_names: HashSet<ir::Id>,
79 used_groups: HashSet<ir::Id>,
81 used_comb_groups: HashSet<ir::Id>,
83 ref_cell_types: HashMap<ir::Id, LinkedHashMap<ir::Id, CellType>>,
85 active_comb: ActiveAssignments,
87}
88
89impl ConstructVisitor for WellFormed {
90 fn from(ctx: &ir::Context) -> CalyxResult<Self>
91 where
92 Self: Sized,
93 {
94 let reserved_names =
95 RESERVED_NAMES.iter().map(|s| ir::Id::from(*s)).collect();
96
97 let mut ref_cell_types = HashMap::new();
98 for comp in ctx.components.iter() {
99 if comp.name == ctx.entrypoint {
101 for cell in comp.cells.iter() {
102 if cell.borrow().is_reference() {
103 return Err(Error::malformed_structure(
104 "ref cells are not allowed for main component",
105 )
106 .with_pos(cell.borrow().get_attributes()));
107 }
108 }
109 }
110
111 let cellmap: LinkedHashMap<ir::Id, CellType> = comp
113 .cells
114 .iter()
115 .filter_map(|cr| {
116 let cell = cr.borrow();
117 if cell.attributes.has(ir::BoolAttr::External)
119 && comp.name != ctx.entrypoint
120 {
121 Some(Err(Error::malformed_structure("Cell cannot be marked `@external` in non-entrypoint component").with_pos(&cell.attributes)))
122 } else if cell.is_reference() {
123 Some(Ok((cell.name(), cell.prototype.clone())))
124 } else {
125 None
126 }
127 })
128 .collect::<CalyxResult<_>>()?;
129 ref_cell_types.insert(comp.name, cellmap);
130 }
131
132 let w_f = WellFormed {
133 reserved_names,
134 used_groups: HashSet::new(),
135 used_comb_groups: HashSet::new(),
136 ref_cell_types,
137 active_comb: ActiveAssignments::default(),
138 };
139
140 Ok(w_f)
141 }
142
143 fn clear_data(&mut self) {
144 self.used_groups = HashSet::default();
145 self.used_comb_groups = HashSet::default();
146 }
147}
148
149impl Named for WellFormed {
150 fn name() -> &'static str {
151 "well-formed"
152 }
153
154 fn description() -> &'static str {
155 "Check if the structure and control are well formed."
156 }
157}
158
159fn obvious_conflicts<'a, I1, I2>(assigns1: I1, assigns2: I2) -> CalyxResult<()>
164where
165 I1: Iterator<Item = &'a ir::Assignment<Nothing>>,
166 I2: Iterator<Item = &'a ir::Assignment<StaticTiming>>,
167{
168 let dsts1 = assigns1.filter(|a| a.guard.is_true()).map(|a| {
169 (
170 a.dst.borrow().canonical(),
171 a.attributes
172 .copy_span()
173 .into_option()
174 .map(|s| s.show())
175 .unwrap_or_else(|| ir::Printer::assignment_to_str(a)),
176 )
177 });
178 let dsts2 = assigns2.filter(|a| a.guard.is_true()).map(|a| {
179 (
180 a.dst.borrow().canonical(),
181 a.attributes
182 .copy_span()
183 .into_option()
184 .map(|s| s.show())
185 .unwrap_or_else(|| ir::Printer::assignment_to_str(a)),
186 )
187 });
188 let dsts = dsts1.chain(dsts2);
189 let dst_grps = dsts
190 .sorted_by(|(dst1, _), (dst2, _)| ir::Canonical::cmp(dst1, dst2))
191 .group_by(|(dst, _)| dst.clone());
192
193 for (_, group) in &dst_grps {
194 let assigns = group.map(|(_, a)| a).collect_vec();
195 if assigns.len() > 1 {
196 let msg = assigns.into_iter().join("");
197 return Err(Error::malformed_structure(format!(
198 "Obviously conflicting assignments found:\n{}",
199 msg
200 )));
201 }
202 }
203 Ok(())
204}
205
206fn same_type(proto_out: &CellType, proto_in: &CellType) -> CalyxResult<()> {
207 if proto_out != proto_in {
208 Err(Error::malformed_control(format!(
209 "Unexpected type for ref cell. Expected `{}`, received `{}`",
210 proto_out.surface_name().unwrap(),
211 proto_in.surface_name().unwrap(),
212 )))
213 } else {
214 Ok(())
215 }
216}
217
218impl Visitor for WellFormed {
219 fn start(
220 &mut self,
221 comp: &mut Component,
222 _ctx: &LibrarySignatures,
223 comps: &[ir::Component],
224 ) -> VisResult {
225 for cell_ref in comp.cells.iter() {
226 let cell = cell_ref.borrow();
227 if self.reserved_names.contains(&cell.name()) {
229 return Err(Error::reserved_name(cell.name())
230 .with_pos(cell.get_attributes()));
231 }
232 if cell.is_reference() {
234 if cell.is_primitive(Some("std_const")) {
235 return Err(Error::malformed_structure(
236 "constant not allowed for ref cells".to_string(),
237 )
238 .with_pos(cell.get_attributes()));
239 }
240 if matches!(cell.prototype, CellType::ThisComponent) {
241 unreachable!(
242 "the current component not allowed for ref cells"
243 );
244 }
245 }
246 }
247
248 if comp.is_comb {
251 if !matches!(&*comp.control.borrow(), ir::Control::Empty(..)) {
252 return Err(Error::malformed_structure(format!("Component `{}` is marked combinational but has a non-empty control program", comp.name)));
253 }
254
255 if !comp.get_groups().is_empty() {
256 let group = comp.get_groups().iter().next().unwrap().borrow();
257 return Err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
258 }
259
260 if !comp.get_static_groups().is_empty() {
261 let group =
262 comp.get_static_groups().iter().next().unwrap().borrow();
263 return Err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
264 }
265
266 if !comp.comb_groups.is_empty() {
267 let group = comp.comb_groups.iter().next().unwrap().borrow();
268 return Err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
269 }
270
271 for cell_ref in comp.cells.iter() {
272 let cell = cell_ref.borrow();
273 let is_comb = match &cell.prototype {
274 CellType::Primitive { is_comb, .. } => is_comb.to_owned(),
275 CellType::Constant { .. } => true,
276 CellType::Component { name } => {
277 let comp_idx =
278 comps.iter().position(|x| x.name == name).unwrap();
279 let comp = comps
280 .get(comp_idx)
281 .expect("Found cell that does not exist");
282 comp.is_comb
283 }
284 _ => false,
285 };
286 if !is_comb {
287 return Err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains non-combinational cell `{}`", comp.name, cell.name())).with_pos(&cell.attributes));
288 }
289 }
290 }
291 if comp.latency.is_some() {
293 assert!(
294 matches!(&*comp.control.borrow(), &ir::Control::Static(_)),
295 "static component {} does not have static control. This should have been checked in ast_to_ir",
296 comp.name
297 );
298 }
299
300 let comp_sig = &comp.signature.borrow();
306 let go_ports =
307 comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec();
308 if go_ports.iter().any(|go_port| {
309 go_port.borrow().attributes.has(ir::NumAttr::Interval)
310 }) {
311 match &*comp.control.borrow() {
312 ir::Control::Static(_) | ir::Control::Empty(_) => (),
313 _ => return Err(Error::malformed_structure(
314 format!("component {} has dynamic control but has @interval annotations", comp.name),
315 )
316 .with_pos(&comp.attributes)),
317 };
318 if !comp.control.borrow().is_empty() {
319 let reference_val = match go_ports[0]
322 .borrow()
323 .attributes
324 .get(ir::NumAttr::Interval)
325 {
326 Some(val) => val,
327 None => {
328 return Err(Error::malformed_structure(
329 "@interval(n) attribute on all @go ports since there is static<n> control",
330 )
331 .with_pos(&comp.attributes))
332 }
333 };
334 for go_port in &go_ports {
336 let go_port_val = match go_port
337 .borrow()
338 .attributes
339 .get(ir::NumAttr::Interval)
340 {
341 Some(val) => val,
342 None => {
343 return Err(Error::malformed_structure(format!(
344 "@go port expected @interval({reference_val}) attribute on all ports \
345 since the component has static<n> control",
346 ))
347 .with_pos(&comp.attributes))
348 }
349 };
350 if go_port_val != reference_val {
351 return Err(Error::malformed_structure(format!(
352 "@go port expected @interval {reference_val}, got @interval {go_port_val}",
353 ))
354 .with_pos(&go_port.borrow().attributes));
355 }
356 match comp.control.borrow().get_latency() {
358 None => {
359 unreachable!("already checked control is static")
360 }
361 Some(control_latency) => {
362 if control_latency != reference_val {
363 return Err(Error::malformed_structure(format!(
364 "component {} expected @interval {reference_val}, got @interval {control_latency}", comp.name,
365 ))
366 .with_pos(&comp.attributes));
367 }
368 }
369 }
370 }
371 }
372 }
373
374 for gr in comp.get_groups().iter() {
377 let group = gr.borrow();
378 let gname = group.name();
379 let mut has_done = false;
380 for assign in &group.assignments {
382 let dst = assign.dst.borrow();
383 if port_is_static_prim(&dst) {
384 return Err(Error::malformed_structure(format!(
385 "Static cell `{}` written to in non-static group",
386 dst.get_parent_name()
387 ))
388 .with_pos(&assign.attributes));
389 }
390 if dst.is_hole() && dst.name == "done" {
391 if has_done {
393 return Err(Error::malformed_structure(format!(
394 "Group `{}` has multiple done conditions",
395 gname
396 ))
397 .with_pos(&assign.attributes));
398 } else {
399 has_done = true;
400 }
401 if gname != dst.get_parent_name() {
403 return Err(Error::malformed_structure(
404 format!("Group `{}` refers to the done condition of another group (`{}`).",
405 gname,
406 dst.get_parent_name())).with_pos(&dst.attributes));
407 }
408 }
409 }
410
411 if !has_done {
413 return Err(Error::malformed_structure(format!(
414 "No writes to the `done' hole for group `{gname}'",
415 ))
416 .with_pos(&group.attributes));
417 }
418 }
419
420 for gr in comp.get_static_groups().iter() {
424 let group = gr.borrow();
425 let group_latency = group.get_latency();
426 for assign in &group.assignments {
428 assign.guard.check_for_each_info(
429 &mut |static_timing: &StaticTiming| {
430 if static_timing.get_interval().0
431 >= static_timing.get_interval().1
432 {
433 Err(Error::malformed_structure(format!(
434 "Static Timing Guard has improper interval: `{}`",
435 static_timing.to_string()
436 ))
437 .with_pos(&assign.attributes))
438 } else if static_timing.get_interval().1 > group_latency {
439 Err(Error::malformed_structure(format!(
440 "Static Timing Guard has interval `{}`, which is out of bounds since its static group has latency {}",
441 static_timing.to_string(),
442 group_latency
443 ))
444 .with_pos(&assign.attributes))
445 } else {
446 Ok(())
447 }
448 },
449 )?;
450 }
451 }
452
453 obvious_conflicts(
455 comp.continuous_assignments.iter(),
456 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
457 )?;
458 for cgr in comp.comb_groups.iter() {
460 for assign in &cgr.borrow().assignments {
461 let dst = assign.dst.borrow();
462 if port_is_static_prim(&dst) {
463 return Err(Error::malformed_structure(format!(
464 "Static cell `{}` written to in non-static group",
465 dst.get_parent_name()
466 ))
467 .with_pos(&assign.attributes));
468 }
469 }
470 obvious_conflicts(
471 cgr.borrow()
472 .assignments
473 .iter()
474 .chain(comp.continuous_assignments.iter()),
475 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
476 )?;
477 }
478
479 Ok(Action::Continue)
480 }
481
482 fn static_enable(
483 &mut self,
484 s: &mut ir::StaticEnable,
485 comp: &mut Component,
486 _ctx: &LibrarySignatures,
487 _comps: &[ir::Component],
488 ) -> VisResult {
489 self.used_groups.insert(s.group.borrow().name());
490
491 let group = s.group.borrow();
492
493 obvious_conflicts(
495 comp.continuous_assignments
496 .iter()
497 .chain(self.active_comb.iter()),
498 group.assignments.iter(),
499 )
500 .map_err(|err| {
501 let msg = s
502 .attributes
503 .copy_span()
504 .into_option()
505 .map(|s| s.format("Assigments activated by group enable"));
506 err.with_post_msg(msg)
507 })?;
508
509 Ok(Action::Continue)
510 }
511
512 fn enable(
513 &mut self,
514 s: &mut ir::Enable,
515 comp: &mut Component,
516 _ctx: &LibrarySignatures,
517 _comps: &[ir::Component],
518 ) -> VisResult {
519 self.used_groups.insert(s.group.borrow().name());
520
521 let group = s.group.borrow();
522 let asgn = group.done_cond();
523 let const_done_assign =
524 asgn.guard.is_true() && asgn.src.borrow().is_constant(1, 1);
525
526 if const_done_assign {
527 return Err(Error::malformed_structure("Group with constant done condition is invalid. Use `comb group` instead to define a combinational group.").with_pos(&group.attributes));
528 }
529
530 if group
532 .attributes
533 .get(ir::NumAttr::Promotable)
534 .map(|v| v == 0)
535 .unwrap_or(false)
536 {
537 return Err(Error::malformed_structure("Group with annotation \"promotable\"=0 is invalid. Use `comb group` instead to define a combinational group or if the group's done condition is not constant, provide the correct \"static\" annotation.").with_pos(&group.attributes));
538 }
539
540 obvious_conflicts(
542 group
543 .assignments
544 .iter()
545 .chain(comp.continuous_assignments.iter())
546 .chain(self.active_comb.iter()),
547 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
548 )
549 .map_err(|err| {
550 let msg = s
551 .attributes
552 .copy_span()
553 .into_option()
554 .map(|s| s.format("Assigments activated by group enable"));
555 err.with_post_msg(msg)
556 })?;
557
558 Ok(Action::Continue)
559 }
560
561 fn invoke(
562 &mut self,
563 s: &mut ir::Invoke,
564 _comp: &mut Component,
565 _ctx: &LibrarySignatures,
566 _comps: &[ir::Component],
567 ) -> VisResult {
568 if let Some(c) = &s.comb_group {
569 self.used_comb_groups.insert(c.borrow().name());
570 }
571 let cell = s.comp.borrow();
573
574 if let CellType::Component { name: id } = &cell.prototype {
575 let cellmap = &self.ref_cell_types[id];
576 let mut mentioned_cells = HashSet::new();
577 for (outcell, incell) in s.ref_cells.iter() {
578 if let Some(t) = cellmap.get(outcell) {
579 let proto = incell.borrow().prototype.clone();
580 same_type(t, &proto)
581 .map_err(|err| err.with_pos(&s.attributes))?;
582 mentioned_cells.insert(outcell);
583 } else {
584 return Err(Error::malformed_control(format!(
585 "{} does not have ref cell named {}",
586 id, outcell
587 )));
588 }
589 }
590 for id in cellmap.keys() {
591 if mentioned_cells.get(id).is_none() {
592 return Err(Error::malformed_control(format!(
593 "unmentioned ref cell: {}",
594 id
595 ))
596 .with_pos(&s.attributes));
597 }
598 }
599 }
600
601 Ok(Action::Continue)
602 }
603
604 fn static_invoke(
605 &mut self,
606 s: &mut ir::StaticInvoke,
607 _comp: &mut Component,
608 _ctx: &LibrarySignatures,
609 _comps: &[ir::Component],
610 ) -> VisResult {
611 let cell = s.comp.borrow();
613
614 if let CellType::Component { name: id } = &cell.prototype {
615 let cellmap = &self.ref_cell_types[id];
616 let mut mentioned_cells = HashSet::new();
617 for (outcell, incell) in s.ref_cells.iter() {
618 if let Some(t) = cellmap.get(outcell) {
619 let proto = incell.borrow().prototype.clone();
620 same_type(t, &proto)
621 .map_err(|err| err.with_pos(&s.attributes))?;
622 mentioned_cells.insert(outcell);
623 } else {
624 return Err(Error::malformed_control(format!(
625 "{} does not have ref cell named {}",
626 id, outcell
627 )));
628 }
629 }
630 for id in cellmap.keys() {
631 if mentioned_cells.get(id).is_none() {
632 return Err(Error::malformed_control(format!(
633 "unmentioned ref cell: {}",
634 id
635 ))
636 .with_pos(&s.attributes));
637 }
638 }
639 }
640
641 Ok(Action::Continue)
642 }
643
644 fn start_if(
645 &mut self,
646 s: &mut ir::If,
647 _comp: &mut Component,
648 _sigs: &LibrarySignatures,
649 _comps: &[ir::Component],
650 ) -> VisResult {
651 if let Some(cgr) = &s.cond {
652 let cg = cgr.borrow();
653 let assigns = &cg.assignments;
654 obvious_conflicts(
656 assigns.iter().chain(self.active_comb.iter()),
657 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
658 )
659 .map_err(|err| {
660 let msg = s.attributes.copy_span().format(format!(
661 "Assignments from `{}' are actived here",
662 cg.name()
663 ));
664 err.with_post_msg(Some(msg))
665 })?;
666 self.active_comb.push(assigns);
668 } else if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
669 let msg = s.attributes.copy_span().format(format!(
670 "If statement has no comb group and its condition port {} is unstable",
671 s.port.borrow().canonical()
672 ));
673 log::warn!("{msg}");
674 }
675 Ok(Action::Continue)
676 }
677
678 fn start_static_if(
679 &mut self,
680 s: &mut ir::StaticIf,
681 _comp: &mut Component,
682 _sigs: &LibrarySignatures,
683 _comps: &[ir::Component],
684 ) -> VisResult {
685 if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
686 let msg = s.attributes.copy_span().format(format!(
687 "static if statement's condition port {} is unstable",
688 s.port.borrow().canonical()
689 ));
690 log::warn!("{msg}");
691 }
692 Ok(Action::Continue)
693 }
694
695 fn finish_if(
696 &mut self,
697 s: &mut ir::If,
698 _comp: &mut Component,
699 _ctx: &LibrarySignatures,
700 _comps: &[ir::Component],
701 ) -> VisResult {
702 if let Some(cond) = &s.cond {
704 self.used_comb_groups.insert(cond.borrow().name());
705 self.active_comb.pop();
707 }
708 Ok(Action::Continue)
709 }
710
711 fn start_while(
712 &mut self,
713 s: &mut ir::While,
714 _comp: &mut Component,
715 _sigs: &LibrarySignatures,
716 _comps: &[ir::Component],
717 ) -> VisResult {
718 if let Some(cgr) = &s.cond {
719 let cg = cgr.borrow();
720 let assigns = &cg.assignments;
721 obvious_conflicts(
723 assigns.iter().chain(self.active_comb.iter()),
724 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
725 )
726 .map_err(|err| {
727 let msg = s.attributes.copy_span().format(format!(
728 "Assignments from `{}' are actived here",
729 cg.name()
730 ));
731 err.with_post_msg(Some(msg))
732 })?;
733 self.active_comb.push(assigns);
735 } else if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
736 let msg = s.attributes.copy_span().format(format!(
737 "While loop has no comb group and its condition port {} is unstable",
738 s.port.borrow().canonical()
739 ));
740 log::warn!("{msg}");
741 }
742 Ok(Action::Continue)
743 }
744
745 fn finish_while(
746 &mut self,
747 s: &mut ir::While,
748 _comp: &mut Component,
749 _ctx: &LibrarySignatures,
750 _comps: &[ir::Component],
751 ) -> VisResult {
752 if let Some(cond) = &s.cond {
754 self.used_comb_groups.insert(cond.borrow().name());
755 self.active_comb.pop();
757 }
758 Ok(Action::Continue)
759 }
760
761 fn finish(
762 &mut self,
763 comp: &mut Component,
764 _ctx: &LibrarySignatures,
765 _comps: &[ir::Component],
766 ) -> VisResult {
767 comp.for_each_assignment(|assign| {
769 assign.for_each_port(|pr| {
770 let port = pr.borrow();
771 if port.is_hole() && port.name == "go" {
772 self.used_groups.insert(port.get_parent_name());
773 }
774 None
775 })
776 });
777 comp.for_each_static_assignment(|assign| {
778 assign.for_each_port(|pr| {
779 let port = pr.borrow();
780 if port.is_hole() && port.name == "go" {
781 self.used_groups.insert(port.get_parent_name());
782 }
783 None
784 })
785 });
786
787 let mut all_groups: HashSet<ir::Id> = comp
789 .get_groups()
790 .iter()
791 .map(|g| g.borrow().name())
792 .collect();
793 let static_groups: HashSet<ir::Id> = comp
794 .get_static_groups()
795 .iter()
796 .map(|g| g.borrow().name())
797 .collect();
798 all_groups.extend(static_groups);
799
800 if let Some(group) = all_groups.difference(&self.used_groups).next() {
801 match comp.find_group(*group) {
802 Some(gr) => {
803 let gr = gr.borrow();
804 return Err(
805 Error::unused(*group, "group").with_pos(&gr.attributes)
806 );
807 }
808 None => {
809 let gr = comp.find_static_group(*group).unwrap();
810 let gr = gr.borrow();
811 return Err(
812 Error::unused(*group, "group").with_pos(&gr.attributes)
813 );
814 }
815 }
816 };
817
818 let all_comb_groups: HashSet<ir::Id> =
819 comp.comb_groups.iter().map(|g| g.borrow().name()).collect();
820 if let Some(comb_group) =
821 all_comb_groups.difference(&self.used_comb_groups).next()
822 {
823 let cgr = comp.find_comb_group(*comb_group).unwrap();
824 let cgr = cgr.borrow();
825 return Err(Error::unused(*comb_group, "combinational group")
826 .with_pos(&cgr.attributes));
827 }
828 Ok(Action::Continue)
829 }
830}