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