calyx_opt/passes_experimental/
hole_inliner.rs

1use crate::traversal::{Action, Named, VisResult, Visitor};
2use crate::{analysis::GraphAnalysis, passes::TopDownCompileControl};
3use calyx_ir::{self as ir, LibrarySignatures, RRC, structure};
4use calyx_utils::Error;
5use ir::Nothing;
6use std::{collections::HashMap, rc::Rc};
7
8#[derive(Default)]
9/// Removes all groups and inlines reads and writes from holes.
10///
11/// After running this pass, there are no groups left in the `wires` section
12/// of the program.
13/// All remaining wires are continuous assignments which can be transformed
14/// into wires in a hardware description language.
15pub struct HoleInliner;
16
17impl Named for HoleInliner {
18    fn name() -> &'static str {
19        "hole-inliner"
20    }
21
22    fn description() -> &'static str {
23        "inlines holes"
24    }
25}
26
27type Store = HashMap<ir::Canonical, (RRC<ir::Port>, ir::Guard<ir::Nothing>)>;
28
29/// Finds the 'fixed_point' of a map from Hole names to guards under the
30/// inlining operation. The map contains entries like:
31/// ```
32/// A[go] -> some_thing & B[go] & !A[done]
33/// B[go] -> C[go]
34/// C[go] -> go
35/// ...
36/// ```
37/// We want to transform this so that the guard expression for every
38/// hole does not itself contain holes.
39///
40/// We compute the fixed point using a worklist algorithm.
41/// Variables:
42///  - `guard(x)`: refers to the guard of the hole `x`
43///  - `worklist`: a queue that contains fully inlined guards that have not yet been inlined into other guards
44///
45/// Algorithm:
46///  - `worklist` is initialized to be all the holes that contain no holes in their guards.
47///  - while there are things in `worklist`:
48///    - pop a hole, `H`, from `worklist`
49///    - for every hole, `a` that reads from `H`
50///      - replace all instances of `H` in `guard(a)` with `guard(H)`
51///      - if no holes in `guard(a)`, add to `worklist`
52fn fixed_point(graph: &GraphAnalysis, map: &mut Store) {
53    // keeps track of next holes we can inline
54    let mut worklist = Vec::new();
55
56    // helper to check if a guard has holes
57    let has_holes = |guard: &ir::Guard<Nothing>| {
58        guard.all_ports().iter().any(|p| p.borrow().is_hole())
59    };
60
61    // initialize the worklist to have guards that have no holes
62    for (key, (_, guard)) in map.iter() {
63        if !has_holes(guard) {
64            worklist.push(key.clone())
65        }
66    }
67
68    while !worklist.is_empty() {
69        let hole_key = worklist.pop().unwrap_or_else(|| unreachable!());
70        let (hole, new_guard) = map[&hole_key].clone();
71
72        // for every read from the hole
73        for read in graph
74            .reads_from(&hole.borrow())
75            .filter(|p| p.borrow().is_hole())
76        {
77            // inline `hole_key` into `read`
78            let key = read.borrow().canonical();
79            map.entry(read.borrow().canonical())
80                .and_modify(|(_, guard)| {
81                    guard.for_each(&mut |port| {
82                        if port.borrow().canonical() == hole_key {
83                            Some(new_guard.clone())
84                        } else {
85                            None
86                        }
87                    })
88                });
89            // if done with this guard, add it to the worklist
90            if !has_holes(&map[&key].1) {
91                worklist.push(key)
92            }
93        }
94    }
95}
96
97impl Visitor for HoleInliner {
98    fn start(
99        &mut self,
100        comp: &mut ir::Component,
101        sigs: &LibrarySignatures,
102        _comps: &[ir::Component],
103    ) -> VisResult {
104        // get the only group in the enable
105        let top_level = match &*comp.control.borrow() {
106            ir::Control::Empty(_) => return Ok(Action::Stop),
107            ir::Control::Enable(en) => Rc::clone(&en.group),
108            _ => {
109                return Err(Error::malformed_control(format!(
110                    "{}: Control shoudl be a single enable. Try running `{}` before inlining.",
111                    Self::name(),
112                    TopDownCompileControl::name()
113                )));
114            }
115        };
116
117        let this_comp = Rc::clone(&comp.signature);
118        let mut builder = ir::Builder::new(comp, sigs);
119
120        // add top_level[go] = this.go
121        let mut asgns = vec![
122            builder.build_assignment(
123                top_level.borrow().get("go"),
124                this_comp.borrow().get_unique_with_attr(ir::NumAttr::Go)?,
125                ir::Guard::True,
126            ),
127            builder.build_assignment(
128                this_comp.borrow().get_unique_with_attr(ir::NumAttr::Done)?,
129                top_level.borrow().get("done"),
130                ir::Guard::True,
131            ),
132        ];
133        builder.component.continuous_assignments.append(&mut asgns);
134
135        // construct analysis graph and find sub-graph of all edges that include a hole
136        let analysis = GraphAnalysis::from(&*builder.component);
137        let subgraph = analysis
138            .edge_induced_subgraph(|src, dst| src.is_hole() || dst.is_hole());
139
140        // if subgraph has cycles, error out
141        if subgraph.has_cycles() {
142            // XXX use topo sort to find where the cycle is
143            return Err(Error::malformed_structure(
144                "Cyclic hole definition.".to_string(),
145            ));
146        }
147
148        // map of holes to their guard expressions
149        let mut map: Store = HashMap::new();
150        let mut assignments = vec![];
151        for group in builder.component.get_groups().iter() {
152            // remove all assignments from group, taking ownership
153            let mut group = group.borrow_mut();
154            assignments.append(&mut group.assignments.drain(..).collect());
155        }
156
157        assert!(
158            builder.component.get_static_groups().is_empty(),
159            "should have removed static groups when inlining holes"
160        );
161
162        // add the continuous assignment edges
163        assignments.append(
164            &mut builder.component.continuous_assignments.drain(..).collect(),
165        );
166
167        for asgn in &mut assignments {
168            // if assignment writes into a hole, save it
169            let dst = asgn.dst.borrow();
170            if dst.is_hole() {
171                map.entry(dst.canonical())
172                    .and_modify(|(_, val)| {
173                        // XXX: seems like unncessary clone
174                        *val = val.clone().or(asgn
175                            .guard
176                            .clone()
177                            .and(ir::Guard::port(Rc::clone(&asgn.src))));
178                    })
179                    .or_insert((
180                        Rc::clone(&asgn.dst),
181                        asgn.guard
182                            .clone()
183                            .and(ir::Guard::port(Rc::clone(&asgn.src))),
184                    ));
185            }
186        }
187
188        // find fixed point of map
189        fixed_point(&subgraph, &mut map);
190
191        // remove edges that write to a hole
192        assignments.retain(|asgn| !asgn.dst.borrow().is_hole());
193
194        // move direct reads from holes into the guard so they can be inlined
195        //   e.g. s.in = G[go]; => s.in G[go] ? 1'b1;
196        structure!(
197            builder;
198            let signal_on = constant(1, 1);
199        );
200        assignments.iter_mut().for_each(|asgn| {
201            if asgn.src.borrow().is_hole() {
202                let and_guard = ir::Guard::port(Rc::clone(&asgn.src));
203                *asgn.guard &= and_guard;
204                asgn.src = signal_on.borrow().get("out");
205            }
206        });
207
208        // replace reads from a hole with the value in the map
209        for asgn in &mut assignments {
210            asgn.guard.for_each(&mut |port| {
211                if port.borrow().is_hole() {
212                    Some(map[&port.borrow().canonical()].1.clone())
213                } else {
214                    None
215                }
216            })
217        }
218        comp.continuous_assignments = assignments;
219
220        // remove all groups
221        comp.get_groups_mut().clear();
222        comp.get_static_groups_mut().clear();
223
224        // remove group from control
225        Ok(Action::change(ir::Control::empty()))
226    }
227}