calyx_opt/passes/
dead_cell_removal.rs

1use crate::traversal::{Action, Named, VisResult, Visitor};
2use calyx_ir::{self as ir};
3use std::collections::HashSet;
4use std::iter;
5
6/// Warn if dead cell removal loops more than this number of times
7const LOOP_THRESHOLD: u64 = 5;
8
9/// Removes unused cells from components.
10#[derive(Default)]
11pub struct DeadCellRemoval {
12    /// Names of cells that have been read from.
13    all_reads: HashSet<ir::Id>,
14}
15
16impl Named for DeadCellRemoval {
17    fn name() -> &'static str {
18        "dead-cell-removal"
19    }
20
21    fn description() -> &'static str {
22        "removes cells that are never used inside a component"
23    }
24}
25
26impl DeadCellRemoval {
27    /// Retain the write if the destination is a hole or if the parent of the
28    /// destination is read from.
29    fn retain_write<T: Clone + Eq + ToString>(
30        &self,
31        wire_reads: &HashSet<ir::Id>,
32        asgn: &ir::Assignment<T>,
33    ) -> bool {
34        let dst = asgn.dst.borrow();
35        if dst.is_hole() {
36            true
37        } else {
38            let parent = &dst.get_parent_name();
39            let out =
40                self.all_reads.contains(parent) || wire_reads.contains(parent);
41            if !out {
42                log::debug!(
43                    "`{}' because `{}' is unused",
44                    ir::Printer::assignment_to_str(asgn),
45                    parent
46                )
47            }
48            out
49        }
50    }
51
52    fn visit_invoke(
53        &mut self,
54        comp: &ir::RRC<ir::Cell>,
55        inputs: &[(ir::Id, ir::RRC<ir::Port>)],
56        outputs: &[(ir::Id, ir::RRC<ir::Port>)],
57        ref_cells: &[(ir::Id, ir::RRC<ir::Cell>)],
58    ) {
59        let cells = inputs
60            .iter()
61            .map(|(_, p)| p)
62            .chain(outputs.iter().map(|(_, p)| p))
63            .map(|p| p.borrow().get_parent_name())
64            .chain(iter::once(comp.borrow().name()))
65            .chain(ref_cells.iter().map(|(_, c)| c.borrow().name()));
66        self.all_reads.extend(cells);
67    }
68}
69
70impl Visitor for DeadCellRemoval {
71    fn start_if(
72        &mut self,
73        s: &mut ir::If,
74        _comp: &mut ir::Component,
75        _sigs: &ir::LibrarySignatures,
76        _comps: &[ir::Component],
77    ) -> VisResult {
78        self.all_reads.insert(s.port.borrow().get_parent_name());
79        Ok(Action::Continue)
80    }
81
82    fn start_static_if(
83        &mut self,
84        s: &mut ir::StaticIf,
85        _comp: &mut ir::Component,
86        _sigs: &ir::LibrarySignatures,
87        _comps: &[ir::Component],
88    ) -> VisResult {
89        self.all_reads.insert(s.port.borrow().get_parent_name());
90        Ok(Action::Continue)
91    }
92
93    fn start_while(
94        &mut self,
95        s: &mut ir::While,
96        _comp: &mut ir::Component,
97        _sigs: &ir::LibrarySignatures,
98        _comps: &[ir::Component],
99    ) -> VisResult {
100        self.all_reads.insert(s.port.borrow().get_parent_name());
101        Ok(Action::Continue)
102    }
103
104    fn invoke(
105        &mut self,
106        s: &mut ir::Invoke,
107        _comp: &mut ir::Component,
108        _sigs: &ir::LibrarySignatures,
109        _comps: &[ir::Component],
110    ) -> VisResult {
111        self.visit_invoke(&s.comp, &s.inputs, &s.outputs, &s.ref_cells);
112        Ok(Action::Continue)
113    }
114
115    fn static_invoke(
116        &mut self,
117        s: &mut ir::StaticInvoke,
118        _comp: &mut ir::Component,
119        _sigs: &ir::LibrarySignatures,
120        _comps: &[ir::Component],
121    ) -> VisResult {
122        self.visit_invoke(&s.comp, &s.inputs, &s.outputs, &s.ref_cells);
123        Ok(Action::Continue)
124    }
125
126    fn finish(
127        &mut self,
128        comp: &mut ir::Component,
129        _sigs: &ir::LibrarySignatures,
130        _comps: &[ir::Component],
131    ) -> VisResult {
132        // Add @external cells and ref cells.
133        self.all_reads.extend(
134            comp.cells
135                .iter()
136                .filter(|c| {
137                    let cell = c.borrow();
138                    cell.attributes.get(ir::BoolAttr::External).is_some()
139                        || cell.is_reference()
140                })
141                .map(|c| c.borrow().name()),
142        );
143        // Add component signature
144        self.all_reads.insert(comp.signature.borrow().name());
145
146        // Add all cells that have at least one output read.
147        let mut count = 0;
148        loop {
149            let mut wire_reads = HashSet::new();
150            comp.for_each_assignment(|assign| {
151                assign.for_each_port(|port| {
152                    let port = port.borrow();
153                    if port.direction == ir::Direction::Output {
154                        wire_reads.insert(port.get_parent_name());
155                    }
156                    None
157                });
158            });
159            comp.for_each_static_assignment(|assign| {
160                assign.for_each_port(|port| {
161                    let port = port.borrow();
162                    if port.direction == ir::Direction::Output {
163                        wire_reads.insert(port.get_parent_name());
164                    }
165                    None
166                });
167            });
168
169            // Remove writes to ports on unused cells.
170            for gr in comp.get_groups().iter() {
171                gr.borrow_mut()
172                    .assignments
173                    .retain(|asgn| self.retain_write(&wire_reads, asgn))
174            }
175            // Remove writes to ports on unused cells.
176            for gr in comp.get_static_groups().iter() {
177                gr.borrow_mut()
178                    .assignments
179                    .retain(|asgn| self.retain_write(&wire_reads, asgn))
180            }
181            for cgr in comp.comb_groups.iter() {
182                cgr.borrow_mut()
183                    .assignments
184                    .retain(|asgn| self.retain_write(&wire_reads, asgn))
185            }
186            comp.continuous_assignments
187                .retain(|asgn| self.retain_write(&wire_reads, asgn));
188
189            // Remove unused cells
190            let removed = comp.cells.retain(|c| {
191                let cell = c.borrow();
192                self.all_reads.contains(&cell.name())
193                    || wire_reads.contains(&cell.name())
194            });
195
196            if removed == 0 {
197                break;
198            }
199
200            count += 1;
201        }
202
203        if count >= LOOP_THRESHOLD {
204            log::warn!("{} looped {count} times", Self::name());
205        }
206
207        Ok(Action::Stop)
208    }
209}