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                    && name == "std_reg"
73                    && let Some(in_port) = c.borrow().find("in")
74                {
75                    return Some((c.borrow().name(), in_port.borrow().width));
76                }
77                None
78            })
79            .collect();
80
81        Self {
82            widths,
83            analysis: ReachingDefinitionAnalysis::new(&comp.control.borrow()),
84            invoke_map: HashMap::new(),
85        }
86    }
87
88    /// This method takes the reaching definition analysis and uses it to
89    /// determine the set of of overlapping definitions for each register.
90    /// Registers may be split into X registers where X is the number of sets in
91    /// the overlap calculation for that register.
92    ///
93    /// For registers with more than one set (i.e. those which have
94    /// non-overlapping subsets of definitions) this method generates a new
95    /// register name, creates the new register, and associates the new name and
96    /// old name with a vector of location ids (group/invoke stmt). This tuple
97    /// can then be used to rewrite the old name into the new name in the
98    /// corresponding locations.
99    fn create_new_regs(
100        &mut self,
101        builder: &mut Builder,
102    ) -> Vec<(ir::Id, ir::Id, Vec<GroupOrInvoke>)> {
103        let overlap = self
104            .analysis
105            .calculate_overlap(builder.component.continuous_assignments.iter());
106
107        let mut rename_list = vec![];
108
109        for (name, sets) in &overlap {
110            if sets.len() > 1 {
111                for defs in &sets[1..] {
112                    let new_name = builder
113                        .add_primitive(
114                            format!("unshr_{name}"),
115                            "std_reg",
116                            &[*self.widths.get(name).unwrap()],
117                        )
118                        .borrow()
119                        .name();
120                    rename_list.push((
121                        new_name,
122                        *name,
123                        defs.iter()
124                            .map(|(_, groupname)| groupname.clone())
125                            .collect(),
126                    ));
127                }
128            }
129        }
130        rename_list
131    }
132
133    fn rename(
134        &mut self,
135        comp: &mut ir::Component,
136        rename_list: &[(ir::Id, ir::Id, Vec<GroupOrInvoke>)],
137    ) {
138        let mut grp_map: RewriteMap = HashMap::new();
139        let mut invoke_map: RewriteMap = HashMap::new();
140        for (new_name, old_name, grouplist) in rename_list {
141            for group_or_invoke in grouplist {
142                let name = *old_name;
143                let cell = comp.find_cell(*new_name).unwrap();
144                match group_or_invoke {
145                    GroupOrInvoke::Group(group) => {
146                        grp_map.entry(*group).or_default().insert(name, cell);
147                    }
148                    GroupOrInvoke::Invoke(invoke) => {
149                        invoke_map
150                            .entry(*invoke)
151                            .or_default()
152                            .insert(name, cell);
153                    }
154                }
155            }
156        }
157
158        for (grp, rename_cells) in grp_map {
159            let group_ref = comp.find_group(grp).unwrap();
160            let mut group = group_ref.borrow_mut();
161            let rewriter = ir::Rewriter {
162                cell_map: rename_cells,
163                ..Default::default()
164            };
165            group
166                .assignments
167                .iter_mut()
168                .for_each(|assign| assign.for_each_port(|p| rewriter.get(p)));
169        }
170
171        self.invoke_map = invoke_map;
172    }
173}
174
175impl Visitor for RegisterUnsharing {
176    fn start(
177        &mut self,
178        comp: &mut ir::Component,
179        sigs: &LibrarySignatures,
180        _comps: &[ir::Component],
181    ) -> VisResult {
182        self.bookkeeper = Bookkeeper::new(comp);
183        let mut builder = Builder::new(comp, sigs);
184        // Build a rename list
185        let rename_list = self.bookkeeper.create_new_regs(&mut builder);
186        // Perform the structural renaming.
187        self.bookkeeper.rename(comp, &rename_list);
188        Ok(Action::Continue)
189    }
190
191    fn invoke(
192        &mut self,
193        invoke: &mut ir::Invoke,
194        _comp: &mut ir::Component,
195        _sigs: &LibrarySignatures,
196        _comps: &[ir::Component],
197    ) -> VisResult {
198        let book = &mut self.bookkeeper;
199
200        if let Some(name) = book.analysis.meta.fetch_label(invoke) {
201            // only do rewrites if there is actually rewriting to do
202            if let Some(rename_vec) = book.invoke_map.get_mut(name) {
203                let cell_map = std::mem::take(rename_vec);
204                let rewriter = ir::Rewriter {
205                    cell_map,
206                    ..Default::default()
207                };
208                rewriter.rewrite_invoke(invoke);
209                *rename_vec = rewriter.cell_map;
210            }
211        }
212
213        Ok(Action::Continue)
214    }
215
216    fn static_invoke(
217        &mut self,
218        invoke: &mut ir::StaticInvoke,
219        _comp: &mut ir::Component,
220        _sigs: &LibrarySignatures,
221        _comps: &[ir::Component],
222    ) -> VisResult {
223        let book = &mut self.bookkeeper;
224
225        if let Some(name) = book.analysis.meta.fetch_label_static(invoke) {
226            // only do rewrites if there is actually rewriting to do
227            if let Some(rename_vec) = book.invoke_map.get_mut(name) {
228                let cell_map = std::mem::take(rename_vec);
229                let rewriter = ir::Rewriter {
230                    cell_map,
231                    ..Default::default()
232                };
233                rewriter.rewrite_static_invoke(invoke);
234                *rename_vec = rewriter.cell_map;
235            }
236        }
237
238        Ok(Action::Continue)
239    }
240}