calyx_opt/passes/
data_path_infer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use crate::traversal::{Action, Named, VisResult, Visitor};
use calyx_ir as ir;
use ir::RRC;
use std::{collections::HashSet, rc::Rc};

#[derive(Default)]
/// Infers `@control` and `@data` annotations for cells.
/// A cell marked with `@data` can have `'x` assignments to its `@data` ports
/// which enables downstream optimizations.
///
/// A cell cannot be marked `@data` if:
/// * If it is used in the guard of an assignment
/// * If it is used as the done condition of a group
/// * If it is used as the conditional port for if or while
/// * If it is used as the input to a non-@data port
/// * If it is used as an input for another @control instance
///
/// Because the last constraint is recursive, we use an iterative algorithm to
/// infer the annotations.
pub struct DataPathInfer {
    /// Cells that cannot be marked as a @data cell
    control_cells: HashSet<ir::Id>,
}

impl Named for DataPathInfer {
    fn name() -> &'static str {
        "infer-data-path"
    }

    fn description() -> &'static str {
        "Infers @data annotations for cells"
    }
}

impl DataPathInfer {
    #[inline]
    /// Mark the cell associated with the port as a part of the control path.
    fn mark_port_control(&mut self, port: &ir::Port) {
        if Self::always_safe_src(port) || port.is_hole() {
            log::debug!("`{}': safe port", port.canonical());
            return;
        }
        log::debug!("`{}': control port", port.canonical());
        self.control_cells.insert(port.get_parent_name());
    }

    #[inline]
    /// Source ports that do not make a cell a control cell.
    /// * A @stable port's value is not combinationally affected by the inputs.
    /// * A @done port cannot be combinationally connected to any inputs and must implicitly be stable.
    fn always_safe_src(port: &ir::Port) -> bool {
        port.attributes.has(ir::BoolAttr::Stable)
            || port.attributes.has(ir::NumAttr::Done)
    }

    /// Handle the port and the combinational group of `if` and `while` statements.
    fn port_and_cg(
        &mut self,
        port: RRC<ir::Port>,
        mb_cg: &Option<RRC<ir::CombGroup>>,
    ) {
        let cond_port = port.borrow();
        assert!(!cond_port.is_hole());
        self.mark_port_control(&cond_port);

        // All ports used in the combinational group cannot be data ports
        // since they are used to compute the condition.
        if let Some(cgr) = mb_cg {
            let cg = cgr.borrow();
            for assign in &cg.assignments {
                self.mark_port_control(&assign.src.borrow());
            }
        }
    }

    /// Handle the assignments during the initial pass:
    fn handle_assign<T: Clone>(&mut self, assign: &ir::Assignment<T>) {
        // If the destination port is not marked as `@data` or is a hole,
        // The source is required to be non-`@data` as well.
        let dst = assign.dst.borrow();
        if dst.is_hole() || !dst.attributes.has(ir::BoolAttr::Data) {
            let src = assign.src.borrow();
            self.mark_port_control(&src);
        }
        // Every cell used in a guard cannot be marked as `@data`
        assign.guard.all_ports().into_iter().for_each(|p| {
            let port = p.borrow();
            self.mark_port_control(&port);
        });
    }

    fn iterate_assign<T: Clone>(&mut self, assign: &ir::Assignment<T>) {
        // If the destination is a control port, then the cell used in the
        // source must also be a control port.
        let dst = assign.dst.borrow();
        let src = assign.src.borrow();
        if !dst.is_hole() {
            let dst_cell = dst.get_parent_name();
            if self.control_cells.contains(&dst_cell) {
                self.mark_port_control(&src);
            }
        }
    }
}

impl Visitor for DataPathInfer {
    fn finish_if(
        &mut self,
        s: &mut ir::If,
        _comp: &mut ir::Component,
        _sigs: &ir::LibrarySignatures,
        _comps: &[ir::Component],
    ) -> VisResult {
        self.port_and_cg(Rc::clone(&s.port), &s.cond);
        Ok(Action::Continue)
    }

    fn finish_while(
        &mut self,
        s: &mut ir::While,
        _comp: &mut ir::Component,
        _sigs: &ir::LibrarySignatures,
        _comps: &[ir::Component],
    ) -> VisResult {
        self.port_and_cg(Rc::clone(&s.port), &s.cond);
        Ok(Action::Continue)
    }

    fn finish(
        &mut self,
        comp: &mut ir::Component,
        _sigs: &ir::LibrarySignatures,
        _comps: &[ir::Component],
    ) -> VisResult {
        // Seed using all cells that have been marked as @control and those that
        // appear in the guard of an assignment
        self.control_cells.extend(comp.cells.iter().filter_map(|c| {
            let cell = c.borrow();
            if cell.attributes.has(ir::BoolAttr::Control) {
                Some(cell.name())
            } else {
                None
            }
        }));

        // Handle all assignment in the component
        comp.for_each_assignment(|assign| self.handle_assign(assign));
        comp.for_each_static_assignment(|assign| self.handle_assign(assign));

        // Iterate: For all assignments, if the destination if a control port, mark the source cell as control
        // Start with zero so we do at least one iteration
        let mut old_len = 0;
        let mut iter_count = 0;
        while old_len != self.control_cells.len() {
            old_len = self.control_cells.len();

            comp.for_each_assignment(|assign| self.iterate_assign(assign));
            comp.for_each_static_assignment(|assign| {
                self.iterate_assign(assign)
            });

            // Log a warning if we are taking too long
            iter_count += 1;
            if iter_count > 5 {
                log::warn!(
                    "Data path infer did not converge after 5 iterations"
                );
            }
        }

        // Mark all cells with attributes
        for c in comp.cells.iter() {
            let mut cell = c.borrow_mut();
            if self.control_cells.contains(&cell.name()) {
                cell.attributes.insert(ir::BoolAttr::Control, 1);
            } else {
                cell.attributes.insert(ir::BoolAttr::Data, 1);
            }
        }

        Ok(Action::Stop)
    }
}