calyx_ir/
from_ast.rs

1use super::{
2    Assignment, Attributes, BackendConf, Builder, Cell, CellType, Component,
3    Context, Control, Direction, GetAttributes, Guard, Id, Invoke,
4    LibrarySignatures, Port, PortDef, RESERVED_NAMES, RRC, StaticControl,
5    StaticInvoke, Transition,
6};
7use crate::{Nothing, PortComp, StaticTiming};
8use calyx_frontend::{BoolAttr, NumAttr, Workspace, ast};
9use calyx_utils::{CalyxResult, Error, GPosIdx, WithPos};
10use itertools::Itertools;
11
12use std::collections::{HashMap, HashSet};
13use std::num::NonZeroU64;
14use std::rc::Rc;
15
16/// Context to store the signature information for all defined primitives and
17/// components.
18#[derive(Default)]
19struct SigCtx {
20    /// Mapping from component names to (signature, Option<static_latency>)
21    comp_sigs: HashMap<Id, (Vec<PortDef<u64>>, Option<NonZeroU64>)>,
22
23    /// Mapping from library functions to signatures
24    lib: LibrarySignatures,
25}
26
27#[derive(Debug, Copy, Clone)]
28pub struct AstConversionConfig {
29    pub extend_signatures: bool,
30}
31
32impl Default for AstConversionConfig {
33    fn default() -> Self {
34        Self {
35            extend_signatures: true,
36        }
37    }
38}
39
40// Assumes cell has a name (i.e., not a constant/ThisComponent)
41// uses sig_ctx to check the latency of comp_name, either thru static<n> or
42// @interval(n)
43fn get_comp_latency(
44    sig_ctx: &SigCtx,
45    cell: RRC<Cell>,
46    attrs: &Attributes,
47) -> CalyxResult<Option<NonZeroU64>> {
48    let comp_name = cell
49        .borrow()
50        .type_name()
51        .unwrap_or_else(|| unreachable!("invoked component without a name"));
52    if let Some(prim) = sig_ctx.lib.find_primitive(comp_name) {
53        match prim.latency {
54            Some(val) => Ok(Some(val)),
55            None => {
56                let prim_sig = &prim.signature;
57                // We can just look at any go port, since if there is an
58                // @interval(n), it must be the same for all ports.
59                let interval_value = prim_sig
60                    .iter()
61                    .find(|port_def| port_def.attributes.has(NumAttr::Go))
62                    .and_then(|go_port| {
63                        go_port.attributes.get(NumAttr::Interval)
64                    });
65                // Annoying thing we have to do bc NonZeroU64.
66                match interval_value {
67                    Some(lat) => Ok(NonZeroU64::new(lat)),
68                    None => Ok(None),
69                }
70            }
71        }
72    } else if let Some((comp_sig, latency)) = sig_ctx.comp_sigs.get(&comp_name)
73    {
74        match latency {
75            Some(val) => Ok(Some(*val)),
76            None => {
77                // We can just look at any go port, since if there is an
78                // @interval(n), it must be the same for all ports.
79                let interval_value = comp_sig
80                    .iter()
81                    .find(|port_def| port_def.attributes.has(NumAttr::Go))
82                    .and_then(|go_port| {
83                        go_port.attributes.get(NumAttr::Interval)
84                    });
85                // Annoying thing we have to do bc NonZeroU64.
86                match interval_value {
87                    Some(lat) => Ok(NonZeroU64::new(lat)),
88                    None => Ok(None),
89                }
90            }
91        }
92    } else {
93        return Err(Error::undefined(
94            comp_name,
95            "primitive or component".to_string(),
96        )
97        .with_pos(attrs));
98    }
99}
100
101// Assumes cell has a name (i.e., not a constant/ThisComponent)
102// uses sig_ctx to check the latency of comp_name, but only checks static<n> latency
103fn get_static_latency(
104    sig_ctx: &SigCtx,
105    cell: RRC<Cell>,
106    attrs: &Attributes,
107) -> CalyxResult<Option<NonZeroU64>> {
108    let comp_name = cell
109        .borrow()
110        .type_name()
111        .unwrap_or_else(|| unreachable!("invoked component without a name"));
112    if let Some(prim) = sig_ctx.lib.find_primitive(comp_name) {
113        Ok(prim.latency)
114    } else if let Some((_, latency)) = sig_ctx.comp_sigs.get(&comp_name) {
115        Ok(*latency)
116    } else {
117        return Err(Error::undefined(
118            comp_name,
119            "primitive or component".to_string(),
120        )
121        .with_pos(attrs));
122    }
123}
124
125// assumes cell has a name (i.e., not a constant/ThisComponent)
126// Uses sig_ctx to check whether port_name is a valid port on cell.
127fn check_valid_port(
128    cell: RRC<Cell>,
129    port_name: &Id,
130    attrs: &Attributes,
131    sig_ctx: &SigCtx,
132) -> CalyxResult<()> {
133    let cell_name = cell.borrow().name();
134    let comp_name = cell
135        .borrow()
136        .type_name()
137        .unwrap_or_else(|| unreachable!("invoked component without a name"));
138    let sig_ports: HashSet<_> =
139        if let Some(prim) = sig_ctx.lib.find_primitive(comp_name) {
140            prim.signature
141                .iter()
142                .map(|port_def| port_def.name())
143                .collect()
144        } else if let Some((comp_sigs, _)) = sig_ctx.comp_sigs.get(&comp_name) {
145            comp_sigs.iter().map(|port_def| port_def.name()).collect()
146        } else {
147            return Err(Error::undefined(
148                comp_name,
149                "primitive or component".to_string(),
150            )
151            .with_pos(attrs));
152        };
153    if !sig_ports.contains(port_name) {
154        return Err(Error::malformed_structure(format!(
155            "cell `{cell_name}` (which is an instance of {comp_name}) does not have port named `{port_name}`"
156        ))
157        .with_pos(attrs));
158    };
159    Ok(())
160}
161
162/// Validates a component signature to make sure there are not duplicate ports.
163fn check_signature(pds: &[PortDef<u64>]) -> CalyxResult<()> {
164    let mut ports: HashSet<Id> = HashSet::new();
165    for pd in pds {
166        let name = pd.name();
167        // check for uniqueness
168        match &pd.direction {
169            Direction::Input => {
170                if !ports.contains(&name) {
171                    ports.insert(name);
172                } else {
173                    return Err(Error::already_bound(name, "port".to_string()));
174                }
175            }
176            Direction::Output => {
177                if !ports.contains(&name) {
178                    ports.insert(name);
179                } else {
180                    return Err(Error::already_bound(name, "port".to_string()));
181                }
182            }
183            Direction::Inout => {
184                panic!("Components shouldn't have inout ports.")
185            }
186        }
187    }
188    Ok(())
189}
190
191/// Construct an IR representation using a parsed AST and command line options.
192pub fn ast_to_ir(
193    mut workspace: Workspace,
194    config: AstConversionConfig,
195) -> CalyxResult<Context> {
196    let prims = workspace.lib.signatures().collect_vec();
197    let mut all_names: HashSet<&Id> =
198        HashSet::with_capacity(workspace.components.len() + prims.len());
199
200    let prim_names = prims.iter().map(|p| (&p.name, p.attributes.copy_span()));
201
202    let comp_names = workspace
203        .components
204        .iter()
205        .map(|comp| (&comp.name, comp.attributes.copy_span()));
206
207    for (bound, span) in prim_names.chain(comp_names) {
208        if all_names.contains(bound) {
209            return Err(Error::already_bound(
210                *bound,
211                "component or primitive".to_string(),
212            )
213            .with_pos(&span));
214        }
215        all_names.insert(bound);
216    }
217
218    // Build the signature context
219    let mut sig_ctx = SigCtx {
220        lib: workspace.lib,
221        ..Default::default()
222    };
223
224    // Add declarations to context
225    for comp in workspace
226        .declarations
227        .iter_mut()
228        .chain(workspace.components.iter_mut())
229    {
230        let sig = &mut comp.signature;
231        check_signature(&*sig)?;
232        // extend the signature if the component does not have the @nointerface attribute.
233        if !comp.attributes.has(BoolAttr::NoInterface)
234            && !comp.is_comb
235            && config.extend_signatures
236        {
237            Component::extend_signature(sig);
238        }
239        sig_ctx
240            .comp_sigs
241            .insert(comp.name, (sig.clone(), comp.latency));
242    }
243
244    // building components from `ast::ComponentDef`s to `ir::Component`
245    let comps: Vec<Component> = workspace
246        .components
247        .into_iter()
248        .map(|comp| build_component(comp, &mut sig_ctx, config))
249        .collect::<Result<_, _>>()?;
250
251    // Find the entrypoint for the program.
252    let entrypoint = comps
253        .iter()
254        .find(|c| c.attributes.has(BoolAttr::TopLevel))
255        .or_else(|| comps.iter().find(|c| c.name == "main"))
256        .map(|c| c.name)
257        .ok_or_else(|| Error::misc("No entry point for the program. Program needs to be either mark a component with the \"toplevel\" attribute or define a component named `main`".to_string()))?;
258
259    Ok(Context {
260        components: comps,
261        lib: sig_ctx.lib,
262        bc: BackendConf::default(),
263        entrypoint,
264        extra_opts: vec![],
265        metadata: workspace.metadata,
266        source_info_table: workspace.source_info_table,
267    })
268}
269
270fn validate_component(
271    comp: &ast::ComponentDef,
272    sig_ctx: &SigCtx,
273) -> CalyxResult<()> {
274    let mut cells: HashMap<Id, GPosIdx> = HashMap::new();
275    let mut groups: HashMap<Id, GPosIdx> = HashMap::new();
276
277    for cell in &comp.cells {
278        let attrs = &cell.attributes;
279        if let Some(pos) = cells.get(&cell.name) {
280            let prev =
281                pos.into_option().map(|s| s.format("Previous definition"));
282            return Err(Error::already_bound(cell.name, "cell".to_string())
283                .with_pos(attrs)
284                .with_post_msg(prev));
285        }
286        cells.insert(cell.name, cell.attributes.copy_span());
287
288        let proto_name = cell.prototype.name;
289
290        if sig_ctx.lib.find_primitive(proto_name).is_none()
291            && !sig_ctx.comp_sigs.contains_key(&proto_name)
292        {
293            return Err(Error::undefined(
294                proto_name,
295                "primitive or component".to_string(),
296            )
297            .with_pos(attrs));
298        }
299    }
300
301    for group in &comp.groups {
302        let name = &group.name;
303        let attrs = &group.attributes;
304        if let Some(pos) = groups.get(name) {
305            let prev =
306                pos.into_option().map(|s| s.format("Previous definition"));
307            return Err(Error::already_bound(*name, "group".to_string())
308                .with_pos(attrs)
309                .with_post_msg(prev));
310        }
311        if let Some(pos) = cells.get(name) {
312            let prev =
313                pos.into_option().map(|s| s.format("Previous definition"));
314            return Err(Error::already_bound(*name, "cell".to_string())
315                .with_pos(attrs)
316                .with_post_msg(prev));
317        }
318        if let Some(pos) = cells.get(name) {
319            let prev =
320                pos.into_option().map(|s| s.format("Previous definition"));
321            return Err(Error::already_bound(*name, "cell".to_string())
322                .with_pos(attrs)
323                .with_post_msg(prev));
324        }
325        groups.insert(*name, group.attributes.copy_span());
326    }
327
328    Ok(())
329}
330
331/// Build an `ir::component::Component` using an `frontend::ast::ComponentDef`.
332fn build_component(
333    comp: ast::ComponentDef,
334    sig_ctx: &mut SigCtx,
335    config: AstConversionConfig,
336) -> CalyxResult<Component> {
337    // Validate the component before building it.
338    validate_component(&comp, sig_ctx)?;
339
340    let mut ir_component = Component::new(
341        comp.name,
342        comp.signature,
343        !comp.attributes.has(BoolAttr::NoInterface)
344            && !comp.is_comb
345            && config.extend_signatures,
346        comp.is_comb,
347        // we may change latency from None to Some(inferred latency)
348        // after we iterate thru the control of the Component
349        comp.latency,
350    );
351
352    let mut builder =
353        Builder::new(&mut ir_component, &sig_ctx.lib).not_generated();
354
355    // For each ast::Cell, add a Cell that contains all the
356    // required information.
357    comp.cells
358        .into_iter()
359        .try_for_each(|cell| add_cell(cell, sig_ctx, &mut builder))?;
360
361    comp.groups
362        .into_iter()
363        .try_for_each(|g| add_group(g, &mut builder))?;
364
365    comp.static_groups
366        .into_iter()
367        .try_for_each(|g| add_static_group(g, &mut builder))?;
368
369    comp.fsms
370        .into_iter()
371        .try_for_each(|f| add_fsm(f, &mut builder))?;
372
373    let continuous_assignments =
374        build_assignments(comp.continuous_assignments, &mut builder)?;
375    builder.component.continuous_assignments = continuous_assignments;
376
377    // Build the Control ast using ast::Control.
378    let control =
379        super::rrc(build_control(comp.control, sig_ctx, &mut builder)?);
380
381    builder.component.control = control;
382
383    ir_component.attributes = comp.attributes;
384
385    // Add reserved names to the component's namegenerator so future conflicts
386    // don't occur
387    ir_component
388        .add_names(RESERVED_NAMES.iter().map(|s| Id::from(*s)).collect());
389
390    Ok(ir_component)
391}
392
393///////////////// Cell Construction /////////////////////////
394
395fn add_cell(
396    cell: ast::Cell,
397    sig_ctx: &SigCtx,
398    builder: &mut Builder,
399) -> CalyxResult<()> {
400    let proto_name = cell.prototype.name;
401
402    let res = if sig_ctx.lib.find_primitive(proto_name).is_some() {
403        let c = builder
404            .try_add_primitive(cell.name, proto_name, &cell.prototype.params)
405            .map_err(|e| e.with_pos(&cell.attributes))?;
406        c.borrow_mut().set_reference(cell.reference);
407        c
408    } else {
409        // Validator ensures that if the protoype is not a primitive, it
410        // is a component.
411        let name = builder.component.generate_name(cell.name);
412        let sig = &sig_ctx.comp_sigs[&proto_name].0;
413        let typ = CellType::Component { name: proto_name };
414        let reference = cell.reference;
415        // Components do not have any bindings for parameters
416        let cell = Builder::cell_from_signature(name, typ, sig.clone());
417        cell.borrow_mut().set_reference(reference);
418        builder.component.cells.add(Rc::clone(&cell));
419        cell
420    };
421
422    // Add attributes to the built cell
423    res.borrow_mut().attributes = cell.attributes;
424
425    Ok(())
426}
427
428///////////////// Group Construction /////////////////////////
429
430/// Build an [super::Group] from an [ast::Group] and attach it to the [Component]
431/// associated with the [Builder]
432fn add_group(group: ast::Group, builder: &mut Builder) -> CalyxResult<()> {
433    if group.is_comb {
434        let ir_group = builder.add_comb_group(group.name);
435        let assigns = build_assignments(group.wires, builder)?;
436
437        ir_group.borrow_mut().attributes = group.attributes;
438        ir_group.borrow_mut().assignments = assigns;
439    } else {
440        let ir_group = builder.add_group(group.name);
441        let assigns = build_assignments(group.wires, builder)?;
442
443        ir_group.borrow_mut().attributes = group.attributes;
444        ir_group.borrow_mut().assignments = assigns;
445    };
446
447    Ok(())
448}
449
450/// Build an [super::StaticGroup] from an [ast::StaticGroup] and attach it to the [Component]
451/// associated with the [Builder]
452fn add_static_group(
453    group: ast::StaticGroup,
454    builder: &mut Builder,
455) -> CalyxResult<()> {
456    if group.latency.get() == 0 {
457        return Err(Error::malformed_structure(
458            "static group with 0 latency".to_string(),
459        ));
460    }
461    let ir_group = builder.add_static_group(group.name, group.latency.get());
462    let assigns = build_static_assignments(group.wires, builder)?;
463
464    ir_group.borrow_mut().attributes = group.attributes;
465    ir_group.borrow_mut().assignments = assigns;
466
467    Ok(())
468}
469
470///////////////// Fsm Construction /////////////////////////
471
472/// Build an [super::Fsm] from an [ast::Fsm] and attach it to the [Component]
473/// associated with the [Builder]
474fn add_fsm(fsm: ast::Fsm, builder: &mut Builder) -> CalyxResult<()> {
475    let ir_fsm = builder.add_fsm(fsm.name);
476    let (state_assignments, state_transition): (
477        Vec<Vec<Assignment<Nothing>>>,
478        Vec<Transition>,
479    ) = fsm
480        .fsm_states
481        .into_iter()
482        .map(|rule| -> CalyxResult<_> {
483            Ok((
484                build_assignments(rule.assignments, builder)?,
485                build_transition(rule.transition, builder)?,
486            ))
487        })
488        .collect::<CalyxResult<Vec<_>>>()?
489        .into_iter()
490        .unzip();
491
492    {
493        let mut ir_fsm = ir_fsm.borrow_mut();
494        ir_fsm.attributes = fsm.attributes;
495        ir_fsm.assignments = state_assignments;
496        ir_fsm.transitions = state_transition;
497    }
498
499    Ok(())
500}
501
502///////////////// Assignment Construction /////////////////////////
503
504/// Get the pointer to the Port represented by `port`.
505fn get_port_ref(port: ast::Port, comp: &Component) -> CalyxResult<RRC<Port>> {
506    match port {
507        ast::Port::Comp { component, port } => comp
508            .find_cell(component)
509            .ok_or_else(|| Error::undefined(component, "cell".to_string()))?
510            .borrow()
511            .find(port)
512            .ok_or_else(|| {
513                Error::undefined(
514                    Id::new(format!("{component}.{port}")),
515                    "port".to_string(),
516                )
517            }),
518        ast::Port::This { port } => {
519            comp.signature.borrow().find(&port).ok_or_else(|| {
520                Error::undefined(port, "component port".to_string())
521            })
522        }
523        ast::Port::Hole {
524            struct_elem,
525            name: port,
526        } => {
527            if let Some(f) = comp.find_fsm(struct_elem) {
528                return f.borrow().find(port).ok_or_else(|| {
529                    Error::undefined(
530                        Id::new(format!("{}.{}", f.borrow().name(), port)),
531                        "hole".to_string(),
532                    )
533                });
534            } else if let Some(g) = comp.find_group(struct_elem) {
535                return g.borrow().find(port).ok_or_else(|| {
536                    Error::undefined(
537                        Id::new(format!("{}.{}", g.borrow().name(), port)),
538                        "hole".to_string(),
539                    )
540                });
541            } else {
542                return comp
543                    .find_static_group(struct_elem)
544                    .ok_or_else(|| {
545                        Error::undefined(struct_elem, "group".to_string())
546                    })?
547                    .borrow()
548                    .find(port)
549                    .ok_or_else(|| Error::undefined(port, "hole".to_string()));
550            }
551        }
552    }
553}
554
555/// Get an port using an ast::Atom.
556/// If the atom is a number and the context doesn't already contain a cell
557/// for this constant, instantiate the constant node and get the "out" port
558/// from it.
559fn atom_to_port(
560    atom: ast::Atom,
561    builder: &mut Builder,
562) -> CalyxResult<RRC<Port>> {
563    match atom {
564        ast::Atom::Num(n) => {
565            let port = builder.add_constant(n.val, n.width).borrow().get("out");
566            Ok(Rc::clone(&port))
567        }
568        ast::Atom::Port(p) => get_port_ref(p, builder.component),
569    }
570}
571
572/// Ensures that the given port has the required direction.
573fn ensure_direction(pr: RRC<Port>, dir: Direction) -> CalyxResult<RRC<Port>> {
574    let port_dir = pr.borrow().direction.clone();
575    match (dir, port_dir) {
576        (Direction::Input, Direction::Output) => {
577            let name = pr.borrow().canonical();
578            Err(Error::malformed_structure(format!(
579                "Port `{name}` occurs in write position but is an output port",
580            )))
581        }
582        (Direction::Output, Direction::Input) => {
583            let name = pr.borrow().canonical();
584            Err(Error::malformed_structure(format!(
585                "Port `{name}` occurs in write position but is an output port",
586            )))
587        }
588        _ => Ok(pr),
589    }
590}
591
592/// Build an ir::Assignment from ast::Wire.
593/// The Assignment contains pointers to the relevant ports.
594fn build_assignment(
595    wire: ast::Wire,
596    builder: &mut Builder,
597) -> CalyxResult<Assignment<Nothing>> {
598    let src_port: RRC<Port> = ensure_direction(
599        atom_to_port(wire.src.expr, builder)?,
600        Direction::Output,
601    )?;
602    let dst_port: RRC<Port> = ensure_direction(
603        get_port_ref(wire.dest, builder.component)?,
604        Direction::Input,
605    )?;
606    if src_port.borrow().width != dst_port.borrow().width {
607        let msg = format!(
608            "Mismatched port widths. Source has size {} while destination requires {}.",
609            src_port.borrow().width,
610            dst_port.borrow().width,
611        );
612        return Err(Error::malformed_structure(msg).with_pos(&wire.attributes));
613    }
614    let guard = match wire.src.guard {
615        Some(g) => build_guard(g, builder)?,
616        None => Guard::True,
617    };
618
619    let mut assign = builder.build_assignment(dst_port, src_port, guard);
620    assign.attributes = wire.attributes;
621    Ok(assign)
622}
623
624/// Build an ir::StaticAssignment from ast::StaticWire.
625/// The Assignment contains pointers to the relevant ports.
626fn build_static_assignment(
627    wire: ast::StaticWire,
628    builder: &mut Builder,
629) -> CalyxResult<Assignment<StaticTiming>> {
630    let src_port: RRC<Port> = ensure_direction(
631        atom_to_port(wire.src.expr, builder)?,
632        Direction::Output,
633    )?;
634    let dst_port: RRC<Port> = ensure_direction(
635        get_port_ref(wire.dest, builder.component)?,
636        Direction::Input,
637    )?;
638    if src_port.borrow().width != dst_port.borrow().width {
639        let msg = format!(
640            "Mismatched port widths. Source has size {} while destination requires {}.",
641            src_port.borrow().width,
642            dst_port.borrow().width,
643        );
644        return Err(Error::malformed_structure(msg).with_pos(&wire.attributes));
645    }
646    let guard = match wire.src.guard {
647        Some(g) => build_static_guard(g, builder)?,
648        None => Guard::True,
649    };
650
651    let mut assign = builder.build_assignment(dst_port, src_port, guard);
652    assign.attributes = wire.attributes;
653    Ok(assign)
654}
655
656fn build_assignments(
657    assigns: Vec<ast::Wire>,
658    builder: &mut Builder,
659) -> CalyxResult<Vec<Assignment<Nothing>>> {
660    assigns
661        .into_iter()
662        .map(|w| {
663            let attrs = w.attributes.clone();
664            build_assignment(w, builder).map_err(|err| err.with_pos(&attrs))
665        })
666        .collect::<CalyxResult<Vec<_>>>()
667}
668
669fn build_static_assignments(
670    assigns: Vec<ast::StaticWire>,
671    builder: &mut Builder,
672) -> CalyxResult<Vec<Assignment<StaticTiming>>> {
673    assigns
674        .into_iter()
675        .map(|w| {
676            let attrs = w.attributes.clone();
677            build_static_assignment(w, builder)
678                .map_err(|err| err.with_pos(&attrs))
679        })
680        .collect::<CalyxResult<Vec<_>>>()
681}
682
683/// Build an ir::Transition from ast::Transition.
684/// The Transition contains pointers to the relevant ports.??
685fn build_transition(
686    transition: ast::Transition,
687    builder: &mut Builder,
688) -> CalyxResult<Transition> {
689    match transition {
690        ast::Transition::Unconditional(state_idx) => {
691            Ok(Transition::Unconditional(state_idx))
692        }
693        ast::Transition::Conditional(conditions) => {
694            let conds = conditions
695                .into_iter()
696                .map(|(condition, state_idx)| {
697                    let guard = match condition {
698                        Some(g) => build_guard(g, builder)?,
699                        None => Guard::True,
700                    };
701                    Ok((guard, state_idx))
702                })
703                .collect::<CalyxResult<Vec<(Guard<Nothing>, u64)>>>()?;
704
705            Ok(Transition::Conditional(conds))
706        }
707    }
708}
709
710/// Transform an ast::GuardExpr to an ir::Guard.
711fn build_guard(
712    guard: ast::GuardExpr,
713    bd: &mut Builder,
714) -> CalyxResult<Guard<Nothing>> {
715    use ast::GuardExpr as GE;
716
717    let into_box_guard = |g: Box<GE>, bd: &mut Builder| -> CalyxResult<_> {
718        Ok(Box::new(build_guard(*g, bd)?))
719    };
720
721    Ok(match guard {
722        GE::Atom(atom) => Guard::port(ensure_direction(
723            atom_to_port(atom, bd)?,
724            Direction::Output,
725        )?),
726        GE::Or(l, r) => Guard::or(build_guard(*l, bd)?, build_guard(*r, bd)?),
727        GE::And(l, r) => Guard::and(build_guard(*l, bd)?, build_guard(*r, bd)?),
728        GE::Not(g) => Guard::Not(into_box_guard(g, bd)?),
729        GE::CompOp((op, l, r)) => {
730            let nl = ensure_direction(atom_to_port(l, bd)?, Direction::Output)?;
731            let nr = ensure_direction(atom_to_port(r, bd)?, Direction::Output)?;
732            let nop = match op {
733                ast::GuardComp::Eq => PortComp::Eq,
734                ast::GuardComp::Neq => PortComp::Neq,
735                ast::GuardComp::Gt => PortComp::Gt,
736                ast::GuardComp::Lt => PortComp::Lt,
737                ast::GuardComp::Geq => PortComp::Geq,
738                ast::GuardComp::Leq => PortComp::Leq,
739            };
740            Guard::CompOp(nop, nl, nr)
741        }
742    })
743}
744
745/// Transform an ast::GuardExpr to an ir::Guard.
746fn build_static_guard(
747    guard: ast::StaticGuardExpr,
748    bd: &mut Builder,
749) -> CalyxResult<Guard<StaticTiming>> {
750    use ast::StaticGuardExpr as SGE;
751
752    let into_box_guard = |g: Box<SGE>, bd: &mut Builder| -> CalyxResult<_> {
753        Ok(Box::new(build_static_guard(*g, bd)?))
754    };
755
756    Ok(match guard {
757        SGE::StaticInfo(interval) => Guard::Info(StaticTiming::new(interval)),
758        SGE::Atom(atom) => Guard::port(ensure_direction(
759            atom_to_port(atom, bd)?,
760            Direction::Output,
761        )?),
762        SGE::Or(l, r) => {
763            Guard::or(build_static_guard(*l, bd)?, build_static_guard(*r, bd)?)
764        }
765        SGE::And(l, r) => {
766            Guard::and(build_static_guard(*l, bd)?, build_static_guard(*r, bd)?)
767        }
768        SGE::Not(g) => Guard::Not(into_box_guard(g, bd)?),
769        SGE::CompOp((op, l, r)) => {
770            let nl = ensure_direction(atom_to_port(l, bd)?, Direction::Output)?;
771            let nr = ensure_direction(atom_to_port(r, bd)?, Direction::Output)?;
772            let nop = match op {
773                ast::GuardComp::Eq => PortComp::Eq,
774                ast::GuardComp::Neq => PortComp::Neq,
775                ast::GuardComp::Gt => PortComp::Gt,
776                ast::GuardComp::Lt => PortComp::Lt,
777                ast::GuardComp::Geq => PortComp::Geq,
778                ast::GuardComp::Leq => PortComp::Leq,
779            };
780            Guard::CompOp(nop, nl, nr)
781        }
782    })
783}
784
785///////////////// Control Construction /////////////////////////
786
787fn assert_latencies_eq(
788    given_latency: Option<NonZeroU64>,
789    inferred_latency: u64,
790) {
791    if let Some(v) = given_latency {
792        assert_eq!(
793            v.get(),
794            inferred_latency,
795            "inferred latency: {inferred_latency}, given latency: {v}"
796        )
797    };
798}
799
800// builds static_seq based on stmts, attributes, and latency
801fn build_static_seq(
802    stmts: Vec<ast::Control>,
803    attributes: Attributes,
804    latency: Option<NonZeroU64>,
805    builder: &mut Builder,
806    sig_ctx: &SigCtx,
807) -> CalyxResult<StaticControl> {
808    let ir_stmts = stmts
809        .into_iter()
810        .map(|c| build_static_control(c, sig_ctx, builder))
811        .collect::<CalyxResult<Vec<_>>>()?;
812    let inferred_latency =
813        ir_stmts.iter().fold(0, |acc, s| acc + (s.get_latency()));
814    assert_latencies_eq(latency, inferred_latency);
815    let mut s = StaticControl::seq(ir_stmts, inferred_latency);
816    *s.get_mut_attributes() = attributes;
817    Ok(s)
818}
819
820fn build_static_par(
821    stmts: Vec<ast::Control>,
822    attributes: Attributes,
823    latency: Option<NonZeroU64>,
824    builder: &mut Builder,
825    sig_ctx: &SigCtx,
826) -> CalyxResult<StaticControl> {
827    let ir_stmts = stmts
828        .into_iter()
829        .map(|c| build_static_control(c, sig_ctx, builder))
830        .collect::<CalyxResult<Vec<_>>>()?;
831    let inferred_latency = match ir_stmts.iter().max_by_key(|s| s.get_latency())
832    {
833        Some(s) => s.get_latency(),
834        None => {
835            return Err(Error::malformed_control(
836                "empty par block".to_string(),
837            ));
838        }
839    };
840    assert_latencies_eq(latency, inferred_latency);
841    let mut p = StaticControl::par(ir_stmts, inferred_latency);
842    *p.get_mut_attributes() = attributes;
843    Ok(p)
844}
845
846fn build_static_if(
847    port: ast::Port,
848    tbranch: ast::Control,
849    fbranch: ast::Control,
850    attributes: Attributes,
851    latency: Option<NonZeroU64>,
852    builder: &mut Builder,
853    sig_ctx: &SigCtx,
854) -> CalyxResult<StaticControl> {
855    let ir_tbranch = build_static_control(tbranch, sig_ctx, builder)?;
856    let ir_fbranch = build_static_control(fbranch, sig_ctx, builder)?;
857    let inferred_latency =
858        std::cmp::max(ir_tbranch.get_latency(), ir_fbranch.get_latency());
859    assert_latencies_eq(latency, inferred_latency);
860    let mut con = StaticControl::static_if(
861        ensure_direction(
862            get_port_ref(port, builder.component)?,
863            Direction::Output,
864        )?,
865        Box::new(ir_tbranch),
866        Box::new(ir_fbranch),
867        inferred_latency,
868    );
869    *con.get_mut_attributes() = attributes;
870    Ok(con)
871}
872
873fn build_static_repeat(
874    num_repeats: u64,
875    body: ast::Control,
876    builder: &mut Builder,
877    attributes: Attributes,
878    sig_ctx: &SigCtx,
879) -> CalyxResult<StaticControl> {
880    let body = build_static_control(body, sig_ctx, builder)?;
881    let total_latency = body.get_latency() * num_repeats;
882    let mut scon =
883        StaticControl::repeat(num_repeats, total_latency, Box::new(body));
884    *scon.get_mut_attributes() = attributes;
885    Ok(scon)
886}
887
888// checks whether `control` is static
889fn build_static_control(
890    control: ast::Control,
891    sig_ctx: &SigCtx,
892    builder: &mut Builder,
893) -> CalyxResult<StaticControl> {
894    let sc = match control {
895        ast::Control::Enable {
896            comp: group,
897            attributes,
898        } => {
899            if builder.component.find_group(group).is_some() {
900                // dynamic group called in build_static_control
901                return Err(Error::malformed_control(
902                    "found dynamic group in static context".to_string(),
903                )
904                .with_pos(&attributes));
905            };
906            let mut en = StaticControl::from(Rc::clone(
907                &builder.component.find_static_group(group).ok_or_else(
908                    || {
909                        Error::undefined(group, "group".to_string())
910                            .with_pos(&attributes)
911                    },
912                )?,
913            ));
914            *en.get_mut_attributes() = attributes;
915            en
916        }
917        ast::Control::StaticInvoke {
918            comp,
919            inputs,
920            outputs,
921            attributes,
922            ref_cells,
923            latency,
924            comb_group,
925        } => {
926            let cell = Rc::clone(
927                &builder.component.find_cell(comp).ok_or_else(|| {
928                    Error::undefined(comp, "cell".to_string())
929                        .with_pos(&attributes)
930                })?,
931            );
932            let comp_name = cell.borrow().type_name().unwrap_or_else(|| {
933                unreachable!("invoked component without a name")
934            });
935            let c_latency =
936                get_comp_latency(sig_ctx, Rc::clone(&cell), &attributes)?;
937            let unwrapped_latency = if let Some(v) = c_latency {
938                v
939            } else {
940                return Err(Error::malformed_control(format!(
941                    "component {comp_name} is statically invoked, but is neither static nor does it have @interval attribute its @go port"
942                ))
943                .with_pos(&attributes));
944            };
945            assert_latencies_eq(latency, unwrapped_latency.into());
946
947            let inputs = inputs
948                .into_iter()
949                .map(|(id, port)| {
950                    // checking that comp_name.id exists on comp's signature
951                    check_valid_port(
952                        Rc::clone(&cell),
953                        &id,
954                        &attributes,
955                        sig_ctx,
956                    )?;
957                    atom_to_port(port, builder)
958                        .and_then(|pr| ensure_direction(pr, Direction::Output))
959                        .map(|p| (id, p))
960                })
961                .collect::<Result<_, _>>()?;
962            let outputs = outputs
963                .into_iter()
964                .map(|(id, port)| {
965                    // checking that comp_name.id exists on comp's signature
966                    check_valid_port(
967                        Rc::clone(&cell),
968                        &id,
969                        &attributes,
970                        sig_ctx,
971                    )?;
972                    atom_to_port(port, builder)
973                        .and_then(|pr| ensure_direction(pr, Direction::Input))
974                        .map(|p| (id, p))
975                })
976                .collect::<Result<_, _>>()?;
977            let mut inv = StaticInvoke {
978                comp: cell,
979                inputs,
980                outputs,
981                attributes: Attributes::default(),
982                ref_cells: Vec::new(),
983                latency: unwrapped_latency.into(),
984                comb_group: None,
985            };
986            if !ref_cells.is_empty() {
987                let mut ext_cell_tuples = Vec::new();
988                for (outcell, incell) in ref_cells {
989                    let ext_cell_ref =
990                        builder.component.find_cell(incell).ok_or_else(
991                            || Error::undefined(incell, "cell".to_string()),
992                        )?;
993                    ext_cell_tuples.push((outcell, ext_cell_ref));
994                }
995                inv.ref_cells = ext_cell_tuples;
996            }
997            if let Some(cg) = comb_group {
998                let cg_ref =
999                    builder.component.find_comb_group(cg).ok_or_else(|| {
1000                        Error::undefined(cg, "combinational group".to_string())
1001                            .with_pos(&inv.attributes)
1002                    })?;
1003                inv.comb_group = Some(cg_ref);
1004            }
1005            let mut con = StaticControl::Invoke(inv);
1006            *con.get_mut_attributes() = attributes;
1007            return Ok(con);
1008        }
1009        ast::Control::StaticSeq {
1010            stmts,
1011            attributes,
1012            latency,
1013        } => {
1014            return build_static_seq(
1015                stmts, attributes, latency, builder, sig_ctx,
1016            );
1017        }
1018        ast::Control::StaticPar {
1019            stmts,
1020            attributes,
1021            latency,
1022        } => {
1023            return build_static_par(
1024                stmts, attributes, latency, builder, sig_ctx,
1025            );
1026        }
1027        ast::Control::StaticIf {
1028            port,
1029            tbranch,
1030            fbranch,
1031            attributes,
1032            latency,
1033        } => {
1034            return build_static_if(
1035                port, *tbranch, *fbranch, attributes, latency, builder, sig_ctx,
1036            );
1037        }
1038        ast::Control::StaticRepeat {
1039            attributes,
1040            num_repeats,
1041            body,
1042        } => {
1043            return build_static_repeat(
1044                num_repeats,
1045                *body,
1046                builder,
1047                attributes,
1048                sig_ctx,
1049            );
1050        }
1051        ast::Control::Par { .. }
1052        | ast::Control::If { .. }
1053        | ast::Control::While { .. }
1054        | ast::Control::Seq { .. }
1055        | ast::Control::Repeat { .. }
1056        | ast::Control::Invoke { .. } => {
1057            return Err(Error::malformed_control(
1058                "found dynamic control in static context".to_string(),
1059            )
1060            .with_pos(control.get_attributes()));
1061        }
1062        ast::Control::Empty { attributes } => {
1063            let mut emp = StaticControl::empty();
1064            *emp.get_mut_attributes() = attributes;
1065            emp
1066        }
1067    };
1068    Ok(sc)
1069}
1070
1071/// Transform ast::Control to ir::Control.
1072fn build_control(
1073    control: ast::Control,
1074    sig_ctx: &SigCtx,
1075    builder: &mut Builder,
1076) -> CalyxResult<Control> {
1077    let c = match control {
1078        ast::Control::Enable {
1079            comp: component,
1080            attributes,
1081        } => {
1082            if let Some(f) = builder.component.find_fsm(component) {
1083                let mut en = Control::fsm_enable(Rc::clone(&f));
1084                *en.get_mut_attributes() = attributes;
1085                en
1086            } else if let Some(g) = builder.component.find_group(component) {
1087                let mut en = Control::enable(Rc::clone(&g));
1088                *en.get_mut_attributes() = attributes;
1089                en
1090            } else {
1091                let mut en = Control::Static(StaticControl::from(Rc::clone(
1092                    &builder
1093                        .component
1094                        .find_static_group(component)
1095                        .ok_or_else(|| {
1096                            Error::undefined(
1097                                component,
1098                                "group or fsm".to_string(),
1099                            )
1100                            .with_pos(&attributes)
1101                        })?,
1102                )));
1103                *en.get_mut_attributes() = attributes;
1104                en
1105            }
1106        }
1107        ast::Control::StaticInvoke {
1108            comp,
1109            inputs,
1110            outputs,
1111            attributes,
1112            ref_cells,
1113            latency,
1114            comb_group,
1115        } => {
1116            let cell = Rc::clone(
1117                &builder.component.find_cell(comp).ok_or_else(|| {
1118                    Error::undefined(comp, "cell".to_string())
1119                        .with_pos(&attributes)
1120                })?,
1121            );
1122            let comp_name = cell.borrow().type_name().unwrap_or_else(|| {
1123                unreachable!("invoked component without a name")
1124            });
1125            let c_latency =
1126                get_comp_latency(sig_ctx, Rc::clone(&cell), &attributes)?;
1127            let unwrapped_latency = if let Some(v) = c_latency {
1128                v
1129            } else {
1130                return Err(Error::malformed_control(format!(
1131                    "component {comp_name} is statically invoked, but is neither static nor does it have @interval attribute its @go port"
1132                ))
1133                .with_pos(&attributes));
1134            };
1135            assert_latencies_eq(latency, unwrapped_latency.into());
1136
1137            let inputs = inputs
1138                .into_iter()
1139                .map(|(id, port)| {
1140                    // checking that comp_name.id exists on comp's signature
1141                    check_valid_port(
1142                        Rc::clone(&cell),
1143                        &id,
1144                        &attributes,
1145                        sig_ctx,
1146                    )?;
1147                    atom_to_port(port, builder)
1148                        .and_then(|pr| ensure_direction(pr, Direction::Output))
1149                        .map(|p| (id, p))
1150                })
1151                .collect::<Result<_, _>>()?;
1152            let outputs = outputs
1153                .into_iter()
1154                .map(|(id, port)| {
1155                    // checking that comp_name.id exists on comp's signature
1156                    check_valid_port(
1157                        Rc::clone(&cell),
1158                        &id,
1159                        &attributes,
1160                        sig_ctx,
1161                    )?;
1162                    atom_to_port(port, builder)
1163                        .and_then(|pr| ensure_direction(pr, Direction::Input))
1164                        .map(|p| (id, p))
1165                })
1166                .collect::<Result<_, _>>()?;
1167            let mut inv = StaticInvoke {
1168                comp: cell,
1169                inputs,
1170                outputs,
1171                attributes: Attributes::default(),
1172                ref_cells: Vec::new(),
1173                latency: unwrapped_latency.into(),
1174                comb_group: None,
1175            };
1176            if !ref_cells.is_empty() {
1177                let mut ext_cell_tuples = Vec::new();
1178                for (outcell, incell) in ref_cells {
1179                    let ext_cell_ref =
1180                        builder.component.find_cell(incell).ok_or_else(
1181                            || Error::undefined(incell, "cell".to_string()),
1182                        )?;
1183                    ext_cell_tuples.push((outcell, ext_cell_ref));
1184                }
1185                inv.ref_cells = ext_cell_tuples;
1186            }
1187            if let Some(cg) = comb_group {
1188                let cg_ref =
1189                    builder.component.find_comb_group(cg).ok_or_else(|| {
1190                        Error::undefined(cg, "combinational group".to_string())
1191                            .with_pos(&inv.attributes)
1192                    })?;
1193                inv.comb_group = Some(cg_ref);
1194            }
1195            let mut con = StaticControl::Invoke(inv);
1196            *con.get_mut_attributes() = attributes;
1197            Control::Static(con)
1198        }
1199        ast::Control::Invoke {
1200            comp: component,
1201            inputs,
1202            outputs,
1203            attributes,
1204            comb_group,
1205            ref_cells,
1206        } => {
1207            let cell = Rc::clone(
1208                &builder.component.find_cell(component).ok_or_else(|| {
1209                    Error::undefined(component, "cell".to_string())
1210                        .with_pos(&attributes)
1211                })?,
1212            );
1213
1214            let comp_name = cell.borrow().type_name().unwrap_or_else(|| {
1215                unreachable!("invoked component without a name")
1216            });
1217
1218            // Error to dynamically invoke static component
1219            if get_static_latency(sig_ctx, Rc::clone(&cell), &attributes)?
1220                .is_some()
1221            {
1222                return Err(Error::malformed_control(format!(
1223                    "static component {comp_name} is dynamically invoked"
1224                ))
1225                .with_pos(&attributes));
1226            }
1227
1228            let inputs = inputs
1229                .into_iter()
1230                .map(|(id, port)| {
1231                    // check that comp_name.id is a valid port based on comp_name's signature
1232                    check_valid_port(
1233                        Rc::clone(&cell),
1234                        &id,
1235                        &attributes,
1236                        sig_ctx,
1237                    )?;
1238                    atom_to_port(port, builder)
1239                        .and_then(|pr| ensure_direction(pr, Direction::Output))
1240                        .map(|p| (id, p))
1241                })
1242                .collect::<Result<_, _>>()?;
1243            let outputs = outputs
1244                .into_iter()
1245                .map(|(id, port)| {
1246                    // check that comp_name.id is a valid port based on comp_name's signature
1247                    check_valid_port(
1248                        Rc::clone(&cell),
1249                        &id,
1250                        &attributes,
1251                        sig_ctx,
1252                    )?;
1253                    atom_to_port(port, builder)
1254                        .and_then(|pr| ensure_direction(pr, Direction::Input))
1255                        .map(|p| (id, p))
1256                })
1257                .collect::<Result<_, _>>()?;
1258            let mut inv = Invoke {
1259                comp: cell,
1260                inputs,
1261                outputs,
1262                attributes,
1263                comb_group: None,
1264                ref_cells: Vec::new(),
1265            };
1266            if let Some(cg) = comb_group {
1267                let cg_ref =
1268                    builder.component.find_comb_group(cg).ok_or_else(|| {
1269                        Error::undefined(cg, "combinational group".to_string())
1270                            .with_pos(&inv.attributes)
1271                    })?;
1272                inv.comb_group = Some(cg_ref);
1273            }
1274            if !ref_cells.is_empty() {
1275                let mut ext_cell_tuples = Vec::new();
1276                for (outcell, incell) in ref_cells {
1277                    let ext_cell_ref =
1278                        builder.component.find_cell(incell).ok_or_else(
1279                            || Error::undefined(incell, "cell".to_string()),
1280                        )?;
1281                    ext_cell_tuples.push((outcell, ext_cell_ref));
1282                }
1283                inv.ref_cells = ext_cell_tuples;
1284            }
1285            Control::Invoke(inv)
1286        }
1287        ast::Control::Seq { stmts, attributes } => {
1288            let mut s = Control::seq(
1289                stmts
1290                    .into_iter()
1291                    .map(|c| build_control(c, sig_ctx, builder))
1292                    .collect::<CalyxResult<Vec<_>>>()?,
1293            );
1294            *s.get_mut_attributes() = attributes;
1295            s
1296        }
1297        ast::Control::StaticSeq {
1298            stmts,
1299            attributes,
1300            latency,
1301        } => {
1302            let s =
1303                build_static_seq(stmts, attributes, latency, builder, sig_ctx);
1304            Control::Static(s?)
1305        }
1306        ast::Control::StaticPar {
1307            stmts,
1308            attributes,
1309            latency,
1310        } => {
1311            let s =
1312                build_static_par(stmts, attributes, latency, builder, sig_ctx);
1313            Control::Static(s?)
1314        }
1315        ast::Control::StaticIf {
1316            port,
1317            tbranch,
1318            fbranch,
1319            attributes,
1320            latency,
1321        } => {
1322            let s = build_static_if(
1323                port, *tbranch, *fbranch, attributes, latency, builder, sig_ctx,
1324            );
1325            Control::Static(s?)
1326        }
1327        ast::Control::StaticRepeat {
1328            attributes,
1329            num_repeats,
1330            body,
1331        } => {
1332            let s = build_static_repeat(
1333                num_repeats,
1334                *body,
1335                builder,
1336                attributes,
1337                sig_ctx,
1338            );
1339            Control::Static(s?)
1340        }
1341        ast::Control::Par { stmts, attributes } => {
1342            let mut p = Control::par(
1343                stmts
1344                    .into_iter()
1345                    .map(|c| build_control(c, sig_ctx, builder))
1346                    .collect::<CalyxResult<Vec<_>>>()?,
1347            );
1348            *p.get_mut_attributes() = attributes;
1349            p
1350        }
1351        ast::Control::If {
1352            port,
1353            cond: maybe_cond,
1354            tbranch,
1355            fbranch,
1356            attributes,
1357        } => {
1358            let group = maybe_cond
1359                .map(|cond| {
1360                    builder.component.find_comb_group(cond).ok_or_else(|| {
1361                        Error::undefined(
1362                            cond,
1363                            "combinational group".to_string(),
1364                        )
1365                        .with_pos(&attributes)
1366                    })
1367                })
1368                .transpose()?;
1369            let mut con = Control::if_(
1370                ensure_direction(
1371                    get_port_ref(port, builder.component)?,
1372                    Direction::Output,
1373                )?,
1374                group,
1375                Box::new(build_control(*tbranch, sig_ctx, builder)?),
1376                Box::new(build_control(*fbranch, sig_ctx, builder)?),
1377            );
1378            *con.get_mut_attributes() = attributes;
1379            con
1380        }
1381        ast::Control::While {
1382            port,
1383            cond: maybe_cond,
1384            body,
1385            attributes,
1386        } => {
1387            let group = maybe_cond
1388                .map(|cond| {
1389                    builder.component.find_comb_group(cond).ok_or_else(|| {
1390                        Error::undefined(
1391                            cond,
1392                            "combinational group".to_string(),
1393                        )
1394                        .with_pos(&attributes)
1395                    })
1396                })
1397                .transpose()?;
1398            let mut con = Control::while_(
1399                ensure_direction(
1400                    get_port_ref(port, builder.component)?,
1401                    Direction::Output,
1402                )?,
1403                group,
1404                Box::new(build_control(*body, sig_ctx, builder)?),
1405            );
1406            *con.get_mut_attributes() = attributes;
1407            con
1408        }
1409        ast::Control::Repeat {
1410            num_repeats,
1411            body,
1412            attributes,
1413        } => {
1414            let mut con = Control::repeat(
1415                num_repeats,
1416                Box::new(build_control(*body, sig_ctx, builder)?),
1417            );
1418            *con.get_mut_attributes() = attributes;
1419            con
1420        }
1421        ast::Control::Empty { attributes } => {
1422            let mut emp = Control::empty();
1423            *emp.get_mut_attributes() = attributes;
1424            emp
1425        }
1426    };
1427    Ok(c)
1428}