calyx_opt/passes/
data_path_infer.rs

1use crate::traversal::{Action, Named, VisResult, Visitor};
2use calyx_ir as ir;
3use ir::RRC;
4use std::{collections::HashSet, rc::Rc};
5
6#[derive(Default)]
7/// Infers `@control` and `@data` annotations for cells.
8/// A cell marked with `@data` can have `'x` assignments to its `@data` ports
9/// which enables downstream optimizations.
10///
11/// A cell cannot be marked `@data` if:
12/// * If it is used in the guard of an assignment
13/// * If it is used as the done condition of a group
14/// * If it is used as the conditional port for if or while
15/// * If it is used as the input to a non-@data port
16/// * If it is used as an input for another @control instance
17///
18/// Because the last constraint is recursive, we use an iterative algorithm to
19/// infer the annotations.
20pub struct DataPathInfer {
21    /// Cells that cannot be marked as a @data cell
22    control_cells: HashSet<ir::Id>,
23}
24
25impl Named for DataPathInfer {
26    fn name() -> &'static str {
27        "infer-data-path"
28    }
29
30    fn description() -> &'static str {
31        "Infers @data annotations for cells"
32    }
33}
34
35impl DataPathInfer {
36    #[inline]
37    /// Mark the cell associated with the port as a part of the control path.
38    fn mark_port_control(&mut self, port: &ir::Port) {
39        if Self::always_safe_src(port) || port.is_hole() {
40            log::debug!("`{}': safe port", port.canonical());
41            return;
42        }
43        log::debug!("`{}': control port", port.canonical());
44        self.control_cells.insert(port.get_parent_name());
45    }
46
47    #[inline]
48    /// Source ports that do not make a cell a control cell.
49    /// * A @stable port's value is not combinationally affected by the inputs.
50    /// * A @done port cannot be combinationally connected to any inputs and must implicitly be stable.
51    fn always_safe_src(port: &ir::Port) -> bool {
52        port.attributes.has(ir::BoolAttr::Stable)
53            || port.attributes.has(ir::NumAttr::Done)
54    }
55
56    /// Handle the port and the combinational group of `if` and `while` statements.
57    fn port_and_cg(
58        &mut self,
59        port: RRC<ir::Port>,
60        mb_cg: &Option<RRC<ir::CombGroup>>,
61    ) {
62        let cond_port = port.borrow();
63        assert!(!cond_port.is_hole());
64        self.mark_port_control(&cond_port);
65
66        // All ports used in the combinational group cannot be data ports
67        // since they are used to compute the condition.
68        if let Some(cgr) = mb_cg {
69            let cg = cgr.borrow();
70            for assign in &cg.assignments {
71                self.mark_port_control(&assign.src.borrow());
72            }
73        }
74    }
75
76    /// Handle the assignments during the initial pass:
77    fn handle_assign<T: Clone>(&mut self, assign: &ir::Assignment<T>) {
78        // If the destination port is not marked as `@data` or is a hole,
79        // The source is required to be non-`@data` as well.
80        let dst = assign.dst.borrow();
81        if dst.is_hole() || !dst.attributes.has(ir::BoolAttr::Data) {
82            let src = assign.src.borrow();
83            self.mark_port_control(&src);
84        }
85        // Every cell used in a guard cannot be marked as `@data`
86        assign.guard.all_ports().into_iter().for_each(|p| {
87            let port = p.borrow();
88            self.mark_port_control(&port);
89        });
90    }
91
92    fn iterate_assign<T: Clone>(&mut self, assign: &ir::Assignment<T>) {
93        // If the destination is a control port, then the cell used in the
94        // source must also be a control port.
95        let dst = assign.dst.borrow();
96        let src = assign.src.borrow();
97        if !dst.is_hole() {
98            let dst_cell = dst.get_parent_name();
99            if self.control_cells.contains(&dst_cell) {
100                self.mark_port_control(&src);
101            }
102        }
103    }
104}
105
106impl Visitor for DataPathInfer {
107    fn finish_if(
108        &mut self,
109        s: &mut ir::If,
110        _comp: &mut ir::Component,
111        _sigs: &ir::LibrarySignatures,
112        _comps: &[ir::Component],
113    ) -> VisResult {
114        self.port_and_cg(Rc::clone(&s.port), &s.cond);
115        Ok(Action::Continue)
116    }
117
118    fn finish_while(
119        &mut self,
120        s: &mut ir::While,
121        _comp: &mut ir::Component,
122        _sigs: &ir::LibrarySignatures,
123        _comps: &[ir::Component],
124    ) -> VisResult {
125        self.port_and_cg(Rc::clone(&s.port), &s.cond);
126        Ok(Action::Continue)
127    }
128
129    fn finish(
130        &mut self,
131        comp: &mut ir::Component,
132        _sigs: &ir::LibrarySignatures,
133        _comps: &[ir::Component],
134    ) -> VisResult {
135        // Seed using all cells that have been marked as @control and those that
136        // appear in the guard of an assignment
137        self.control_cells.extend(comp.cells.iter().filter_map(|c| {
138            let cell = c.borrow();
139            if cell.attributes.has(ir::BoolAttr::Control) {
140                Some(cell.name())
141            } else {
142                None
143            }
144        }));
145
146        // Handle all assignment in the component
147        comp.for_each_assignment(|assign| self.handle_assign(assign));
148        comp.for_each_static_assignment(|assign| self.handle_assign(assign));
149
150        // Iterate: For all assignments, if the destination if a control port, mark the source cell as control
151        // Start with zero so we do at least one iteration
152        let mut old_len = 0;
153        let mut iter_count = 0;
154        while old_len != self.control_cells.len() {
155            old_len = self.control_cells.len();
156
157            comp.for_each_assignment(|assign| self.iterate_assign(assign));
158            comp.for_each_static_assignment(|assign| {
159                self.iterate_assign(assign)
160            });
161
162            // Log a warning if we are taking too long
163            iter_count += 1;
164            if iter_count > 5 {
165                log::warn!(
166                    "Data path infer did not converge after 5 iterations"
167                );
168            }
169        }
170
171        // Mark all cells with attributes
172        for c in comp.cells.iter() {
173            let mut cell = c.borrow_mut();
174            if self.control_cells.contains(&cell.name()) {
175                cell.attributes.insert(ir::BoolAttr::Control, 1);
176            } else {
177                cell.attributes.insert(ir::BoolAttr::Data, 1);
178            }
179        }
180
181        Ok(Action::Stop)
182    }
183}