calyx_opt/passes/
uniquefy_enables.rs

1use std::{
2    cmp,
3    collections::{BTreeMap, BTreeSet, HashMap},
4};
5
6use crate::traversal::{
7    Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor,
8};
9use calyx_frontend::SetAttr;
10use calyx_ir::{self as ir, Nothing};
11use calyx_utils::{CalyxResult, Id, OutputFile};
12use serde::Serialize;
13
14// This pass is used by the profiler.
15// 1) It converts each dynamic and static enable to an enable of a unique group.
16// 2) It creates a unique comb group for every invoke (to identify the invoke and give profiler-instrumentation a place to instrument the invoke)
17// 3) computes path descriptors for each unique enable group and par (outputted to `path_descriptor_json` if provided); and
18// 4) statically assigns par thread ids to each unique enable group (outputted to `par_thread_json` if provided).
19
20// UG stands for "unique group". This is to separate these names from the original group names
21const UNIQUE_GROUP_SUFFIX: &str = "UG";
22
23pub struct UniquefyEnables {
24    path_descriptor_json: Option<OutputFile>,
25    path_descriptor_infos: BTreeMap<String, PathDescriptorInfo>,
26    par_thread_json: Option<OutputFile>,
27    par_thread_info: BTreeMap<String, BTreeMap<String, u32>>,
28    // Map to maintain a trackable unique name for every invoke comb group.
29    // Will be cleared out after every component.
30    invoke_cell_to_counter: HashMap<Id, i64>,
31}
32
33impl Named for UniquefyEnables {
34    fn name() -> &'static str {
35        "uniquefy-enables"
36    }
37
38    fn description() -> &'static str {
39        "Make all control (dynamic and static) enables unique."
40    }
41
42    fn opts() -> Vec<crate::traversal::PassOpt> {
43        vec![
44            PassOpt::new(
45                "path-descriptor-json",
46                "Write the path descriptor of each enable and par to a JSON file",
47                ParseVal::OutStream(OutputFile::Null),
48                PassOpt::parse_outstream,
49            ),
50            PassOpt::new(
51                "par-thread-json",
52                "Write an assigned thread ID of each enable to a JSON file",
53                ParseVal::OutStream(OutputFile::Null),
54                PassOpt::parse_outstream,
55            ),
56        ]
57    }
58}
59
60/// Information to serialize for locating path descriptors
61#[derive(Serialize)]
62struct PathDescriptorInfo {
63    /// enable id --> descriptor
64    pub enables: BTreeMap<String, String>,
65    /// descriptor --> position set
66    /// (Ideally I'd do a position set --> descriptor mapping but
67    /// a set shouldn't be a key.)
68    pub control_pos: BTreeMap<String, BTreeSet<u32>>,
69}
70
71impl ConstructVisitor for UniquefyEnables {
72    fn from(ctx: &ir::Context) -> CalyxResult<Self>
73    where
74        Self: Sized + Named,
75    {
76        let opts = Self::get_opts(ctx);
77        Ok(UniquefyEnables {
78            path_descriptor_json: opts[&"path-descriptor-json"]
79                .not_null_outstream(),
80            path_descriptor_infos: BTreeMap::new(),
81            par_thread_json: opts[&"par-thread-json"].not_null_outstream(),
82            par_thread_info: BTreeMap::new(),
83            invoke_cell_to_counter: HashMap::new(),
84        })
85    }
86
87    fn clear_data(&mut self) {
88        self.invoke_cell_to_counter = HashMap::new();
89    }
90}
91
92fn assign_par_threads_static(
93    control: &ir::StaticControl,
94    start_idx: u32,
95    next_idx: u32,
96    enable_to_track: &mut BTreeMap<String, u32>,
97) -> u32 {
98    match control {
99        ir::StaticControl::Repeat(ir::StaticRepeat { body, .. }) => {
100            assign_par_threads_static(
101                body,
102                start_idx,
103                next_idx,
104                enable_to_track,
105            )
106        }
107        ir::StaticControl::Enable(ir::StaticEnable { group, .. }) => {
108            let group_name = group.borrow().name().to_string();
109            enable_to_track.insert(group_name, start_idx);
110            start_idx + 1
111        }
112        ir::StaticControl::Par(ir::StaticPar { stmts, .. }) => {
113            let mut idx = next_idx;
114            for stmt in stmts {
115                idx = assign_par_threads_static(
116                    stmt,
117                    idx,
118                    idx + 1,
119                    enable_to_track,
120                );
121            }
122            idx
123        }
124        ir::StaticControl::Seq(ir::StaticSeq { stmts, .. }) => {
125            let mut new_next_idx = next_idx;
126            for stmt in stmts {
127                let potential_new_idx = assign_par_threads_static(
128                    stmt,
129                    start_idx,
130                    new_next_idx,
131                    enable_to_track,
132                );
133                new_next_idx = cmp::max(new_next_idx, potential_new_idx)
134            }
135            new_next_idx
136        }
137        ir::StaticControl::If(ir::StaticIf {
138            tbranch, fbranch, ..
139        }) => {
140            let false_next_idx = assign_par_threads_static(
141                tbranch,
142                start_idx,
143                next_idx,
144                enable_to_track,
145            );
146            assign_par_threads_static(
147                fbranch,
148                start_idx,
149                false_next_idx,
150                enable_to_track,
151            )
152        }
153        ir::StaticControl::Invoke(ir::StaticInvoke { comb_group, .. }) => {
154            let c_group_name =
155                comb_group.as_ref().unwrap().borrow().name().to_string();
156            enable_to_track.insert(c_group_name, start_idx);
157            start_idx + 1
158        }
159        _ => next_idx,
160    }
161}
162
163fn assign_par_threads(
164    control: &ir::Control,
165    start_idx: u32,
166    next_idx: u32,
167    enable_to_track: &mut BTreeMap<String, u32>,
168) -> u32 {
169    match control {
170        ir::Control::Seq(ir::Seq { stmts, .. }) => {
171            let mut new_next_idx = next_idx;
172            for stmt in stmts {
173                let potential_new_idx = assign_par_threads(
174                    stmt,
175                    start_idx,
176                    new_next_idx,
177                    enable_to_track,
178                );
179                new_next_idx = cmp::max(new_next_idx, potential_new_idx)
180            }
181            new_next_idx
182        }
183        ir::Control::Enable(enable) => {
184            let group_name = enable.group.borrow().name().to_string();
185            enable_to_track.insert(group_name, start_idx);
186            start_idx + 1
187        }
188        ir::Control::Par(ir::Par { stmts, .. }) => {
189            let mut idx = next_idx;
190            for stmt in stmts {
191                idx = assign_par_threads(stmt, idx, idx + 1, enable_to_track);
192            }
193            idx
194        }
195        ir::Control::If(ir::If {
196            tbranch,
197            fbranch,
198            cond,
199            ..
200        }) => {
201            let true_next_idx = if let Some(comb_group) = cond {
202                enable_to_track
203                    .insert(comb_group.borrow().name().to_string(), start_idx);
204                start_idx + 1
205            } else {
206                start_idx
207            };
208            let false_next_idx = assign_par_threads(
209                tbranch,
210                true_next_idx,
211                next_idx,
212                enable_to_track,
213            );
214            assign_par_threads(
215                fbranch,
216                start_idx,
217                false_next_idx,
218                enable_to_track,
219            )
220        }
221        ir::Control::While(ir::While { body, cond, .. }) => {
222            let body_start_idx = if let Some(comb_group) = cond {
223                enable_to_track
224                    .insert(comb_group.borrow().name().to_string(), start_idx);
225                start_idx + 1
226            } else {
227                start_idx
228            };
229            assign_par_threads(body, body_start_idx, next_idx, enable_to_track)
230        }
231        ir::Control::Repeat(ir::Repeat { body, .. }) => {
232            assign_par_threads(body, start_idx, next_idx, enable_to_track)
233        }
234        ir::Control::Static(static_control) => assign_par_threads_static(
235            static_control,
236            start_idx,
237            next_idx,
238            enable_to_track,
239        ),
240        ir::Control::Invoke(ir::Invoke { comb_group, .. }) => {
241            let c_group_name =
242                comb_group.as_ref().unwrap().borrow().name().to_string();
243            enable_to_track.insert(c_group_name, start_idx);
244            start_idx + 1
245        }
246        _ => next_idx,
247    }
248}
249
250fn compute_path_descriptors_static(
251    control: &ir::StaticControl,
252    current_id: String,
253    path_descriptor_info: &mut PathDescriptorInfo,
254    parent_is_component: bool,
255) {
256    match control {
257        ir::StaticControl::Repeat(ir::StaticRepeat {
258            body,
259            attributes,
260            ..
261        }) => {
262            let repeat_id = format!("{current_id}-");
263            let body_id = format!("{repeat_id}b");
264            compute_path_descriptors_static(
265                body,
266                body_id,
267                path_descriptor_info,
268                false,
269            );
270            let new_pos_set = retrieve_pos_set(attributes);
271            path_descriptor_info
272                .control_pos
273                .insert(repeat_id, new_pos_set);
274        }
275        ir::StaticControl::Enable(ir::StaticEnable { group, .. }) => {
276            let group_id = if parent_is_component {
277                // edge case: the entire control is just one static enable
278                format!("{current_id}0")
279            } else {
280                current_id
281            };
282            let group_name = group.borrow().name();
283            path_descriptor_info
284                .enables
285                .insert(group_name.to_string(), group_id);
286        }
287        ir::StaticControl::Invoke(ir::StaticInvoke { comb_group, .. }) => {
288            // edge case: the entire control is just this static invoke
289            let invoke_id = if parent_is_component {
290                format!("{current_id}0")
291            } else {
292                current_id
293            };
294            let comb_group_name = comb_group.as_ref().unwrap().borrow().name();
295            path_descriptor_info
296                .enables
297                .insert(comb_group_name.to_string(), invoke_id);
298        }
299        ir::StaticControl::Par(ir::StaticPar {
300            stmts, attributes, ..
301        }) => {
302            let par_id = format!("{current_id}-");
303            for (acc, stmt) in stmts.iter().enumerate() {
304                let stmt_id = format!("{par_id}{acc}");
305                compute_path_descriptors_static(
306                    stmt,
307                    stmt_id,
308                    path_descriptor_info,
309                    false,
310                );
311            }
312            let new_pos_set: BTreeSet<u32> = retrieve_pos_set(attributes);
313            path_descriptor_info.control_pos.insert(par_id, new_pos_set);
314        }
315        ir::StaticControl::Seq(ir::StaticSeq {
316            stmts, attributes, ..
317        }) => {
318            let seq_id = format!("{current_id}-");
319            for (acc, stmt) in stmts.iter().enumerate() {
320                let stmt_id = format!("{seq_id}{acc}");
321                compute_path_descriptors_static(
322                    stmt,
323                    stmt_id,
324                    path_descriptor_info,
325                    false,
326                );
327            }
328            let new_pos_set: BTreeSet<u32> = retrieve_pos_set(attributes);
329            path_descriptor_info.control_pos.insert(seq_id, new_pos_set);
330        }
331        ir::StaticControl::If(ir::StaticIf {
332            tbranch,
333            fbranch,
334            attributes,
335            ..
336        }) => {
337            let if_id = format!("{current_id}-");
338            // process true branch
339            let true_id = format!("{if_id}t");
340            compute_path_descriptors_static(
341                tbranch,
342                true_id,
343                path_descriptor_info,
344                false,
345            );
346            // process false branch
347            let false_id = format!("{if_id}f");
348            compute_path_descriptors_static(
349                fbranch,
350                false_id,
351                path_descriptor_info,
352                false,
353            );
354            path_descriptor_info
355                .control_pos
356                .insert(if_id, retrieve_pos_set(attributes));
357        }
358        ir::StaticControl::Empty(_empty) => (),
359    }
360}
361
362fn compute_path_descriptors(
363    control: &ir::Control,
364    current_id: String,
365    path_descriptor_info: &mut PathDescriptorInfo,
366    parent_is_component: bool,
367) {
368    match control {
369        ir::Control::Seq(ir::Seq {
370            stmts, attributes, ..
371        }) => {
372            let seq_id = format!("{current_id}-");
373            for (acc, stmt) in stmts.iter().enumerate() {
374                let stmt_id = format!("{current_id}-{acc}");
375                compute_path_descriptors(
376                    stmt,
377                    stmt_id,
378                    path_descriptor_info,
379                    false,
380                );
381            }
382            let new_pos_set = retrieve_pos_set(attributes);
383            path_descriptor_info.control_pos.insert(seq_id, new_pos_set);
384        }
385        ir::Control::Par(ir::Par {
386            stmts, attributes, ..
387        }) => {
388            let par_id = format!("{current_id}-");
389            for (acc, stmt) in stmts.iter().enumerate() {
390                let stmt_id = format!("{par_id}{acc}");
391                compute_path_descriptors(
392                    stmt,
393                    stmt_id,
394                    path_descriptor_info,
395                    false,
396                );
397            }
398            // add this node to path_descriptor_info
399            let new_pos_set = retrieve_pos_set(attributes);
400            path_descriptor_info.control_pos.insert(par_id, new_pos_set);
401        }
402        ir::Control::If(ir::If {
403            tbranch,
404            fbranch,
405            attributes,
406            cond,
407            ..
408        }) => {
409            let if_id = format!("{current_id}-");
410            // process condition if it exists
411            if let Some(comb_group) = cond {
412                let comb_id = format!("{if_id}c");
413                path_descriptor_info
414                    .enables
415                    .insert(comb_group.borrow().name().to_string(), comb_id);
416            }
417
418            // process true branch
419            let true_id = format!("{if_id}t");
420            compute_path_descriptors(
421                tbranch,
422                true_id,
423                path_descriptor_info,
424                false,
425            );
426            // process false branch
427            let false_id = format!("{if_id}f");
428            compute_path_descriptors(
429                fbranch,
430                false_id,
431                path_descriptor_info,
432                false,
433            );
434            // add this node to path_descriptor_info
435            let new_pos_set = retrieve_pos_set(attributes);
436            path_descriptor_info.control_pos.insert(if_id, new_pos_set);
437        }
438        ir::Control::While(ir::While {
439            body,
440            attributes,
441            cond,
442            ..
443        }) => {
444            let while_id = format!("{current_id}-");
445            let body_id = format!("{while_id}b");
446            // FIXME: we need to create unique enables for comb groups associated with `while`s and `if`s`
447
448            // add path descriptor for comb group associated with while if exists
449            if let Some(comb_group) = cond {
450                let comb_id = format!("{while_id}c");
451                path_descriptor_info
452                    .enables
453                    .insert(comb_group.borrow().name().to_string(), comb_id);
454            }
455            compute_path_descriptors(
456                body,
457                body_id,
458                path_descriptor_info,
459                false,
460            );
461            // add this node to path_descriptor_info
462            let new_pos_set = retrieve_pos_set(attributes);
463            path_descriptor_info
464                .control_pos
465                .insert(while_id, new_pos_set);
466        }
467        ir::Control::Enable(ir::Enable { group, .. }) => {
468            let group_id = if parent_is_component {
469                // edge case: the entire control is just one enable
470                format!("{current_id}0")
471            } else {
472                current_id
473            };
474            let group_name = group.borrow().name();
475            path_descriptor_info
476                .enables
477                .insert(group_name.to_string(), group_id);
478        }
479        ir::Control::Invoke(ir::Invoke { comb_group, .. }) => {
480            let invoke_id = if parent_is_component {
481                // edge case: the entire control is just this enable
482                format!("{current_id}0")
483            } else {
484                current_id
485            };
486            let comb_group_name =
487                comb_group.as_ref().unwrap().borrow().name().to_string();
488            path_descriptor_info
489                .enables
490                .insert(comb_group_name, invoke_id);
491        }
492        ir::Control::Repeat(ir::Repeat {
493            body, attributes, ..
494        }) => {
495            let repeat_id = format!("{current_id}-");
496            let body_id = format!("{repeat_id}b");
497            compute_path_descriptors(
498                body,
499                body_id,
500                path_descriptor_info,
501                false,
502            );
503            // add this node to path_descriptor_info
504            let new_pos_set = retrieve_pos_set(attributes);
505            path_descriptor_info
506                .control_pos
507                .insert(repeat_id, new_pos_set);
508        }
509        ir::Control::Static(static_control) => {
510            compute_path_descriptors_static(
511                static_control,
512                current_id,
513                path_descriptor_info,
514                parent_is_component,
515            );
516        }
517        ir::Control::Empty(_) => (),
518        ir::Control::FSMEnable(_) => todo!(),
519    }
520}
521
522/// Returns a BTreeSet with the elements contained in the @pos set attribute.
523fn retrieve_pos_set(attributes: &calyx_ir::Attributes) -> BTreeSet<u32> {
524    let new_pos_set: BTreeSet<u32> =
525        if let Some(pos_set) = attributes.get_set(SetAttr::Pos) {
526            pos_set.iter().copied().collect()
527        } else {
528            BTreeSet::new()
529        };
530    new_pos_set
531}
532
533/// Helper function to construct a unique version of a combinational group used as the
534/// condition in an if or a while, if one exists. Otherwise returns None.
535fn create_unique_comb_group(
536    cond: &Option<std::rc::Rc<std::cell::RefCell<calyx_ir::CombGroup>>>,
537    comp: &mut calyx_ir::Component,
538    sigs: &calyx_ir::LibrarySignatures,
539) -> Option<std::rc::Rc<std::cell::RefCell<calyx_ir::CombGroup>>> {
540    if let Some(comb_group) = cond {
541        let unique_comb_group_name: String =
542            format!("{}{}", comb_group.borrow().name(), UNIQUE_GROUP_SUFFIX);
543        let mut builder = ir::Builder::new(comp, sigs);
544        let unique_comb_group = builder.add_comb_group(unique_comb_group_name);
545        unique_comb_group.borrow_mut().assignments =
546            comb_group.borrow().assignments.clone();
547        unique_comb_group.borrow_mut().attributes =
548            comb_group.borrow().attributes.clone();
549        Some(unique_comb_group)
550    } else {
551        None
552    }
553}
554
555impl Visitor for UniquefyEnables {
556    fn finish_while(
557        &mut self,
558        s: &mut calyx_ir::While,
559        comp: &mut calyx_ir::Component,
560        sigs: &calyx_ir::LibrarySignatures,
561        _comps: &[calyx_ir::Component],
562    ) -> VisResult {
563        // create a freshly named version of the condition comb group if one exists.
564        s.cond = create_unique_comb_group(&s.cond, comp, sigs);
565        Ok(Action::Continue)
566    }
567
568    fn finish_if(
569        &mut self,
570        s: &mut calyx_ir::If,
571        comp: &mut calyx_ir::Component,
572        sigs: &calyx_ir::LibrarySignatures,
573        _comps: &[calyx_ir::Component],
574    ) -> VisResult {
575        // create a freshly named version of the condition comb group if one exists.
576        s.cond = create_unique_comb_group(&s.cond, comp, sigs);
577        Ok(Action::Continue)
578    }
579
580    fn enable(
581        &mut self,
582        s: &mut calyx_ir::Enable,
583        comp: &mut calyx_ir::Component,
584        sigs: &calyx_ir::LibrarySignatures,
585        _comps: &[calyx_ir::Component],
586    ) -> VisResult {
587        // create a unique group for this particular enable.
588        let group_name = s.group.borrow().name();
589        // UG stands for "unique group". This is to separate these names from the original group names
590        let unique_group_name: String =
591            format!("{group_name}{UNIQUE_GROUP_SUFFIX}");
592        // create an unique-ified version of the group
593        let mut builder = ir::Builder::new(comp, sigs);
594        let unique_group = builder.add_group(unique_group_name);
595        let mut unique_group_assignments: Vec<calyx_ir::Assignment<Nothing>> =
596            Vec::new();
597        for asgn in s.group.borrow().assignments.iter() {
598            if asgn.dst.borrow().get_parent_name() == group_name
599                && asgn.dst.borrow().name == "done"
600            {
601                // done needs to be reassigned
602                let new_done_asgn = builder.build_assignment(
603                    unique_group.borrow().get("done"),
604                    asgn.src.clone(),
605                    *asgn.guard.clone(),
606                );
607                unique_group_assignments.push(new_done_asgn);
608            } else {
609                unique_group_assignments.push(asgn.clone());
610            }
611        }
612        unique_group
613            .borrow_mut()
614            .assignments
615            .append(&mut unique_group_assignments);
616        // copy over all attributes that were in the original group.
617        unique_group.borrow_mut().attributes =
618            s.group.borrow().attributes.clone();
619        Ok(Action::Change(Box::new(ir::Control::enable(unique_group))))
620    }
621
622    fn static_enable(
623        &mut self,
624        s: &mut calyx_ir::StaticEnable,
625        comp: &mut calyx_ir::Component,
626        sigs: &calyx_ir::LibrarySignatures,
627        _comps: &[calyx_ir::Component],
628    ) -> VisResult {
629        // create a unique group for this particular static enable.
630        let group_name = s.group.borrow().name();
631        let unique_group_name = format!("{group_name}{UNIQUE_GROUP_SUFFIX}");
632        // create an unique-ified version of the group
633        let mut builder = ir::Builder::new(comp, sigs);
634        let unique_group = builder.add_static_group(
635            unique_group_name,
636            s.group.borrow().get_latency(),
637        );
638        // Since we don't need to worry about setting the `done` signal, the assignments of unique_group are
639        // a straight copy of the original group's assignments
640        unique_group.borrow_mut().assignments =
641            s.group.borrow().assignments.clone();
642        // copy over all attributes that were in the original group.
643        unique_group.borrow_mut().attributes =
644            s.group.borrow().attributes.clone();
645        Ok(Action::Change(Box::new(ir::Control::static_enable(
646            unique_group,
647        ))))
648    }
649
650    fn invoke(
651        &mut self,
652        s: &mut calyx_ir::Invoke,
653        comp: &mut calyx_ir::Component,
654        sigs: &calyx_ir::LibrarySignatures,
655        _comps: &[calyx_ir::Component],
656    ) -> VisResult {
657        // invokes need a uniquely named comb group attached to them
658        let mut builder = ir::Builder::new(comp, sigs);
659        let invoked_cell_name = s.comp.borrow().name();
660        let invoke_id = if let Some(comb_group_ref) = &s.comb_group {
661            Id::new(format!(
662                "{}_{}",
663                invoked_cell_name,
664                comb_group_ref.borrow().name()
665            ))
666        } else {
667            invoked_cell_name
668        };
669        // pull value from internal unique counter to differentiate between multiple invokes of the same cell.
670        let internal_counter =
671            self.invoke_cell_to_counter.get(&invoke_id).unwrap_or(&0);
672        let comb_cell_prefix = format!(
673            "invoke_{invoke_id}{internal_counter}{UNIQUE_GROUP_SUFFIX}"
674        );
675        let new_comb_group = builder.add_comb_group(comb_cell_prefix);
676        // update internal counter
677        self.invoke_cell_to_counter
678            .insert(invoked_cell_name, *internal_counter + 1);
679        if let Some(comb_group_ref) = &s.comb_group {
680            // copy assignments over to new group
681            for asgn in &comb_group_ref.borrow().assignments {
682                new_comb_group.borrow_mut().assignments.push(asgn.clone());
683            }
684        }
685        s.comb_group = Some(new_comb_group);
686        Ok(Action::Continue)
687    }
688
689    fn static_invoke(
690        &mut self,
691        s: &mut calyx_ir::StaticInvoke,
692        comp: &mut calyx_ir::Component,
693        sigs: &calyx_ir::LibrarySignatures,
694        _comps: &[calyx_ir::Component],
695    ) -> VisResult {
696        // invokes need a uniquely named comb group attached to them
697        let mut builder = ir::Builder::new(comp, sigs);
698        let invoked_cell_name = s.comp.borrow().name();
699        // pull value from internal unique counter to differentiate between multiple invokes of the same cell.
700        let internal_counter = self
701            .invoke_cell_to_counter
702            .get(&invoked_cell_name)
703            .unwrap_or(&0);
704        let comb_cell_prefix = format!(
705            "invoke_{invoked_cell_name}{internal_counter}{UNIQUE_GROUP_SUFFIX}"
706        );
707        // update internal counter
708        self.invoke_cell_to_counter
709            .insert(invoked_cell_name, *internal_counter + 1);
710        let new_comb_group = builder.add_comb_group(comb_cell_prefix);
711        if let Some(comb_group_ref) = &s.comb_group {
712            // copy assignments over to new group
713            for asgn in &comb_group_ref.borrow().assignments {
714                new_comb_group.borrow_mut().assignments.push(asgn.clone());
715            }
716        }
717        s.comb_group = Some(new_comb_group);
718        Ok(Action::Continue)
719    }
720
721    fn finish(
722        &mut self,
723        comp: &mut calyx_ir::Component,
724        _sigs: &calyx_ir::LibrarySignatures,
725        _comps: &[calyx_ir::Component],
726    ) -> VisResult {
727        // Compute path descriptors for each enable and par block in the component.
728        let control = comp.control.borrow();
729        let mut path_descriptor_info = PathDescriptorInfo {
730            enables: BTreeMap::new(),
731            control_pos: BTreeMap::new(),
732        };
733        compute_path_descriptors(
734            &control,
735            format!("{}.", comp.name),
736            &mut path_descriptor_info,
737            true,
738        );
739        self.path_descriptor_infos
740            .insert(comp.name.to_string(), path_descriptor_info);
741        // Compute par thread ids for each enable in the component.
742        let mut enable_to_track: BTreeMap<String, u32> = BTreeMap::new();
743        assign_par_threads(&control, 0, 1, &mut enable_to_track);
744        self.par_thread_info
745            .insert(comp.name.to_string(), enable_to_track);
746        Ok(Action::Continue)
747    }
748
749    fn finish_context(&mut self, _ctx: &mut calyx_ir::Context) -> VisResult {
750        // Write path descriptors to file if prompted.
751        if let Some(json_out_file) = &mut self.path_descriptor_json {
752            let _ = serde_json::to_writer_pretty(
753                json_out_file.get_write(),
754                &self.path_descriptor_infos,
755            );
756        }
757        // Write par thread assignments to file if prompted.
758        if let Some(json_out_file) = &mut self.par_thread_json {
759            let _ = serde_json::to_writer_pretty(
760                json_out_file.get_write(),
761                &self.par_thread_info,
762            );
763        }
764        Ok(Action::Continue)
765    }
766}