calyx_opt/passes/
component_inliner.rs

1use crate::analysis;
2use crate::traversal::{
3    Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult,
4    Visitor,
5};
6use calyx_ir::{self as ir, GetAttributes, LibrarySignatures, RRC, rewriter};
7use calyx_utils::Error;
8use ir::Nothing;
9use itertools::Itertools;
10use linked_hash_map::LinkedHashMap;
11use std::collections::{HashMap, HashSet};
12use std::rc::Rc;
13
14/// Inlines all sub-components marked with the `@inline` attribute.
15/// Cannot inline components when they:
16///   1. Are primitives
17///   2. Are invoked structurally
18///   3. Invoked using `invoke`-`with` statements
19///
20/// For each component that needs to be inlined, we need to:
21///   1. Inline all cells defined by that instance.
22///   2. Inline all groups defined by that instance.
23///   3. Inline the control program for every `invoke` statement referring to the
24///      instance.
25pub struct ComponentInliner {
26    /// Force inlining of all components. Parsed from the command line.
27    always_inline: bool,
28    /// Generate new_fsms for the componnent we generate. Helpful if you don't
29    /// want the fsms to get too many states
30    new_fsms: bool,
31    /// Map from the name of an instance to its associated control program.
32    control_map: HashMap<ir::Id, ir::Control>,
33    /// Mapping for ports on cells that have been inlined.
34    interface_rewrites: rewriter::PortRewriteMap,
35    /// Cells that have been inlined. We retain these so that references within
36    /// the control program of the parent are valid.
37    inlined_cells: Vec<RRC<ir::Cell>>,
38}
39
40impl Named for ComponentInliner {
41    fn name() -> &'static str {
42        "inline"
43    }
44
45    fn description() -> &'static str {
46        "inline all component instances marked with @inline attribute"
47    }
48
49    fn opts() -> Vec<PassOpt> {
50        vec![
51            PassOpt::new(
52                "always",
53                "Attempt to inline all components into the main component",
54                ParseVal::Bool(false),
55                PassOpt::parse_bool,
56            ),
57            PassOpt::new(
58                "new-fsms",
59                "Instantiate new FSM for each inlined component",
60                ParseVal::Bool(false),
61                PassOpt::parse_bool,
62            ),
63        ]
64    }
65}
66
67impl ComponentInliner {
68    /// Equivalent to a default method but not automatically derived because
69    /// it conflicts with the autogeneration of `ConstructVisitor`.
70    fn new(always_inline: bool, new_fsms: bool) -> Self {
71        ComponentInliner {
72            always_inline,
73            new_fsms,
74            control_map: HashMap::default(),
75            interface_rewrites: LinkedHashMap::default(),
76            inlined_cells: Vec::default(),
77        }
78    }
79}
80
81impl ConstructVisitor for ComponentInliner {
82    fn from(ctx: &ir::Context) -> calyx_utils::CalyxResult<Self>
83    where
84        Self: Sized,
85    {
86        let opts = Self::get_opts(ctx);
87        Ok(ComponentInliner::new(
88            opts[&"always"].bool(),
89            opts[&"new-fsms"].bool(),
90        ))
91    }
92
93    fn clear_data(&mut self) {
94        *self = ComponentInliner::new(self.always_inline, self.new_fsms);
95    }
96}
97
98impl ComponentInliner {
99    /// Inline a cell definition into the component associated with the `builder`.
100    fn inline_cell(
101        builder: &mut ir::Builder,
102        cell_ref: &RRC<ir::Cell>,
103    ) -> (ir::Id, RRC<ir::Cell>) {
104        let cell = cell_ref.borrow();
105        let cn = cell.name();
106        let new_cell = match &cell.prototype {
107            ir::CellType::Primitive {
108                name,
109                param_binding,
110                ..
111            } => builder.add_primitive(
112                cn,
113                *name,
114                &param_binding.iter().map(|(_, v)| *v).collect_vec(),
115            ),
116            ir::CellType::Component { name } => {
117                builder.add_component(cn, *name, cell.get_signature())
118            }
119            ir::CellType::Constant { val, width } => {
120                builder.add_constant(*val, *width)
121            }
122            ir::CellType::ThisComponent => unreachable!(),
123        };
124        (cn, new_cell)
125    }
126
127    /// Rewrite assignments using a [CellMap], [PortMap], and an optional new group.
128    /// Attempts the following rewrites in order:
129    /// 1. Using the [CellMap] to get the same port on a new [Cell].
130    /// 2. Using the [PortMap] to a new [Port].
131    /// 3. Using `new_group` to rewrite use of a group hole if the port is a hole.
132    fn rewrite_assigns(
133        assigns: &mut [ir::Assignment<Nothing>],
134        port_rewrite: &ir::Rewriter,
135        new_group: Option<&RRC<ir::Group>>,
136    ) {
137        assigns.iter_mut().for_each(|assign| {
138            assign.for_each_port(|port| {
139                port_rewrite.get(port).or_else(|| {
140                    if let Some(grp) = new_group {
141                        if port.borrow().is_hole() {
142                            return Some(grp.borrow().get(&port.borrow().name));
143                        }
144                    }
145                    None
146                })
147            });
148        })
149    }
150
151    /// Rewrite assignments using a [CellMap], [PortMap], and an optional new group.
152    /// Attempts the following rewrites in order:
153    /// 1. Using the [CellMap] to get the same port on a new [Cell].
154    /// 2. Using the [PortMap] to a new [Port].
155    /// 3. Using `new_group` to rewrite use of a group hole if the port is a hole.
156    fn rewrite_assigns_static(
157        assigns: &mut [ir::Assignment<ir::StaticTiming>],
158        port_rewrite: &ir::Rewriter,
159        new_group: Option<&RRC<ir::StaticGroup>>,
160    ) {
161        assigns.iter_mut().for_each(|assign| {
162            assign.for_each_port(|port| {
163                port_rewrite.get(port).or_else(|| {
164                    if let Some(grp) = new_group {
165                        if port.borrow().is_hole() {
166                            return Some(grp.borrow().get(&port.borrow().name));
167                        }
168                    }
169                    None
170                })
171            });
172        })
173    }
174
175    /// Rewrites vec based on self.interface_rewrites Used as a helper function
176    /// for rewrite_invoke_ports
177    fn rewrite_vec(&self, v: &mut [(ir::Id, RRC<ir::Port>)]) {
178        v.iter_mut().for_each(|(_, port)| {
179            let key = port.borrow().canonical();
180            if let Some(rewrite) = self.interface_rewrites.get(&key) {
181                *port = Rc::clone(rewrite);
182            }
183        })
184    }
185
186    /// Rewrites the input/output ports of the invoke based on self.interface_rewrites
187    fn rewrite_invoke_ports(&self, invoke: &mut ir::Invoke) {
188        self.rewrite_vec(&mut invoke.inputs);
189        self.rewrite_vec(&mut invoke.outputs);
190    }
191
192    /// Inline a group definition from a component into the component associated
193    /// with the `builder`.
194    fn inline_group(
195        builder: &mut ir::Builder,
196        port_rewrite: &ir::Rewriter,
197        gr: &RRC<ir::Group>,
198    ) -> (ir::Id, RRC<ir::Group>) {
199        let group = gr.borrow();
200        let new_group = builder.add_group(group.name());
201        new_group.borrow_mut().attributes = group.attributes.clone();
202
203        // Rewrite assignments
204        let mut asgns = group.assignments.clone();
205        Self::rewrite_assigns(&mut asgns, port_rewrite, Some(&new_group));
206        new_group.borrow_mut().assignments = asgns;
207        (group.name(), new_group)
208    }
209
210    /// Inline a group definition from a component into the component associated
211    /// with the `builder`.
212    fn inline_static_group(
213        builder: &mut ir::Builder,
214        port_rewrite: &ir::Rewriter,
215        gr: &RRC<ir::StaticGroup>,
216    ) -> (ir::Id, RRC<ir::StaticGroup>) {
217        let group = gr.borrow();
218        let new_group =
219            builder.add_static_group(group.name(), group.get_latency());
220        new_group.borrow_mut().attributes = group.attributes.clone();
221
222        // Rewrite assignments
223        let mut asgns = group.assignments.clone();
224        Self::rewrite_assigns_static(
225            &mut asgns,
226            port_rewrite,
227            Some(&new_group),
228        );
229        new_group.borrow_mut().assignments = asgns;
230        (group.name(), new_group)
231    }
232
233    /// Inline a group definition from a component into the component associated
234    /// with the `builder`.
235    fn inline_comb_group(
236        builder: &mut ir::Builder,
237        port_rewrite: &ir::Rewriter,
238        gr: &RRC<ir::CombGroup>,
239    ) -> (ir::Id, RRC<ir::CombGroup>) {
240        let group = gr.borrow();
241        let new_group = builder.add_comb_group(group.name());
242        new_group.borrow_mut().attributes = group.attributes.clone();
243
244        // Rewrite assignments
245        let mut asgns = group.assignments.clone();
246        Self::rewrite_assigns(&mut asgns, port_rewrite, None);
247        new_group.borrow_mut().assignments = asgns;
248        (group.name(), new_group)
249    }
250
251    /// Adds wires that can hold the values written to various output ports.
252    fn inline_interface(
253        builder: &mut ir::Builder,
254        comp: &ir::Component,
255        name: ir::Id,
256    ) -> rewriter::PortRewriteMap {
257        // For each output port, generate a wire that will store its value
258        comp.signature
259            .borrow()
260            .ports
261            .iter()
262            .map(|port_ref| {
263                let port = port_ref.borrow();
264                let wire_name = format!("{}_{}", name, port.name);
265                let wire_ref =
266                    builder.add_primitive(wire_name, "std_wire", &[port.width]);
267                let wire = wire_ref.borrow();
268                let pn = match port.direction {
269                    ir::Direction::Input => "in",
270                    ir::Direction::Output => "out",
271                    ir::Direction::Inout => unreachable!(),
272                };
273                (port.canonical(), wire.get(pn))
274            })
275            .collect()
276    }
277
278    /// Inline component `comp` into the parent component attached to `builder`.
279    /// Returns:
280    /// 1. The control program associated with the component being inlined,
281    ///    rewritten for the specific instance.
282    /// 2. A [PortMap] (in form of an iterator) to be used in the parent component to rewrite uses
283    ///    of interface ports of the component being inlined.
284    fn inline_component(
285        builder: &mut ir::Builder,
286        mut cell_map: rewriter::RewriteMap<ir::Cell>,
287        comp: &ir::Component,
288        name: ir::Id,
289    ) -> (
290        ir::Control,
291        impl Iterator<Item = (ir::Canonical, RRC<ir::Port>)>,
292    ) {
293        // For each cell in the component, create a new cell in the parent
294        // of the same type and build a rewrite map using it.
295        cell_map.extend(comp.cells.iter().filter_map(|cell_ref| {
296            if !cell_ref.borrow().is_reference() {
297                Some(Self::inline_cell(builder, cell_ref))
298            } else {
299                None
300            }
301        }));
302
303        // Rewrites to inline the interface.
304        let interface_map = Self::inline_interface(builder, comp, name);
305        let mut rewrite = ir::Rewriter {
306            cell_map,
307            port_map: interface_map,
308            ..Default::default()
309        };
310
311        // For each group, create a new group and rewrite all assignments within
312        // it using the `rewrite_map`.
313        rewrite.group_map = comp
314            .get_groups()
315            .iter()
316            .map(|gr| Self::inline_group(builder, &rewrite, gr))
317            .collect();
318        rewrite.static_group_map = comp
319            .get_static_groups()
320            .iter()
321            .map(|gr| Self::inline_static_group(builder, &rewrite, gr))
322            .collect();
323        rewrite.comb_group_map = comp
324            .comb_groups
325            .iter()
326            .map(|gr| Self::inline_comb_group(builder, &rewrite, gr))
327            .collect();
328
329        // Rewrite continuous assignments
330        let mut cont_assigns = comp.continuous_assignments.clone();
331        Self::rewrite_assigns(&mut cont_assigns, &rewrite, None);
332        builder
333            .component
334            .continuous_assignments
335            .extend(cont_assigns);
336
337        // Generate a control program associated with this instance
338        let mut con = ir::Cloner::control(&comp.control.borrow());
339        rewrite.rewrite_control(&mut con);
340
341        // Generate interface map for use in the parent cell.
342        // Return as an iterator because it's immediately merged into the global rewrite map.
343        let rev_interface_map =
344            rewrite.port_map.into_iter().map(move |(cp, pr)| {
345                let ir::Canonical { port: p, .. } = cp;
346                let port = pr.borrow();
347                let np = match port.name.id.as_str() {
348                    "in" => "out",
349                    "out" => "in",
350                    _ => unreachable!(),
351                };
352                (
353                    ir::Canonical::new(name, p),
354                    port.cell_parent().borrow().get(np),
355                )
356            });
357
358        (con, rev_interface_map)
359    }
360}
361
362impl Visitor for ComponentInliner {
363    // Inlining should proceed bottom-up
364    fn iteration_order() -> Order {
365        Order::Post
366    }
367
368    fn start(
369        &mut self,
370        comp: &mut ir::Component,
371        sigs: &LibrarySignatures,
372        comps: &[ir::Component],
373    ) -> VisResult {
374        // Separate out cells that need to be inlined.
375        let (inline_cells, cells): (Vec<_>, Vec<_>) =
376            comp.cells.drain().partition(|cr| {
377                let cell = cr.borrow();
378                // If forced inlining is enabled, attempt to inline every
379                // component.
380                if self.always_inline {
381                    cell.is_component()
382                } else {
383                    cell.get_attribute(ir::BoolAttr::Inline).is_some()
384                }
385            });
386        comp.cells.append(cells.into_iter());
387
388        // Early exit if there is nothing to inline
389        if inline_cells.is_empty() {
390            return Ok(Action::Stop);
391        }
392
393        // Use analysis to get all bindings for invokes and filter out bindings
394        // for inlined cells.
395        let invoke_bindings: HashMap<ir::Id, _> =
396            analysis::ControlPorts::<true>::from(&*comp.control.borrow())
397                .get_all_bindings()
398                .into_iter()
399                .filter(|(instance, _)| {
400                    inline_cells.iter().any(|c| c.borrow().name() == instance)
401                })
402                .collect();
403
404        // If any invoke has more than one binding, error out:
405        for (instance, bindings) in &invoke_bindings {
406            if bindings.len() > 1 {
407                let bindings_str = bindings
408                    .iter()
409                    .map(|(cells, ports)| {
410                        format!(
411                            "[{}]({})",
412                            cells
413                                .iter()
414                                .map(|(c, cell)| format!(
415                                    "{c}={}",
416                                    cell.borrow().name()
417                                ))
418                                .join(", "),
419                            ports
420                                .iter()
421                                .map(|(p, port)| format!(
422                                    "{p}={}",
423                                    port.borrow().canonical()
424                                ))
425                                .join(", ")
426                        )
427                    })
428                    .join("\n");
429                return Err(Error::pass_assumption(
430                    Self::name(),
431                    format!(
432                        "Instance `{}.{instance}` invoked with multiple parameters (currently unsupported):\n{bindings_str}",
433                        comp.name,
434                    ),
435                ));
436            }
437        }
438
439        // Mapping from component name to component definition
440        let comp_map = comps
441            .iter()
442            .map(|comp| (&comp.name, comp))
443            .collect::<HashMap<_, _>>();
444
445        // Rewrites for the interface ports of inlined cells.
446        let mut interface_rewrites: rewriter::PortRewriteMap =
447            LinkedHashMap::new();
448        // Track names of cells that were inlined.
449        let mut inlined_cells = HashSet::new();
450        let mut builder = ir::Builder::new(comp, sigs);
451        for cell_ref in &inline_cells {
452            let cell = cell_ref.borrow();
453            // Error if the cell is not a component
454            if !cell.is_component() {
455                let msg = format!(
456                    "Cannot inline `{}`. It is a instance of primitive: `{}`",
457                    cell.name(),
458                    cell.type_name()
459                        .unwrap_or_else(|| ir::Id::from("constant"))
460                );
461
462                return Err(Error::pass_assumption(Self::name(), msg)
463                    .with_pos(&cell.attributes));
464            }
465
466            let comp_name = cell.type_name().unwrap();
467            let cell_map =
468                if let Some(binding) = &invoke_bindings.get(&cell.name()) {
469                    let (cell_binds, _) = &binding[0];
470                    cell_binds.iter().map(|(k, v)| (*k, v.clone())).collect()
471                } else {
472                    log::info!(
473                        "no binding for `{}` which means instance is unused",
474                        cell.name()
475                    );
476                    HashMap::new()
477                };
478            let (control, rewrites) = Self::inline_component(
479                &mut builder,
480                cell_map,
481                comp_map[&comp_name],
482                cell.name(),
483            );
484            interface_rewrites.extend(rewrites);
485            self.control_map.insert(cell.name(), control);
486            inlined_cells.insert(cell.name());
487        }
488
489        // XXX: This is unneccessarily iterate over the newly inlined groups.
490        // Rewrite all assignment in the component to use interface wires
491        // from the inlined instances and check if the `go` or `done` ports
492        // on any of the instances was used for structural invokes.
493        builder.component.for_each_assignment(|assign| {
494            assign.for_each_port(|pr| {
495                let port = &pr.borrow();
496                let np = interface_rewrites.get(&port.canonical());
497                if np.is_some() && (port.name == "go" || port.name == "done") {
498                    panic!(
499                        "Cannot inline instance. It is structurally structurally invoked: `{}`",
500                        port.cell_parent().borrow().name(),
501                    );
502                }
503                np.cloned()
504            });
505        });
506
507        builder.component.for_each_static_assignment(|assign| {
508            assign.for_each_port(|pr| {
509                let port = &pr.borrow();
510                let np = interface_rewrites.get(&port.canonical());
511                if np.is_some() && (port.name == "go" || port.name == "done") {
512                    panic!(
513                        "Cannot inline instance. It is structurally structurally invoked: `{}`",
514                        port.cell_parent().borrow().name(),
515                    );
516                }
517                np.cloned()
518            });
519        });
520
521        // Ensure that all invokes use the same parameters and inline the parameter assignments.
522        for (instance, mut bindings) in invoke_bindings {
523            let Some((_, binding)) = bindings.pop() else {
524                unreachable!("Instance binding is empty");
525            };
526            let mut assigns = binding
527                .into_iter()
528                .filter(|(_, pr)| {
529                    let port = pr.borrow();
530                    // Skip clk and reset ports
531                    !port.attributes.has(ir::BoolAttr::Clk)
532                        && !port.attributes.has(ir::BoolAttr::Reset)
533                })
534                .map(|(name, param)| {
535                    let port = Rc::clone(
536                        &interface_rewrites
537                            [&ir::Canonical::new(instance, name)],
538                    );
539                    // The parameter can refer to port on a cell that has been
540                    // inlined.
541                    let name = param.borrow().canonical();
542                    let new_param = interface_rewrites
543                        .get(&name)
544                        .map(Rc::clone)
545                        .unwrap_or(param);
546                    let dir = port.borrow().direction.clone();
547                    match dir {
548                        ir::Direction::Input => builder.build_assignment(
549                            port,
550                            new_param,
551                            ir::Guard::True,
552                        ),
553                        ir::Direction::Output => builder.build_assignment(
554                            new_param,
555                            port,
556                            ir::Guard::True,
557                        ),
558                        ir::Direction::Inout => unreachable!(),
559                    }
560                })
561                .collect_vec();
562            builder
563                .component
564                .continuous_assignments
565                .append(&mut assigns);
566        }
567
568        self.interface_rewrites = interface_rewrites;
569        // Save inlined cells so that references within the parent control
570        // program remain valid.
571        self.inlined_cells = inline_cells;
572
573        Ok(Action::Continue)
574    }
575
576    fn start_if(
577        &mut self,
578        s: &mut ir::If,
579        _comp: &mut ir::Component,
580        _sigs: &LibrarySignatures,
581        _comps: &[ir::Component],
582    ) -> VisResult {
583        let name = &s.port.borrow().canonical();
584        if let Some(new_port) = self.interface_rewrites.get(name) {
585            s.port = Rc::clone(new_port);
586        }
587        Ok(Action::Continue)
588    }
589
590    fn start_while(
591        &mut self,
592        s: &mut ir::While,
593        _comp: &mut ir::Component,
594        _sigs: &LibrarySignatures,
595        _comps: &[ir::Component],
596    ) -> VisResult {
597        let name = &s.port.borrow().canonical();
598        if let Some(new_port) = self.interface_rewrites.get(name) {
599            s.port = Rc::clone(new_port);
600        }
601        Ok(Action::Continue)
602    }
603
604    fn invoke(
605        &mut self,
606        s: &mut ir::Invoke,
607        _comp: &mut ir::Component,
608        _sigs: &LibrarySignatures,
609        _comps: &[ir::Component],
610    ) -> VisResult {
611        // Regardless of whether the associated instance has been inlined,
612        // we still may need to rewrite the input/output ports
613        self.rewrite_invoke_ports(s);
614
615        // If the associated instance has been inlined, replace the invoke with
616        // its control program.
617        let cell = s.comp.borrow();
618        if let Some(con) = self.control_map.get_mut(&cell.name()) {
619            if self.new_fsms {
620                con.get_mut_attributes().insert(ir::BoolAttr::NewFSM, 1);
621            }
622            Ok(Action::change(ir::Cloner::control(con)))
623        } else {
624            Ok(Action::Continue)
625        }
626    }
627}