calyx_opt/passes/
hole_inliner.rs

1use crate::traversal::{Action, Named, VisResult, Visitor};
2use crate::{analysis::GraphAnalysis, passes::TopDownCompileControl};
3use calyx_ir::{self as ir, structure, LibrarySignatures, RRC};
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
59            .all_ports()
60            .iter()
61            .map(|p| p.borrow().is_hole())
62            .any(|e| e)
63    };
64
65    // initialize the worklist to have guards that have no holes
66    for (key, (_, guard)) in map.iter() {
67        if !has_holes(guard) {
68            worklist.push(key.clone())
69        }
70    }
71
72    while !worklist.is_empty() {
73        let hole_key = worklist.pop().unwrap_or_else(|| unreachable!());
74        let (hole, new_guard) = map[&hole_key].clone();
75
76        // for every read from the hole
77        for read in graph
78            .reads_from(&hole.borrow())
79            .filter(|p| p.borrow().is_hole())
80        {
81            // inline `hole_key` into `read`
82            let key = read.borrow().canonical();
83            map.entry(read.borrow().canonical())
84                .and_modify(|(_, guard)| {
85                    guard.for_each(&mut |port| {
86                        if port.borrow().canonical() == hole_key {
87                            Some(new_guard.clone())
88                        } else {
89                            None
90                        }
91                    })
92                });
93            // if done with this guard, add it to the worklist
94            if !has_holes(&map[&key].1) {
95                worklist.push(key)
96            }
97        }
98    }
99}
100
101impl Visitor for HoleInliner {
102    fn start(
103        &mut self,
104        comp: &mut ir::Component,
105        sigs: &LibrarySignatures,
106        _comps: &[ir::Component],
107    ) -> VisResult {
108        // get the only group in the enable
109        let top_level = match &*comp.control.borrow() {
110            ir::Control::Empty(_) => return Ok(Action::Stop),
111            ir::Control::Enable(en) => Rc::clone(&en.group),
112            _ => return Err(Error::malformed_control(format!(
113                    "{}: Control shoudl be a single enable. Try running `{}` before inlining.",
114                    Self::name(),
115                    TopDownCompileControl::name()))
116            )
117        };
118
119        let this_comp = Rc::clone(&comp.signature);
120        let mut builder = ir::Builder::new(comp, sigs);
121
122        // add top_level[go] = this.go
123        let mut asgns = vec![
124            builder.build_assignment(
125                top_level.borrow().get("go"),
126                this_comp.borrow().get_unique_with_attr(ir::NumAttr::Go)?,
127                ir::Guard::True,
128            ),
129            builder.build_assignment(
130                this_comp.borrow().get_unique_with_attr(ir::NumAttr::Done)?,
131                top_level.borrow().get("done"),
132                ir::Guard::True,
133            ),
134        ];
135        builder.component.continuous_assignments.append(&mut asgns);
136
137        // construct analysis graph and find sub-graph of all edges that include a hole
138        let analysis = GraphAnalysis::from(&*builder.component);
139        let subgraph = analysis
140            .edge_induced_subgraph(|src, dst| src.is_hole() || dst.is_hole());
141
142        // if subgraph has cycles, error out
143        if subgraph.has_cycles() {
144            // XXX use topo sort to find where the cycle is
145            return Err(Error::malformed_structure(
146                "Cyclic hole definition.".to_string(),
147            ));
148        }
149
150        // map of holes to their guard expressions
151        let mut map: Store = HashMap::new();
152        let mut assignments = vec![];
153        for group in builder.component.get_groups().iter() {
154            // remove all assignments from group, taking ownership
155            let mut group = group.borrow_mut();
156            assignments.append(&mut group.assignments.drain(..).collect());
157        }
158
159        assert!(
160            builder.component.get_static_groups().is_empty(),
161            "should have removed static groups when inlining holes"
162        );
163
164        // add the continuous assignment edges
165        assignments.append(
166            &mut builder.component.continuous_assignments.drain(..).collect(),
167        );
168
169        for asgn in &mut assignments {
170            // if assignment writes into a hole, save it
171            let dst = asgn.dst.borrow();
172            if dst.is_hole() {
173                map.entry(dst.canonical())
174                    .and_modify(|(_, val)| {
175                        // XXX: seems like unncessary clone
176                        *val = val.clone().or(asgn
177                            .guard
178                            .clone()
179                            .and(ir::Guard::port(Rc::clone(&asgn.src))));
180                    })
181                    .or_insert((
182                        Rc::clone(&asgn.dst),
183                        asgn.guard
184                            .clone()
185                            .and(ir::Guard::port(Rc::clone(&asgn.src))),
186                    ));
187            }
188        }
189
190        // find fixed point of map
191        fixed_point(&subgraph, &mut map);
192
193        // remove edges that write to a hole
194        assignments.retain(|asgn| !asgn.dst.borrow().is_hole());
195
196        // move direct reads from holes into the guard so they can be inlined
197        //   e.g. s.in = G[go]; => s.in G[go] ? 1'b1;
198        structure!(
199            builder;
200            let signal_on = constant(1, 1);
201        );
202        assignments.iter_mut().for_each(|asgn| {
203            if asgn.src.borrow().is_hole() {
204                let and_guard = ir::Guard::port(Rc::clone(&asgn.src));
205                *asgn.guard &= and_guard;
206                asgn.src = signal_on.borrow().get("out");
207            }
208        });
209
210        // replace reads from a hole with the value in the map
211        for asgn in &mut assignments {
212            asgn.guard.for_each(&mut |port| {
213                if port.borrow().is_hole() {
214                    Some(map[&port.borrow().canonical()].1.clone())
215                } else {
216                    None
217                }
218            })
219        }
220        comp.continuous_assignments = assignments;
221
222        // remove all groups
223        comp.get_groups_mut().clear();
224        comp.get_static_groups_mut().clear();
225
226        // remove group from control
227        Ok(Action::change(ir::Control::empty()))
228    }
229}