calyx_opt/passes/
profiler_instrumentation.rs

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
36/// Adds probe wires to each group (includes static groups and comb groups) to detect when a group is active.
37/// Used by the profiler.
38pub struct ProfilerInstrumentation {
39    probe_stats: BTreeMap<String, StatsEntry>,
40    emit_probe_stats: Option<OutputFile>,
41    invoke_comb_groups_to_stats: HashMap<Id, Option<StatsEntry>>,
42}
43
44/// Mapping group names to constructs (groups/primitives/cells) that the group enabled,
45/// along with the guard that was involved in the assignment.
46type CallsFromGroupMap<T> = HashMap<Id, Vec<(Id, ir::Guard<T>)>>;
47
48impl Named for ProfilerInstrumentation {
49    fn name() -> &'static str {
50        "profiler-instrumentation"
51    }
52
53    fn description() -> &'static str {
54        "Add instrumentation for profiling"
55    }
56
57    fn opts() -> Vec<crate::traversal::PassOpt> {
58        vec![PassOpt::new(
59            "emit-probe-stats",
60            "emit json file of shared cells",
61            ParseVal::OutStream(OutputFile::Null),
62            PassOpt::parse_outstream,
63        )]
64    }
65}
66
67impl ConstructVisitor for ProfilerInstrumentation {
68    fn from(ctx: &ir::Context) -> CalyxResult<Self>
69    where
70        Self: Sized + Named,
71    {
72        let opts = Self::get_opts(ctx);
73
74        Ok(ProfilerInstrumentation {
75            probe_stats: BTreeMap::new(),
76            emit_probe_stats: opts["emit-probe-stats"].not_null_outstream(),
77            invoke_comb_groups_to_stats: HashMap::new(),
78        })
79    }
80
81    fn clear_data(&mut self) {}
82}
83
84fn count_helper<T>(map_opt: Option<CallsFromGroupMap<T>>) -> u32 {
85    match map_opt {
86        Some(map) => map
87            .values()
88            .fold(0, |acc, vec_ref| acc + vec_ref.len() as u32),
89        None => 0,
90    }
91}
92
93fn count<T>(
94    num_groups: u32,
95    structural_enable_map_opt: Option<CallsFromGroupMap<T>>,
96    cell_invoke_map_opt: Option<CallsFromGroupMap<T>>,
97    primitive_map_opt: Option<CallsFromGroupMap<T>>,
98) -> StatsEntry {
99    let num_structural_enables = count_helper(structural_enable_map_opt);
100    let num_cell_invokes = count_helper(cell_invoke_map_opt);
101    let num_primitive_invokes = count_helper(primitive_map_opt);
102
103    StatsEntry {
104        group_probe: num_groups,
105        structural_enable_probe: num_structural_enables,
106        cell_probe: num_cell_invokes,
107        primitive_probe: num_primitive_invokes,
108    }
109}
110
111/// Creates probe cells and assignments pertaining to standard groups.
112fn group(
113    comp: &mut ir::Component,
114    sigs: &ir::LibrarySignatures,
115    collect_stats: bool,
116) -> Option<StatsEntry> {
117    // groups to groups that they enabled
118    let mut structural_enable_map: CallsFromGroupMap<Nothing> = HashMap::new();
119    // groups to cells (from non-primitive components) that they invoked
120    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
121    // groups to primitives that they invoked
122    let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
123    let group_names = comp
124        .groups
125        .iter()
126        .map(|group| group.borrow().name())
127        .collect::<Vec<_>>();
128
129    // Dynamic groups: iterate and check for structural enables, cell invokes, and primitive enables
130    for group_ref in comp.groups.iter() {
131        let group = &group_ref.borrow();
132        // set to prevent adding multiple probes for a combinational primitive enabled by the group
133        let mut comb_primitives_covered = HashSet::new();
134        let mut primitive_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
135        for assignment_ref in group.assignments.iter() {
136            let dst_borrow = assignment_ref.dst.borrow();
137            if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
138                && dst_borrow.name == "go"
139            {
140                // found an invocation of go
141                let invoked_group_name =
142                    parent_group_ref.upgrade().borrow().name();
143                let guard = *(assignment_ref.guard.clone());
144                match structural_enable_map.get_mut(&invoked_group_name) {
145                    Some(vec_ref) => vec_ref.push((group.name(), guard)),
146                    None => {
147                        structural_enable_map.insert(
148                            invoked_group_name,
149                            vec![(group.name(), guard)],
150                        );
151                    }
152                }
153            }
154            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
155                match cell_ref.upgrade().borrow().prototype.clone() {
156                    calyx_ir::CellType::Primitive {
157                        name: _,
158                        param_binding: _,
159                        is_comb,
160                        latency: _,
161                    } => {
162                        let cell_name = cell_ref.upgrade().borrow().name();
163                        if is_comb {
164                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
165                            if comb_primitives_covered.insert(cell_name) {
166                                primitive_vec.push((cell_name, Guard::True));
167                            }
168                        } else if dst_borrow.has_attribute(NumAttr::Go) {
169                            // non-combinational primitives
170                            let guard = Guard::and(
171                                *(assignment_ref.guard.clone()),
172                                Guard::port(ir::rrc(
173                                    assignment_ref.src.borrow().clone(),
174                                )),
175                            );
176                            primitive_vec.push((cell_name, guard));
177                        }
178                    }
179                    calyx_ir::CellType::Component { name: _ } => {
180                        if dst_borrow.has_attribute(NumAttr::Go) {
181                            let cell_name = cell_ref.upgrade().borrow().name();
182                            let guard = *(assignment_ref.guard.clone());
183                            match cell_invoke_map.get_mut(&group.name()) {
184                                Some(vec_ref) => {
185                                    vec_ref.push((cell_name, guard));
186                                }
187                                None => {
188                                    cell_invoke_map.insert(
189                                        group.name(),
190                                        vec![(cell_name, guard)],
191                                    );
192                                }
193                            }
194                        }
195                    }
196                    _ => (),
197                }
198            }
199        }
200        primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
201    }
202
203    // create probe cells and assignments
204    let group_name_assign_and_cell = create_probes_and_assignments(
205        comp,
206        sigs,
207        &group_names,
208        Some(&structural_enable_map),
209        Some(&cell_invoke_map),
210        Some(&primitive_invoke_map),
211    );
212
213    // Add created assignments to each group and their corresponding probe cells
214    for group in comp.groups.iter() {
215        for (group_name, asgn, cell) in group_name_assign_and_cell.iter() {
216            if group.borrow().name() == group_name {
217                group.borrow_mut().assignments.push(asgn.clone());
218                comp.cells.add(cell.to_owned());
219            }
220        }
221    }
222
223    if collect_stats {
224        Some(count(
225            group_names.len() as u32,
226            Some(structural_enable_map),
227            Some(cell_invoke_map),
228            Some(primitive_invoke_map),
229        ))
230    } else {
231        None
232    }
233}
234
235/// Creates probe cells and assignments pertaining to combinational groups.
236/// `covered` is the set of comb groups that were attached to invokes
237/// and already got instrumentation probes covered, so should be ignored
238/// by this function.
239fn combinational_group(
240    comp: &mut ir::Component,
241    sigs: &ir::LibrarySignatures,
242    collect_stats: bool,
243    covered: &HashSet<Id>,
244) -> Option<StatsEntry> {
245    // NOTE: combinational groups cannot structurally enable other groups
246
247    // groups to cells (from non-primitive components) that they invoked
248    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
249    // groups to primitives that they invoked
250    let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
251
252    let group_names = comp
253        .comb_groups
254        .iter()
255        // filter out any comb groups that are in covered
256        .filter(|group| !covered.contains(&group.borrow().name()))
257        .map(|group| group.borrow().name())
258        .collect::<Vec<_>>();
259
260    for group_ref in comp
261        .comb_groups
262        .iter()
263        .filter(|group| !covered.contains(&group.borrow().name()))
264    {
265        let group = &group_ref.borrow();
266        let mut comb_primitives_covered = HashSet::new();
267        let mut comb_cells_covered = HashSet::new();
268
269        for assignment_ref in group.assignments.iter() {
270            let dst_borrow = assignment_ref.dst.borrow();
271            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
272                match cell_ref.upgrade().borrow().prototype.clone() {
273                    calyx_ir::CellType::Primitive {
274                        name: _,
275                        param_binding: _,
276                        is_comb,
277                        latency: _,
278                    } => {
279                        let cell_name = cell_ref.upgrade().borrow().name();
280                        if is_comb {
281                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
282                            if comb_primitives_covered.insert(cell_name) {
283                                match primitive_invoke_map
284                                    .get_mut(&group.name())
285                                {
286                                    Some(vec_ref) => {
287                                        vec_ref.push((cell_name, Guard::True));
288                                    }
289                                    None => {
290                                        primitive_invoke_map.insert(
291                                            group.name(),
292                                            vec![(cell_name, Guard::True)],
293                                        );
294                                    }
295                                }
296                            }
297                        } else if dst_borrow.has_attribute(NumAttr::Go) {
298                            panic!(
299                                "Non-combinational primitive {} invoked inside of combinational group {}!",
300                                dst_borrow.canonical(),
301                                group.name()
302                            )
303                        }
304                    }
305                    calyx_ir::CellType::Component { name: _ } => {
306                        let cell_name = cell_ref.upgrade().borrow().name();
307                        if dst_borrow.name == "go" {
308                            panic!(
309                                "Non-combinational cell {} invoked inside of combinational group {}!",
310                                cell_name,
311                                group.name()
312                            );
313                        } else if comb_cells_covered.insert(cell_name) {
314                            let guard = *(assignment_ref.guard.clone());
315                            match cell_invoke_map.get_mut(&group.name()) {
316                                Some(vec_ref) => {
317                                    vec_ref.push((cell_name, guard));
318                                }
319                                None => {
320                                    cell_invoke_map.insert(
321                                        group.name(),
322                                        vec![(cell_name, guard)],
323                                    );
324                                }
325                            }
326                        }
327                    }
328                    _ => (),
329                }
330            }
331        }
332    }
333
334    let group_name_asgn_and_cell = create_probes_and_assignments(
335        comp,
336        sigs,
337        &group_names,
338        None, // assuming no structural enables within comb groups
339        Some(&cell_invoke_map),
340        Some(&primitive_invoke_map),
341    );
342
343    // Comb: Add created assignments to each group
344    for comb_group in comp.comb_groups.iter() {
345        for (comb_group_name, asgn, cell) in group_name_asgn_and_cell.iter() {
346            if comb_group.borrow().name() == comb_group_name {
347                comb_group.borrow_mut().assignments.push(asgn.clone());
348                comp.cells.add(cell.to_owned());
349            }
350        }
351    }
352
353    if collect_stats {
354        Some(count(
355            group_names.len() as u32,
356            None,
357            Some(cell_invoke_map),
358            Some(primitive_invoke_map),
359        ))
360    } else {
361        None
362    }
363}
364
365/// Creates probe cells and assignments pertaining to static groups.
366fn static_group(
367    comp: &mut ir::Component,
368    sigs: &ir::LibrarySignatures,
369    collect_stats: bool,
370) -> Option<StatsEntry> {
371    let group_names = comp
372        .static_groups
373        .iter()
374        .map(|group| group.borrow().name())
375        .collect::<Vec<_>>();
376
377    // groups to groups that they enabled
378    let mut structural_enable_map: CallsFromGroupMap<ir::StaticTiming> =
379        HashMap::new();
380    // groups to cells (from non-primitive components) that they invoked
381    let mut cell_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
382        HashMap::new();
383    // groups to primitives that they invoked
384    let mut primitive_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
385        HashMap::new();
386
387    for group_ref in comp.static_groups.iter() {
388        let group = &group_ref.borrow();
389        // set to prevent adding multiple probes for a combinational primitive enabled by the group
390        let mut comb_primitives_covered = HashSet::new();
391        let mut primitive_vec: Vec<(Id, ir::Guard<ir::StaticTiming>)> =
392            Vec::new();
393        for assignment_ref in group.assignments.iter() {
394            let dst_borrow = assignment_ref.dst.borrow();
395            if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
396                && dst_borrow.name == "go"
397            {
398                // found an invocation of go
399                let invoked_group_name =
400                    parent_group_ref.upgrade().borrow().name();
401                let guard = *(assignment_ref.guard).clone();
402                structural_enable_map
403                    .entry(invoked_group_name)
404                    .or_default()
405                    .push((group.name(), guard));
406            }
407            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
408                match cell_ref.upgrade().borrow().prototype.clone() {
409                    calyx_ir::CellType::Primitive { is_comb, .. } => {
410                        let cell_name = cell_ref.upgrade().borrow().name();
411                        if is_comb {
412                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
413                            if comb_primitives_covered.insert(cell_name) {
414                                primitive_vec.push((cell_name, Guard::True));
415                            }
416                        } else if dst_borrow.has_attribute(NumAttr::Go) {
417                            // non-combinational primitives
418                            let guard = Guard::and(
419                                *(assignment_ref.guard).clone(),
420                                Guard::port(ir::rrc(
421                                    assignment_ref.src.borrow().clone(),
422                                )),
423                            );
424                            primitive_vec.push((cell_name, guard));
425                        }
426                    }
427                    calyx_ir::CellType::Component { name: _ } => {
428                        if dst_borrow.has_attribute(NumAttr::Go) {
429                            let cell_name = cell_ref.upgrade().borrow().name();
430                            let guard = *(assignment_ref.guard.clone());
431                            cell_invoke_map
432                                .entry(group.name())
433                                .or_default()
434                                .push((cell_name, guard));
435                        }
436                    }
437                    _ => (),
438                }
439            }
440        }
441        primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
442    }
443
444    let group_name_assign_and_cell = create_probes_and_assignments(
445        comp,
446        sigs,
447        &group_names,
448        Some(&structural_enable_map),
449        Some(&cell_invoke_map),
450        Some(&primitive_invoke_map),
451    );
452
453    // Add created assignments to each group
454    for static_group in comp.static_groups.iter() {
455        for (static_group_name, asgn, cell) in group_name_assign_and_cell.iter()
456        {
457            if static_group.borrow().name() == static_group_name {
458                static_group.borrow_mut().assignments.push(asgn.clone());
459                comp.cells.add(cell.to_owned());
460            }
461        }
462    }
463
464    if collect_stats {
465        Some(count(
466            group_names.len() as u32,
467            Some(structural_enable_map),
468            Some(cell_invoke_map),
469            Some(primitive_invoke_map),
470        ))
471    } else {
472        None
473    }
474}
475
476/// Creates all probe cells and assignments for a certain kind of .
477/// Returns a Vec where each element is (GROUP, ASGN, CELL) where
478/// GROUP is the group to write the assignment in,
479/// ASGN is the probe assignment to insert into the group,
480/// CELL is the generated probe wire to add to cells
481fn create_probes_and_assignments<T: Clone>(
482    comp: &mut ir::Component,
483    sigs: &ir::LibrarySignatures,
484    group_names: &[Id],
485    structural_enable_map_opt: Option<&CallsFromGroupMap<T>>,
486    cell_invoke_map_opt: Option<&CallsFromGroupMap<T>>,
487    primitive_invoke_map_opt: Option<&CallsFromGroupMap<T>>,
488) -> Vec<(
489    Id,
490    calyx_ir::Assignment<T>,
491    std::rc::Rc<std::cell::RefCell<calyx_ir::Cell>>,
492)> {
493    let delimiter = "___";
494    let comp_name = comp.name;
495    // build probe and assignments for every group (dynamic and static) + all structural invokes
496    let mut builder = ir::Builder::new(comp, sigs);
497    let one = builder.add_constant(1, 1);
498
499    // (group name, assignment to insert, probe cell to insert) for each probe we want to insert
500    // we assume that each probe cell will only have one assignment.
501    let mut group_name_assign_and_cell = Vec::new();
502
503    // probe and assignments for group enable (this group is currently active)
504    for group_name in group_names.iter() {
505        // store group and component name (differentiate between groups of the same name under different components)
506        let name = format!("{group_name}{delimiter}{comp_name}_group_probe");
507        let probe_cell = builder.add_primitive(name, "std_wire", &[1]);
508        let probe_asgn: ir::Assignment<T> = builder.build_assignment(
509            probe_cell.borrow().get("in"),
510            one.borrow().get("out"),
511            Guard::True,
512        );
513        // the probes should be @control because they should have value 0 whenever the corresponding group is not active.
514        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
515        probe_cell
516            .borrow_mut()
517            .add_attribute(BoolAttr::Protected, 1);
518        group_name_assign_and_cell.push((*group_name, probe_asgn, probe_cell));
519    }
520
521    if let Some(sem) = structural_enable_map_opt {
522        // probe and assignments for structural enables (this group is structurally enabling a child group)
523        for (invoked_group_name, parent_groups) in sem.iter() {
524            for (parent_group, guard) in parent_groups.iter() {
525                let probe_cell_name = format!(
526                    "{invoked_group_name}{delimiter}{parent_group}{delimiter}{comp_name}_se_probe"
527                );
528                let probe_cell =
529                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
530                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
531                probe_cell
532                    .borrow_mut()
533                    .add_attribute(BoolAttr::Protected, 1);
534                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
535                    probe_cell.borrow().get("in"),
536                    one.borrow().get("out"),
537                    guard.clone(),
538                );
539                group_name_assign_and_cell.push((
540                    *parent_group,
541                    probe_asgn,
542                    probe_cell,
543                ));
544            }
545        }
546    }
547
548    if let Some(cell_invoke_map) = cell_invoke_map_opt {
549        // probe cell and assignments for structural cell invocations (the group is structurally invoking a cell.)
550        for (invoker_group, invoked_cells) in cell_invoke_map.iter() {
551            for (invoked_cell, guard) in invoked_cells {
552                let probe_cell_name = format!(
553                    "{invoked_cell}{delimiter}{invoker_group}{delimiter}{comp_name}_cell_probe"
554                );
555                let probe_cell =
556                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
557                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
558                probe_cell
559                    .borrow_mut()
560                    .add_attribute(BoolAttr::Protected, 1);
561                // NOTE: this probe is active for the duration of the whole group. Hence, it may be active even when the cell itself is inactive.
562                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
563                    probe_cell.borrow().get("in"),
564                    one.borrow().get("out"),
565                    guard.clone(),
566                );
567                group_name_assign_and_cell.push((
568                    *invoker_group,
569                    probe_asgn,
570                    probe_cell,
571                ));
572            }
573        }
574    }
575
576    if let Some(primitive_invoke_map) = primitive_invoke_map_opt {
577        // probe and assignments for primitive invocations (this group is activating a primitive)
578        for (group, primitive_invs) in primitive_invoke_map.iter() {
579            for (primitive_cell_name, guard) in primitive_invs.iter() {
580                let probe_cell_name = format!(
581                    "{primitive_cell_name}{delimiter}{group}{delimiter}{comp_name}_primitive_probe"
582                );
583                let probe_cell =
584                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
585                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
586                probe_cell
587                    .borrow_mut()
588                    .add_attribute(BoolAttr::Protected, 1);
589                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
590                    probe_cell.borrow().get("in"),
591                    one.borrow().get("out"),
592                    guard.clone(),
593                );
594                group_name_assign_and_cell
595                    .push((*group, probe_asgn, probe_cell));
596            }
597        }
598    }
599
600    group_name_assign_and_cell
601}
602
603/// Creates probes for continuous assignments outside of groups. For every cell
604/// or primitive involved in a continuous assignment, this function will generate
605/// "contprimitive" and "contcell" wires as probes.
606fn continuous_assignments(
607    comp: &mut ir::Component,
608    sigs: &ir::LibrarySignatures,
609    collect_stats: bool,
610) -> Option<StatsEntry> {
611    // vector of cells (non-primitives) invoked
612    let mut cell_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
613    // vector of primitives invoked
614    let mut primitive_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
615
616    // set to prevent adding multiple probes for a combinational primitive
617    let mut comb_primitives_covered = HashSet::new();
618    let mut comb_cells_covered = HashSet::new();
619    for assignment_ref in comp.continuous_assignments.iter() {
620        let dst_borrow = assignment_ref.dst.borrow();
621        let guard = *(assignment_ref.guard).clone();
622        if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
623            match cell_ref.upgrade().borrow().prototype.clone() {
624                calyx_ir::CellType::Primitive { .. } => {
625                    let cell_name = cell_ref.upgrade().borrow().name();
626                    // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
627                    if comb_primitives_covered.insert(cell_name) {
628                        primitive_invoke_vec.push((cell_name, guard));
629                    }
630                }
631                calyx_ir::CellType::Component { .. } => {
632                    let cell_name = cell_ref.upgrade().borrow().name();
633                    if comb_cells_covered.insert(cell_name) {
634                        cell_invoke_vec.push((cell_name, guard));
635                    }
636                }
637                _ => (),
638            }
639        }
640    }
641
642    // add probes for primitives in continuous assignment
643    let delimiter = "___";
644    let comp_name = comp.name;
645    let mut builder = ir::Builder::new(comp, sigs);
646    let one = builder.add_constant(1, 1);
647    let mut assign_and_cell = Vec::new();
648    for (primitive_cell_name, guard) in primitive_invoke_vec.iter() {
649        let probe_cell_name = format!(
650            "{primitive_cell_name}{delimiter}{comp_name}_contprimitive_probe"
651        );
652        let probe_cell =
653            builder.add_primitive(probe_cell_name, "std_wire", &[1]);
654        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
655        probe_cell
656            .borrow_mut()
657            .add_attribute(BoolAttr::Protected, 1);
658        let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
659            probe_cell.borrow().get("in"),
660            one.borrow().get("out"),
661            guard.clone(),
662        );
663        assign_and_cell.push((probe_asgn, probe_cell));
664    }
665    // add probes for cells (non-primitives) in continuous assignment
666    for (cell_name, guard) in cell_invoke_vec.iter() {
667        let probe_cell_name =
668            format!("{cell_name}{delimiter}{comp_name}_contcell_probe");
669        let probe_cell =
670            builder.add_primitive(probe_cell_name, "std_wire", &[1]);
671        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
672        probe_cell
673            .borrow_mut()
674            .add_attribute(BoolAttr::Protected, 1);
675        let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
676            probe_cell.borrow().get("in"),
677            one.borrow().get("out"),
678            guard.clone(),
679        );
680        assign_and_cell.push((probe_asgn, probe_cell));
681    }
682
683    // Add created assignments to continuous assignments
684    for (asgn, cell) in assign_and_cell.iter() {
685        comp.continuous_assignments.push(asgn.clone());
686        comp.cells.add(cell.to_owned());
687    }
688
689    if collect_stats {
690        Some(StatsEntry {
691            group_probe: 0,
692            structural_enable_probe: 0,
693            cell_probe: cell_invoke_vec.len() as u32,
694            primitive_probe: primitive_invoke_vec.len() as u32,
695        })
696    } else {
697        None
698    }
699}
700
701fn populate_stats(
702    component_name: Id,
703    stats_map: &mut BTreeMap<String, StatsEntry>,
704    stats_list: Vec<Option<StatsEntry>>,
705) {
706    let this_comp_stats_list = stats_list.iter().fold(
707        StatsEntry {
708            group_probe: 0,
709            structural_enable_probe: 0,
710            cell_probe: 0,
711            primitive_probe: 0,
712        },
713        |s, g_s_opt| match g_s_opt {
714            Some(g_s) => s + g_s.clone(),
715            None => s,
716        },
717    );
718    stats_map.insert(component_name.to_string(), this_comp_stats_list);
719}
720
721impl Visitor for ProfilerInstrumentation {
722    fn invoke(
723        &mut self,
724        s: &mut calyx_ir::Invoke,
725        comp: &mut calyx_ir::Component,
726        sigs: &calyx_ir::LibrarySignatures,
727        _comps: &[calyx_ir::Component],
728    ) -> VisResult {
729        let cell_name = s.comp.borrow().name();
730        // for invokes, we instrument the comb group
731        let mut comb_group = match &s.comb_group {
732            Some(s) => s.borrow_mut(),
733            None => {
734                panic!(
735                    "Invokes should come with a comb group. Please run `uniquefy_enables` before running this pass!"
736                )
737            }
738        };
739        let comb_group_name = comb_group.name();
740
741        // To avoid code cloning, we will reuse create_probes_and_assignments by passing in
742        // one-key maps (where the key is the name of the comb group) for cell_invoke_map_opt and primitive_invoke_map_opt
743        let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
744        cell_invoke_map.insert(comb_group_name, vec![(cell_name, Guard::True)]);
745
746        // scanning to see if there are primitive uses (this can happen if the comb group was user defined)
747        let mut primitive_name_set = HashSet::new();
748        let mut primitives_invoked_vec = vec![];
749        for assignment_ref in comb_group.assignments.iter() {
750            let dst_borrow = assignment_ref.dst.borrow();
751            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent
752                && let calyx_ir::CellType::Primitive { .. } =
753                    cell_ref.upgrade().borrow().prototype.clone()
754            {
755                let primitive_cell_name = cell_ref.upgrade().borrow().name();
756                if primitive_name_set.insert(primitive_cell_name) {
757                    primitives_invoked_vec.push((
758                        primitive_cell_name,
759                        *(assignment_ref.guard.clone()),
760                    ));
761                }
762            }
763        }
764        let mut primitive_invoke_map: CallsFromGroupMap<Nothing> =
765            HashMap::new();
766        primitive_invoke_map.insert(comb_group_name, primitives_invoked_vec);
767
768        let group_name_asgn_and_cell = create_probes_and_assignments(
769            comp,
770            sigs,
771            &[comb_group_name],
772            None,
773            Some(&cell_invoke_map),
774            Some(&primitive_invoke_map),
775        );
776
777        // insert created assignments back into comb group
778        for (_comb_group_name, asgn, cell) in group_name_asgn_and_cell {
779            comb_group.assignments.push(asgn.clone());
780            comp.cells.add(cell.to_owned());
781        }
782
783        // collect statistics
784        let stats = if self.emit_probe_stats.is_some() {
785            Some(count(
786                1,
787                None,
788                Some(cell_invoke_map),
789                Some(primitive_invoke_map),
790            ))
791        } else {
792            None
793        };
794        self.invoke_comb_groups_to_stats
795            .insert(comb_group_name, stats);
796
797        Ok(Action::Continue)
798    }
799
800    fn static_invoke(
801        &mut self,
802        s: &mut calyx_ir::StaticInvoke,
803        comp: &mut calyx_ir::Component,
804        sigs: &calyx_ir::LibrarySignatures,
805        _comps: &[calyx_ir::Component],
806    ) -> VisResult {
807        let cell_name = s.comp.borrow().name();
808        // for invokes, we instrument the comb group
809        let mut comb_group = match &s.comb_group {
810            Some(s) => s.borrow_mut(),
811            None => {
812                panic!(
813                    "Invokes should come with a comb group. Please run `uniquefy_enables` before running this pass!"
814                )
815            }
816        };
817        let comb_group_name = comb_group.name();
818
819        // To avoid code cloning, we will reuse create_probes_and_assignments by passing in
820        // one-key maps (where the key is the name of the comb group) for cell_invoke_map_opt and primitive_invoke_map_opt
821        let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
822        cell_invoke_map.insert(comb_group_name, vec![(cell_name, Guard::True)]);
823
824        // scanning to see if there are primitive uses (this can happen if the comb group was user defined)
825        let mut primitive_name_set = HashSet::new();
826        let mut primitives_invoked_vec = vec![];
827        for assignment_ref in comb_group.assignments.iter() {
828            let dst_borrow = assignment_ref.dst.borrow();
829            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent
830                && let calyx_ir::CellType::Primitive { name, .. } =
831                    cell_ref.upgrade().borrow().prototype.clone()
832                && primitive_name_set.insert(name)
833            {
834                primitives_invoked_vec
835                    .push((name, *(assignment_ref.guard.clone())));
836            }
837        }
838        let mut primitive_invoke_map: CallsFromGroupMap<Nothing> =
839            HashMap::new();
840        primitive_invoke_map.insert(comb_group_name, primitives_invoked_vec);
841
842        let group_name_asgn_and_cell = create_probes_and_assignments(
843            comp,
844            sigs,
845            &[comb_group_name],
846            None,
847            Some(&cell_invoke_map),
848            Some(&primitive_invoke_map),
849        );
850
851        // insert created assignments back into comb group
852        for (_comb_group_name, asgn, cell) in group_name_asgn_and_cell {
853            comb_group.assignments.push(asgn.clone());
854            comp.cells.add(cell.to_owned());
855        }
856
857        // collect statistics
858        let stats = if self.emit_probe_stats.is_some() {
859            Some(count(
860                1,
861                None,
862                Some(cell_invoke_map),
863                Some(primitive_invoke_map),
864            ))
865        } else {
866            None
867        };
868        self.invoke_comb_groups_to_stats
869            .insert(comb_group_name, stats);
870
871        Ok(Action::Continue)
872    }
873
874    fn finish(
875        &mut self,
876        comp: &mut calyx_ir::Component,
877        sigs: &calyx_ir::LibrarySignatures,
878        _comps: &[calyx_ir::Component],
879    ) -> VisResult {
880        let count = self.emit_probe_stats.is_some();
881        let group_stats_opt = group(comp, sigs, count);
882        let comb_group_stats_opt = combinational_group(
883            comp,
884            sigs,
885            count,
886            &self.invoke_comb_groups_to_stats.keys().cloned().collect(),
887        );
888        let static_group_stats_opt = static_group(comp, sigs, count);
889        let continuous_assignments_opt =
890            continuous_assignments(comp, sigs, count);
891
892        if count {
893            populate_stats(
894                comp.name,
895                &mut self.probe_stats,
896                vec![
897                    group_stats_opt,
898                    comb_group_stats_opt,
899                    static_group_stats_opt,
900                    continuous_assignments_opt,
901                ],
902            )
903        }
904        Ok(Action::Continue)
905    }
906
907    fn finish_context(&mut self, _ctx: &mut calyx_ir::Context) -> VisResult {
908        if let Some(json_out_file) = &mut self.emit_probe_stats {
909            let _ = serde_json::to_writer_pretty(
910                json_out_file.get_write(),
911                &self.probe_stats,
912            );
913        }
914        Ok(Action::Stop)
915    }
916}