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}
42
43/// Mapping group names to constructs (groups/primitives/cells) that the group enabled,
44/// along with the guard that was involved in the assignment.
45type 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
109/// Creates probe cells and assignments pertaining to standard groups.
110fn group(
111    comp: &mut ir::Component,
112    sigs: &ir::LibrarySignatures,
113    collect_stats: bool,
114) -> Option<StatsEntry> {
115    // groups to groups that they enabled
116    let mut structural_enable_map: CallsFromGroupMap<Nothing> = HashMap::new();
117    // groups to cells (from non-primitive components) that they invoked
118    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
119    // groups to primitives that they invoked
120    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    // Dynamic groups: iterate and check for structural enables, cell invokes, and primitive enables
128    for group_ref in comp.groups.iter() {
129        let group = &group_ref.borrow();
130        // set to prevent adding multiple probes for a combinational primitive enabled by the group
131        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                // found an invocation of go
139                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                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
163                            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                            // non-combinational primitives
168                            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    // create probe cells and assignments
202    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    // Add created assignments to each group and their corresponding probe cells
212    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
233/// Creates probe cells and assignments pertaining to combinational groups.
234fn combinational_group(
235    comp: &mut ir::Component,
236    sigs: &ir::LibrarySignatures,
237    collect_stats: bool,
238) -> Option<StatsEntry> {
239    // NOTE: combinational groups cannot structurally enable other groups
240
241    // groups to cells (from non-primitive components) that they invoked
242    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
243    // groups to primitives that they invoked
244    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                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
270                            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, // assuming no structural enables within comb groups
327        Some(&cell_invoke_map),
328        Some(&primitive_invoke_map),
329    );
330
331    // Comb: Add created assignments to each group
332    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
353/// Creates probe cells and assignments pertaining to static groups.
354fn 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    // groups to groups that they enabled
366    let mut structural_enable_map: CallsFromGroupMap<ir::StaticTiming> =
367        HashMap::new();
368    // groups to cells (from non-primitive components) that they invoked
369    let mut cell_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
370        HashMap::new();
371    // groups to primitives that they invoked
372    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        // set to prevent adding multiple probes for a combinational primitive enabled by the group
378        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                // found an invocation of go
387                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                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
401                            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                            // non-combinational primitives
406                            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    // Add created assignments to each group
442    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
464/// Creates all probe cells and assignments for a certain kind of .
465/// Returns a Vec where each element is (GROUP, ASGN, CELL) where
466/// GROUP is the group to write the assignment in,
467/// ASGN is the probe assignment to insert into the group,
468/// CELL is the generated probe wire to add to cells
469fn 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    // build probe and assignments for every group (dynamic and static) + all structural invokes
484    let mut builder = ir::Builder::new(comp, sigs);
485    let one = builder.add_constant(1, 1);
486
487    // (group name, assignment to insert, probe cell to insert) for each probe we want to insert
488    // we assume that each probe cell will only have one assignment.
489    let mut group_name_assign_and_cell = Vec::new();
490
491    // probe and assignments for group enable (this group is currently active)
492    for group_name in group_names.iter() {
493        // store group and component name (differentiate between groups of the same name under different components)
494        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        // the probes should be @control because they should have value 0 whenever the corresponding group is not active.
502        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        // probe and assignments for structural enables (this group is structurally enabling a child group)
511        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        // probe cell and assignments for structural cell invocations (the group is structurally invoking a cell.)
538        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                // NOTE: this probe is active for the duration of the whole group. Hence, it may be active even when the cell itself is inactive.
550                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        // probe and assignments for primitive invocations (this group is activating a primitive)
566        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
591/// Creates probes for continuous assignments outside of groups. For every cell
592/// or primitive involved in a continuous assignment, this function will generate
593/// "contprimitive" and "contcell" wires as probes.
594fn continuous_assignments(
595    comp: &mut ir::Component,
596    sigs: &ir::LibrarySignatures,
597    collect_stats: bool,
598) -> Option<StatsEntry> {
599    // vector of cells (non-primitives) invoked
600    let mut cell_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
601    // vector of primitives invoked
602    let mut primitive_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
603
604    // set to prevent adding multiple probes for a combinational primitive
605    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                    // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
615                    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    // add probes for primitives in continuous assignment
631    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    // add probes for cells (non-primitives) in continuous assignment
654    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    // Add created assignments to continuous assignments
672    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}