1use core::panic;
2use std::{
3 collections::{BTreeMap, HashMap, HashSet},
4 ops::Add,
5};
6
7use crate::traversal::{
8 Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor,
9};
10use calyx_ir::{self as ir, BoolAttr, Guard, Id, Nothing, NumAttr};
11use calyx_utils::{CalyxResult, OutputFile};
12use serde::Serialize;
13
14#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
15struct StatsEntry {
16 group_probe: u32,
17 structural_enable_probe: u32,
18 cell_probe: u32,
19 primitive_probe: u32,
20}
21
22impl Add for StatsEntry {
23 type Output = Self;
24
25 fn add(self, other: Self) -> Self {
26 Self {
27 group_probe: self.group_probe + other.group_probe,
28 structural_enable_probe: self.structural_enable_probe
29 + other.structural_enable_probe,
30 cell_probe: self.cell_probe + other.cell_probe,
31 primitive_probe: self.primitive_probe + other.primitive_probe,
32 }
33 }
34}
35
36pub struct ProfilerInstrumentation {
39 probe_stats: BTreeMap<String, StatsEntry>,
40 emit_probe_stats: Option<OutputFile>,
41}
42
43type CallsFromGroupMap<T> = HashMap<Id, Vec<(Id, ir::Guard<T>)>>;
46
47impl Named for ProfilerInstrumentation {
48 fn name() -> &'static str {
49 "profiler-instrumentation"
50 }
51
52 fn description() -> &'static str {
53 "Add instrumentation for profiling"
54 }
55
56 fn opts() -> Vec<crate::traversal::PassOpt> {
57 vec![PassOpt::new(
58 "emit-probe-stats",
59 "emit json file of shared cells",
60 ParseVal::OutStream(OutputFile::Null),
61 PassOpt::parse_outstream,
62 )]
63 }
64}
65
66impl ConstructVisitor for ProfilerInstrumentation {
67 fn from(ctx: &ir::Context) -> CalyxResult<Self>
68 where
69 Self: Sized + Named,
70 {
71 let opts = Self::get_opts(ctx);
72
73 Ok(ProfilerInstrumentation {
74 probe_stats: BTreeMap::new(),
75 emit_probe_stats: opts["emit-probe-stats"].not_null_outstream(),
76 })
77 }
78
79 fn clear_data(&mut self) {}
80}
81
82fn count_helper<T>(map_opt: Option<CallsFromGroupMap<T>>) -> u32 {
83 match map_opt {
84 Some(map) => map
85 .values()
86 .fold(0, |acc, vec_ref| acc + vec_ref.len() as u32),
87 None => 0,
88 }
89}
90
91fn count<T>(
92 num_groups: u32,
93 structural_enable_map_opt: Option<CallsFromGroupMap<T>>,
94 cell_invoke_map_opt: Option<CallsFromGroupMap<T>>,
95 primitive_map_opt: Option<CallsFromGroupMap<T>>,
96) -> StatsEntry {
97 let num_structural_enables = count_helper(structural_enable_map_opt);
98 let num_cell_invokes = count_helper(cell_invoke_map_opt);
99 let num_primitive_invokes = count_helper(primitive_map_opt);
100
101 StatsEntry {
102 group_probe: num_groups,
103 structural_enable_probe: num_structural_enables,
104 cell_probe: num_cell_invokes,
105 primitive_probe: num_primitive_invokes,
106 }
107}
108
109fn group(
111 comp: &mut ir::Component,
112 sigs: &ir::LibrarySignatures,
113 collect_stats: bool,
114) -> Option<StatsEntry> {
115 let mut structural_enable_map: CallsFromGroupMap<Nothing> = HashMap::new();
117 let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
119 let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
121 let group_names = comp
122 .groups
123 .iter()
124 .map(|group| group.borrow().name())
125 .collect::<Vec<_>>();
126
127 for group_ref in comp.groups.iter() {
129 let group = &group_ref.borrow();
130 let mut comb_primitives_covered = HashSet::new();
132 let mut primitive_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
133 for assignment_ref in group.assignments.iter() {
134 let dst_borrow = assignment_ref.dst.borrow();
135 if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
136 && dst_borrow.name == "go"
137 {
138 let invoked_group_name =
140 parent_group_ref.upgrade().borrow().name();
141 let guard = *(assignment_ref.guard.clone());
142 match structural_enable_map.get_mut(&invoked_group_name) {
143 Some(vec_ref) => vec_ref.push((group.name(), guard)),
144 None => {
145 structural_enable_map.insert(
146 invoked_group_name,
147 vec![(group.name(), guard)],
148 );
149 }
150 }
151 }
152 if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
153 match cell_ref.upgrade().borrow().prototype.clone() {
154 calyx_ir::CellType::Primitive {
155 name: _,
156 param_binding: _,
157 is_comb,
158 latency: _,
159 } => {
160 let cell_name = cell_ref.upgrade().borrow().name();
161 if is_comb {
162 if comb_primitives_covered.insert(cell_name) {
164 primitive_vec.push((cell_name, Guard::True));
165 }
166 } else if dst_borrow.has_attribute(NumAttr::Go) {
167 let guard = Guard::and(
169 *(assignment_ref.guard.clone()),
170 Guard::port(ir::rrc(
171 assignment_ref.src.borrow().clone(),
172 )),
173 );
174 primitive_vec.push((cell_name, guard));
175 }
176 }
177 calyx_ir::CellType::Component { name: _ } => {
178 if dst_borrow.has_attribute(NumAttr::Go) {
179 let cell_name = cell_ref.upgrade().borrow().name();
180 let guard = *(assignment_ref.guard.clone());
181 match cell_invoke_map.get_mut(&group.name()) {
182 Some(vec_ref) => {
183 vec_ref.push((cell_name, guard));
184 }
185 None => {
186 cell_invoke_map.insert(
187 group.name(),
188 vec![(cell_name, guard)],
189 );
190 }
191 }
192 }
193 }
194 _ => (),
195 }
196 }
197 }
198 primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
199 }
200
201 let group_name_assign_and_cell = create_assignments(
203 comp,
204 sigs,
205 &group_names,
206 Some(&structural_enable_map),
207 Some(&cell_invoke_map),
208 Some(&primitive_invoke_map),
209 );
210
211 for group in comp.groups.iter() {
213 for (group_name, asgn, cell) in group_name_assign_and_cell.iter() {
214 if group.borrow().name() == group_name {
215 group.borrow_mut().assignments.push(asgn.clone());
216 comp.cells.add(cell.to_owned());
217 }
218 }
219 }
220
221 if collect_stats {
222 Some(count(
223 group_names.len() as u32,
224 Some(structural_enable_map),
225 Some(cell_invoke_map),
226 Some(primitive_invoke_map),
227 ))
228 } else {
229 None
230 }
231}
232
233fn combinational_group(
235 comp: &mut ir::Component,
236 sigs: &ir::LibrarySignatures,
237 collect_stats: bool,
238) -> Option<StatsEntry> {
239 let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
243 let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
245
246 let group_names = comp
247 .comb_groups
248 .iter()
249 .map(|group| group.borrow().name())
250 .collect::<Vec<_>>();
251
252 for group_ref in comp.comb_groups.iter() {
253 let group = &group_ref.borrow();
254 let mut comb_primitives_covered = HashSet::new();
255 let mut comb_cells_covered = HashSet::new();
256
257 for assignment_ref in group.assignments.iter() {
258 let dst_borrow = assignment_ref.dst.borrow();
259 if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
260 match cell_ref.upgrade().borrow().prototype.clone() {
261 calyx_ir::CellType::Primitive {
262 name: _,
263 param_binding: _,
264 is_comb,
265 latency: _,
266 } => {
267 let cell_name = cell_ref.upgrade().borrow().name();
268 if is_comb {
269 if comb_primitives_covered.insert(cell_name) {
271 match primitive_invoke_map
272 .get_mut(&group.name())
273 {
274 Some(vec_ref) => {
275 vec_ref.push((cell_name, Guard::True));
276 }
277 None => {
278 primitive_invoke_map.insert(
279 group.name(),
280 vec![(cell_name, Guard::True)],
281 );
282 }
283 }
284 }
285 } else if dst_borrow.has_attribute(NumAttr::Go) {
286 panic!(
287 "Non-combinational primitive {} invoked inside of combinational group {}!",
288 dst_borrow.canonical(),
289 group.name()
290 )
291 }
292 }
293 calyx_ir::CellType::Component { name: _ } => {
294 let cell_name = cell_ref.upgrade().borrow().name();
295 if dst_borrow.name == "go" {
296 panic!(
297 "Non-combinational cell {} invoked inside of combinational group {}!",
298 cell_name,
299 group.name()
300 );
301 } else if comb_cells_covered.insert(cell_name) {
302 let guard = *(assignment_ref.guard.clone());
303 match cell_invoke_map.get_mut(&group.name()) {
304 Some(vec_ref) => {
305 vec_ref.push((cell_name, guard));
306 }
307 None => {
308 cell_invoke_map.insert(
309 group.name(),
310 vec![(cell_name, guard)],
311 );
312 }
313 }
314 }
315 }
316 _ => (),
317 }
318 }
319 }
320 }
321
322 let group_name_asgn_and_cell = create_assignments(
323 comp,
324 sigs,
325 &group_names,
326 None, Some(&cell_invoke_map),
328 Some(&primitive_invoke_map),
329 );
330
331 for comb_group in comp.comb_groups.iter() {
333 for (comb_group_name, asgn, cell) in group_name_asgn_and_cell.iter() {
334 if comb_group.borrow().name() == comb_group_name {
335 comb_group.borrow_mut().assignments.push(asgn.clone());
336 comp.cells.add(cell.to_owned());
337 }
338 }
339 }
340
341 if collect_stats {
342 Some(count(
343 group_names.len() as u32,
344 None,
345 Some(cell_invoke_map),
346 Some(primitive_invoke_map),
347 ))
348 } else {
349 None
350 }
351}
352
353fn static_group(
355 comp: &mut ir::Component,
356 sigs: &ir::LibrarySignatures,
357 collect_stats: bool,
358) -> Option<StatsEntry> {
359 let group_names = comp
360 .static_groups
361 .iter()
362 .map(|group| group.borrow().name())
363 .collect::<Vec<_>>();
364
365 let mut structural_enable_map: CallsFromGroupMap<ir::StaticTiming> =
367 HashMap::new();
368 let mut cell_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
370 HashMap::new();
371 let mut primitive_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
373 HashMap::new();
374
375 for group_ref in comp.static_groups.iter() {
376 let group = &group_ref.borrow();
377 let mut comb_primitives_covered = HashSet::new();
379 let mut primitive_vec: Vec<(Id, ir::Guard<ir::StaticTiming>)> =
380 Vec::new();
381 for assignment_ref in group.assignments.iter() {
382 let dst_borrow = assignment_ref.dst.borrow();
383 if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
384 && dst_borrow.name == "go"
385 {
386 let invoked_group_name =
388 parent_group_ref.upgrade().borrow().name();
389 let guard = *(assignment_ref.guard).clone();
390 structural_enable_map
391 .entry(invoked_group_name)
392 .or_default()
393 .push((group.name(), guard));
394 }
395 if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
396 match cell_ref.upgrade().borrow().prototype.clone() {
397 calyx_ir::CellType::Primitive { is_comb, .. } => {
398 let cell_name = cell_ref.upgrade().borrow().name();
399 if is_comb {
400 if comb_primitives_covered.insert(cell_name) {
402 primitive_vec.push((cell_name, Guard::True));
403 }
404 } else if dst_borrow.has_attribute(NumAttr::Go) {
405 let guard = Guard::and(
407 *(assignment_ref.guard).clone(),
408 Guard::port(ir::rrc(
409 assignment_ref.src.borrow().clone(),
410 )),
411 );
412 primitive_vec.push((cell_name, guard));
413 }
414 }
415 calyx_ir::CellType::Component { name: _ } => {
416 if dst_borrow.has_attribute(NumAttr::Go) {
417 let cell_name = cell_ref.upgrade().borrow().name();
418 let guard = *(assignment_ref.guard.clone());
419 cell_invoke_map
420 .entry(group.name())
421 .or_default()
422 .push((cell_name, guard));
423 }
424 }
425 _ => (),
426 }
427 }
428 }
429 primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
430 }
431
432 let group_name_assign_and_cell = create_assignments(
433 comp,
434 sigs,
435 &group_names,
436 Some(&structural_enable_map),
437 Some(&cell_invoke_map),
438 Some(&primitive_invoke_map),
439 );
440
441 for static_group in comp.static_groups.iter() {
443 for (static_group_name, asgn, cell) in group_name_assign_and_cell.iter()
444 {
445 if static_group.borrow().name() == static_group_name {
446 static_group.borrow_mut().assignments.push(asgn.clone());
447 comp.cells.add(cell.to_owned());
448 }
449 }
450 }
451
452 if collect_stats {
453 Some(count(
454 group_names.len() as u32,
455 Some(structural_enable_map),
456 Some(cell_invoke_map),
457 Some(primitive_invoke_map),
458 ))
459 } else {
460 None
461 }
462}
463
464fn create_assignments<T: Clone>(
470 comp: &mut ir::Component,
471 sigs: &ir::LibrarySignatures,
472 group_names: &[Id],
473 structural_enable_map_opt: Option<&CallsFromGroupMap<T>>,
474 cell_invoke_map_opt: Option<&CallsFromGroupMap<T>>,
475 primitive_invoke_map_opt: Option<&CallsFromGroupMap<T>>,
476) -> Vec<(
477 Id,
478 calyx_ir::Assignment<T>,
479 std::rc::Rc<std::cell::RefCell<calyx_ir::Cell>>,
480)> {
481 let delimiter = "___";
482 let comp_name = comp.name;
483 let mut builder = ir::Builder::new(comp, sigs);
485 let one = builder.add_constant(1, 1);
486
487 let mut group_name_assign_and_cell = Vec::new();
490
491 for group_name in group_names.iter() {
493 let name = format!("{group_name}{delimiter}{comp_name}_group_probe");
495 let probe_cell = builder.add_primitive(name, "std_wire", &[1]);
496 let probe_asgn: ir::Assignment<T> = builder.build_assignment(
497 probe_cell.borrow().get("in"),
498 one.borrow().get("out"),
499 Guard::True,
500 );
501 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
503 probe_cell
504 .borrow_mut()
505 .add_attribute(BoolAttr::Protected, 1);
506 group_name_assign_and_cell.push((*group_name, probe_asgn, probe_cell));
507 }
508
509 if let Some(sem) = structural_enable_map_opt {
510 for (invoked_group_name, parent_groups) in sem.iter() {
512 for (parent_group, guard) in parent_groups.iter() {
513 let probe_cell_name = format!(
514 "{invoked_group_name}{delimiter}{parent_group}{delimiter}{comp_name}_se_probe"
515 );
516 let probe_cell =
517 builder.add_primitive(probe_cell_name, "std_wire", &[1]);
518 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
519 probe_cell
520 .borrow_mut()
521 .add_attribute(BoolAttr::Protected, 1);
522 let probe_asgn: ir::Assignment<T> = builder.build_assignment(
523 probe_cell.borrow().get("in"),
524 one.borrow().get("out"),
525 guard.clone(),
526 );
527 group_name_assign_and_cell.push((
528 *parent_group,
529 probe_asgn,
530 probe_cell,
531 ));
532 }
533 }
534 }
535
536 if let Some(cell_invoke_map) = cell_invoke_map_opt {
537 for (invoker_group, invoked_cells) in cell_invoke_map.iter() {
539 for (invoked_cell, guard) in invoked_cells {
540 let probe_cell_name = format!(
541 "{invoked_cell}{delimiter}{invoker_group}{delimiter}{comp_name}_cell_probe"
542 );
543 let probe_cell =
544 builder.add_primitive(probe_cell_name, "std_wire", &[1]);
545 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
546 probe_cell
547 .borrow_mut()
548 .add_attribute(BoolAttr::Protected, 1);
549 let probe_asgn: ir::Assignment<T> = builder.build_assignment(
551 probe_cell.borrow().get("in"),
552 one.borrow().get("out"),
553 guard.clone(),
554 );
555 group_name_assign_and_cell.push((
556 *invoker_group,
557 probe_asgn,
558 probe_cell,
559 ));
560 }
561 }
562 }
563
564 if let Some(primitive_invoke_map) = primitive_invoke_map_opt {
565 for (group, primitive_invs) in primitive_invoke_map.iter() {
567 for (primitive_cell_name, guard) in primitive_invs.iter() {
568 let probe_cell_name = format!(
569 "{primitive_cell_name}{delimiter}{group}{delimiter}{comp_name}_primitive_probe"
570 );
571 let probe_cell =
572 builder.add_primitive(probe_cell_name, "std_wire", &[1]);
573 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
574 probe_cell
575 .borrow_mut()
576 .add_attribute(BoolAttr::Protected, 1);
577 let probe_asgn: ir::Assignment<T> = builder.build_assignment(
578 probe_cell.borrow().get("in"),
579 one.borrow().get("out"),
580 guard.clone(),
581 );
582 group_name_assign_and_cell
583 .push((*group, probe_asgn, probe_cell));
584 }
585 }
586 }
587
588 group_name_assign_and_cell
589}
590
591fn continuous_assignments(
595 comp: &mut ir::Component,
596 sigs: &ir::LibrarySignatures,
597 collect_stats: bool,
598) -> Option<StatsEntry> {
599 let mut cell_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
601 let mut primitive_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
603
604 let mut comb_primitives_covered = HashSet::new();
606 let mut comb_cells_covered = HashSet::new();
607 for assignment_ref in comp.continuous_assignments.iter() {
608 let dst_borrow = assignment_ref.dst.borrow();
609 let guard = *(assignment_ref.guard).clone();
610 if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
611 match cell_ref.upgrade().borrow().prototype.clone() {
612 calyx_ir::CellType::Primitive { .. } => {
613 let cell_name = cell_ref.upgrade().borrow().name();
614 if comb_primitives_covered.insert(cell_name) {
616 primitive_invoke_vec.push((cell_name, guard));
617 }
618 }
619 calyx_ir::CellType::Component { .. } => {
620 let cell_name = cell_ref.upgrade().borrow().name();
621 if comb_cells_covered.insert(cell_name) {
622 cell_invoke_vec.push((cell_name, guard));
623 }
624 }
625 _ => (),
626 }
627 }
628 }
629
630 let delimiter = "___";
632 let comp_name = comp.name;
633 let mut builder = ir::Builder::new(comp, sigs);
634 let one = builder.add_constant(1, 1);
635 let mut assign_and_cell = Vec::new();
636 for (primitive_cell_name, guard) in primitive_invoke_vec.iter() {
637 let probe_cell_name = format!(
638 "{primitive_cell_name}{delimiter}{comp_name}_contprimitive_probe"
639 );
640 let probe_cell =
641 builder.add_primitive(probe_cell_name, "std_wire", &[1]);
642 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
643 probe_cell
644 .borrow_mut()
645 .add_attribute(BoolAttr::Protected, 1);
646 let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
647 probe_cell.borrow().get("in"),
648 one.borrow().get("out"),
649 guard.clone(),
650 );
651 assign_and_cell.push((probe_asgn, probe_cell));
652 }
653 for (cell_name, guard) in cell_invoke_vec.iter() {
655 let probe_cell_name =
656 format!("{cell_name}{delimiter}{comp_name}_contcell_probe");
657 let probe_cell =
658 builder.add_primitive(probe_cell_name, "std_wire", &[1]);
659 probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
660 probe_cell
661 .borrow_mut()
662 .add_attribute(BoolAttr::Protected, 1);
663 let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
664 probe_cell.borrow().get("in"),
665 one.borrow().get("out"),
666 guard.clone(),
667 );
668 assign_and_cell.push((probe_asgn, probe_cell));
669 }
670
671 for (asgn, cell) in assign_and_cell.iter() {
673 comp.continuous_assignments.push(asgn.clone());
674 comp.cells.add(cell.to_owned());
675 }
676
677 if collect_stats {
678 Some(StatsEntry {
679 group_probe: 0,
680 structural_enable_probe: 0,
681 cell_probe: cell_invoke_vec.len() as u32,
682 primitive_probe: primitive_invoke_vec.len() as u32,
683 })
684 } else {
685 None
686 }
687}
688
689fn populate_stats(
690 component_name: Id,
691 stats_map: &mut BTreeMap<String, StatsEntry>,
692 stats_list: Vec<Option<StatsEntry>>,
693) {
694 let this_comp_stats_list = stats_list.iter().fold(
695 StatsEntry {
696 group_probe: 0,
697 structural_enable_probe: 0,
698 cell_probe: 0,
699 primitive_probe: 0,
700 },
701 |s, g_s_opt| match g_s_opt {
702 Some(g_s) => s + g_s.clone(),
703 None => s,
704 },
705 );
706 stats_map.insert(component_name.to_string(), this_comp_stats_list);
707}
708
709impl Visitor for ProfilerInstrumentation {
710 fn start(
711 &mut self,
712 comp: &mut ir::Component,
713 sigs: &ir::LibrarySignatures,
714 _comps: &[ir::Component],
715 ) -> VisResult {
716 let count = self.emit_probe_stats.is_some();
717 let group_stats_opt = group(comp, sigs, count);
718 let comb_group_stats_opt = combinational_group(comp, sigs, count);
719 let static_group_stats_opt = static_group(comp, sigs, count);
720 let continuous_assignments_opt =
721 continuous_assignments(comp, sigs, count);
722
723 if count {
724 populate_stats(
725 comp.name,
726 &mut self.probe_stats,
727 vec![
728 group_stats_opt,
729 comb_group_stats_opt,
730 static_group_stats_opt,
731 continuous_assignments_opt,
732 ],
733 )
734 }
735 Ok(Action::Continue)
736 }
737
738 fn finish_context(&mut self, _ctx: &mut calyx_ir::Context) -> VisResult {
739 if let Some(json_out_file) = &mut self.emit_probe_stats {
740 let _ = serde_json::to_writer_pretty(
741 json_out_file.get_write(),
742 &self.probe_stats,
743 );
744 }
745 Ok(Action::Stop)
746 }
747}