calyx_opt/passes/
component_iniliner.rs

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