calyx_opt/passes_experimental/
register_unsharing.rs

1//! Pass to unshare registers by analyzing the live ranges of values stored
2//! within them.
3use crate::analysis::reaching_defns::{
4    GroupOrInvoke, ReachingDefinitionAnalysis,
5};
6use crate::traversal::{Action, Named, VisResult, Visitor};
7use calyx_ir::{self as ir, Builder, LibrarySignatures, rewriter};
8use std::collections::HashMap;
9
10/// Unsharing registers reduces the amount of multiplexers used in the final design, trading them
11/// off for more memory.
12///
13/// A register use is said to be shared if it is used to store multiple, non-overlapping values in
14/// it. Unsharing, then, is the process of identifying such usages of registers and generating
15/// new registers to store non-overlapping values. For example, the following program:
16///
17/// ```
18/// let x = 1;
19/// x = x + 2;
20/// x = x + 3
21/// ```
22///
23/// Can be rewritten as:
24/// ```
25/// let x = 1;
26/// let y = x + 2;
27/// let z = y + 3;
28/// ```
29///
30/// On the other hand, the following use of a register cannot be unshared:
31/// ```
32/// let x = 0;
33/// for i in 0..10 {
34///   x = x + 1;
35/// }
36/// ```
37#[derive(Default)]
38pub struct RegisterUnsharing {
39    bookkeeper: Bookkeeper,
40}
41
42impl Named for RegisterUnsharing {
43    fn name() -> &'static str {
44        "register-unsharing"
45    }
46
47    fn description() -> &'static str {
48        "Split apart shared values into separate registers"
49    }
50}
51
52type RewriteMap = HashMap<ir::Id, rewriter::RewriteMap<ir::Cell>>;
53
54// A struct for managing the overlapping analysis and rewrite information
55#[derive(Default)]
56struct Bookkeeper {
57    analysis: ReachingDefinitionAnalysis,
58    widths: HashMap<ir::Id, u64>,
59    invoke_map: RewriteMap,
60}
61
62impl Bookkeeper {
63    // Create a new bookkeeper from the component
64    fn new(comp: &ir::Component) -> Self {
65        // width map is needed to create new registers with the proper widths
66        let widths = comp
67            .cells
68            .iter()
69            .filter_map(|c| {
70                if let ir::CellType::Primitive { name, .. } =
71                    &c.borrow().prototype
72                {
73                    if name == "std_reg" {
74                        if let Some(in_port) = c.borrow().find("in") {
75                            return Some((
76                                c.borrow().name(),
77                                in_port.borrow().width,
78                            ));
79                        }
80                    }
81                }
82                None
83            })
84            .collect();
85
86        Self {
87            widths,
88            analysis: ReachingDefinitionAnalysis::new(&comp.control.borrow()),
89            invoke_map: HashMap::new(),
90        }
91    }
92
93    /// This method takes the reaching definition analysis and uses it to
94    /// determine the set of of overlapping definitions for each register.
95    /// Registers may be split into X registers where X is the number of sets in
96    /// the overlap calculation for that register.
97    ///
98    /// For registers with more than one set (i.e. those which have
99    /// non-overlapping subsets of definitions) this method generates a new
100    /// register name, creates the new register, and associates the new name and
101    /// old name with a vector of location ids (group/invoke stmt). This tuple
102    /// can then be used to rewrite the old name into the new name in the
103    /// corresponding locations.
104    fn create_new_regs(
105        &mut self,
106        builder: &mut Builder,
107    ) -> Vec<(ir::Id, ir::Id, Vec<GroupOrInvoke>)> {
108        let overlap = self
109            .analysis
110            .calculate_overlap(builder.component.continuous_assignments.iter());
111
112        let mut rename_list = vec![];
113
114        for (name, sets) in &overlap {
115            if sets.len() > 1 {
116                for defs in &sets[1..] {
117                    let new_name = builder
118                        .add_primitive(
119                            format!("unshr_{name}"),
120                            "std_reg",
121                            &[*self.widths.get(name).unwrap()],
122                        )
123                        .borrow()
124                        .name();
125                    rename_list.push((
126                        new_name,
127                        *name,
128                        defs.iter()
129                            .map(|(_, groupname)| groupname.clone())
130                            .collect(),
131                    ));
132                }
133            }
134        }
135        rename_list
136    }
137
138    fn rename(
139        &mut self,
140        comp: &mut ir::Component,
141        rename_list: &[(ir::Id, ir::Id, Vec<GroupOrInvoke>)],
142    ) {
143        let mut grp_map: RewriteMap = HashMap::new();
144        let mut invoke_map: RewriteMap = HashMap::new();
145        for (new_name, old_name, grouplist) in rename_list {
146            for group_or_invoke in grouplist {
147                let name = *old_name;
148                let cell = comp.find_cell(*new_name).unwrap();
149                match group_or_invoke {
150                    GroupOrInvoke::Group(group) => {
151                        grp_map.entry(*group).or_default().insert(name, cell);
152                    }
153                    GroupOrInvoke::Invoke(invoke) => {
154                        invoke_map
155                            .entry(*invoke)
156                            .or_default()
157                            .insert(name, cell);
158                    }
159                }
160            }
161        }
162
163        for (grp, rename_cells) in grp_map {
164            let group_ref = comp.find_group(grp).unwrap();
165            let mut group = group_ref.borrow_mut();
166            let rewriter = ir::Rewriter {
167                cell_map: rename_cells,
168                ..Default::default()
169            };
170            group
171                .assignments
172                .iter_mut()
173                .for_each(|assign| assign.for_each_port(|p| rewriter.get(p)));
174        }
175
176        self.invoke_map = invoke_map;
177    }
178}
179
180impl Visitor for RegisterUnsharing {
181    fn start(
182        &mut self,
183        comp: &mut ir::Component,
184        sigs: &LibrarySignatures,
185        _comps: &[ir::Component],
186    ) -> VisResult {
187        self.bookkeeper = Bookkeeper::new(comp);
188        let mut builder = Builder::new(comp, sigs);
189        // Build a rename list
190        let rename_list = self.bookkeeper.create_new_regs(&mut builder);
191        // Perform the structural renaming.
192        self.bookkeeper.rename(comp, &rename_list);
193        Ok(Action::Continue)
194    }
195
196    fn invoke(
197        &mut self,
198        invoke: &mut ir::Invoke,
199        _comp: &mut ir::Component,
200        _sigs: &LibrarySignatures,
201        _comps: &[ir::Component],
202    ) -> VisResult {
203        let book = &mut self.bookkeeper;
204
205        if let Some(name) = book.analysis.meta.fetch_label(invoke) {
206            // only do rewrites if there is actually rewriting to do
207            if let Some(rename_vec) = book.invoke_map.get_mut(name) {
208                let cell_map = std::mem::take(rename_vec);
209                let rewriter = ir::Rewriter {
210                    cell_map,
211                    ..Default::default()
212                };
213                rewriter.rewrite_invoke(invoke);
214                *rename_vec = rewriter.cell_map;
215            }
216        }
217
218        Ok(Action::Continue)
219    }
220
221    fn static_invoke(
222        &mut self,
223        invoke: &mut ir::StaticInvoke,
224        _comp: &mut ir::Component,
225        _sigs: &LibrarySignatures,
226        _comps: &[ir::Component],
227    ) -> VisResult {
228        let book = &mut self.bookkeeper;
229
230        if let Some(name) = book.analysis.meta.fetch_label_static(invoke) {
231            // only do rewrites if there is actually rewriting to do
232            if let Some(rename_vec) = book.invoke_map.get_mut(name) {
233                let cell_map = std::mem::take(rename_vec);
234                let rewriter = ir::Rewriter {
235                    cell_map,
236                    ..Default::default()
237                };
238                rewriter.rewrite_static_invoke(invoke);
239                *rename_vec = rewriter.cell_map;
240            }
241        }
242
243        Ok(Action::Continue)
244    }
245}