From 02d64d5a41564df4e78e7a3e16b17d20ce8a27c8 Mon Sep 17 00:00:00 2001 From: Nathaniel Navarro Date: Sat, 15 Jun 2024 18:09:10 -0400 Subject: [PATCH] Introduce ability to pass in subtypes as ref cells in `invoke` statements (#2085) * WIP: attempt at implementing subtyping. TBD if this works * typo in structure * change error for wellformedness subtyping runt test * compile_invoke now compiles, tbd if actually works * some clippy fixes * wip compile invoke changes * added debugging stuff to invoke, still a WIP * WIP: Need to do clean up but also this might allow subtyping???? Hope so * add comments to well-formed * maybe works? * seems to be working! * update runt tests * make clippy happy * formatting * update comments * more runt tests * replace hashed map with linkedhashmap for determinism * fix bug where iterating over too many ports in comp_ports in compile_invoke * more runt tests for subtyping * derive partialeq and eq for InLineAttributes * extract `get_concrete_port` as helper function * compile_invoke formatting * WIP: extract require_subtype. TODO: allow it to take in StaticInvoke as well * factor out subtyping check * elaborate on some comments in well_formed * improve comments * add some info about subtyping to the language reference --- calyx-frontend/src/attribute.rs | 2 +- calyx-frontend/src/attributes.rs | 14 ++ calyx-ir/src/rewriter.rs | 4 +- calyx-ir/src/structure.rs | 13 +- calyx-opt/src/passes/compile_invoke.rs | 73 ++++++--- calyx-opt/src/passes/component_iniliner.rs | 6 +- calyx-opt/src/passes/discover_external.rs | 4 +- calyx-opt/src/passes/well_formed.rs | 154 ++++++++++-------- docs/lang/ref.md | 65 +++++++- tests/passes/subtyping.expect | 93 +++++++++++ tests/passes/subtyping.futil | 95 +++++++++++ .../well-formed/ref-type-mismatch.expect | 2 +- 12 files changed, 427 insertions(+), 98 deletions(-) create mode 100644 tests/passes/subtyping.expect create mode 100644 tests/passes/subtyping.futil diff --git a/calyx-frontend/src/attribute.rs b/calyx-frontend/src/attribute.rs index 79e5c76042..e50ece67d5 100644 --- a/calyx-frontend/src/attribute.rs +++ b/calyx-frontend/src/attribute.rs @@ -195,7 +195,7 @@ impl FromStr for Attribute { } } -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] /// Inline storage for boolean attributes. pub(super) struct InlineAttributes { /// Boolean attributes stored in a 16-bit number. diff --git a/calyx-frontend/src/attributes.rs b/calyx-frontend/src/attributes.rs index 16fed2626d..230011b468 100644 --- a/calyx-frontend/src/attributes.rs +++ b/calyx-frontend/src/attributes.rs @@ -169,6 +169,20 @@ impl Attributes { } } +impl PartialEq for Attributes { + fn eq(&self, other: &Self) -> bool { + self.inl == other.inl + && self.hinfo.attrs.len() == other.hinfo.attrs.len() + && self + .hinfo + .attrs + .iter() + .all(|(k, v)| other.hinfo.attrs.get(k) == Some(v)) + } +} + +impl Eq for Attributes {} + #[cfg(feature = "serialize")] impl serde::Serialize for HeapAttrInfo { fn serialize(&self, ser: S) -> Result diff --git a/calyx-ir/src/rewriter.rs b/calyx-ir/src/rewriter.rs index d3815d9265..624923de10 100644 --- a/calyx-ir/src/rewriter.rs +++ b/calyx-ir/src/rewriter.rs @@ -1,3 +1,5 @@ +use linked_hash_map::LinkedHashMap; + use crate::control::StaticInvoke; use crate::{self as ir, RRC}; use std::borrow::BorrowMut; @@ -10,7 +12,7 @@ pub type RewriteMap = HashMap>; /// Map to rewrite port uses. Maps the canonical name of an old port (generated using /// [ir::Port::canonical]) to the new [ir::Port] instance. -pub type PortRewriteMap = HashMap>; +pub type PortRewriteMap = LinkedHashMap>; #[derive(Default)] /// A structure to track rewrite maps for ports. Stores both cell rewrites and direct port diff --git a/calyx-ir/src/structure.rs b/calyx-ir/src/structure.rs index 7633eb0f87..42d10eae29 100644 --- a/calyx-ir/src/structure.rs +++ b/calyx-ir/src/structure.rs @@ -126,6 +126,15 @@ impl Port { { self.get_attributes().has(attr) } + + /// Returns true if the widths, name, direction, and attributes of 2 ports match. + /// This is different than `==` equivalence, which only looks at parent and name. + pub fn type_equivalent(&self, other: &Port) -> bool { + self.width == other.width + && self.direction == other.direction + && self.name == other.name + && self.attributes == other.attributes + } } impl GetAttributes for Port { @@ -215,7 +224,7 @@ pub enum CellType { } impl CellType { - /// Return the name associated with this CellType is present + /// Return the name associated with this CellType if present pub fn get_name(&self) -> Option { match self { CellType::Primitive { name, .. } | CellType::Component { name } => { @@ -244,7 +253,7 @@ impl CellType { } /// Represents an instantiated cell. -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Cell { /// Name of this cell. diff --git a/calyx-opt/src/passes/compile_invoke.rs b/calyx-opt/src/passes/compile_invoke.rs index 32251e350f..5b1d14f05f 100644 --- a/calyx-opt/src/passes/compile_invoke.rs +++ b/calyx-opt/src/passes/compile_invoke.rs @@ -6,6 +6,7 @@ use calyx_ir::{self as ir, Attributes, LibrarySignatures}; use calyx_utils::{CalyxResult, Error}; use ir::{Assignment, RRC, WRC}; use itertools::Itertools; +use linked_hash_map::LinkedHashMap; use std::collections::HashMap; use std::rc::Rc; @@ -54,13 +55,13 @@ fn build_assignments( /// Map for storing added ports for each ref cell /// level of Hashmap represents: /// HashMap<-component name-, Hashmap<(-ref cell name-,-port name-), port>>; -struct RefPortMap(HashMap>>); +struct RefPortMap(HashMap>>); impl RefPortMap { fn insert( &mut self, comp_name: ir::Id, - ports: HashMap>, + ports: LinkedHashMap>, ) { self.0.insert(comp_name, ports); } @@ -68,7 +69,7 @@ impl RefPortMap { fn get( &self, comp_name: &ir::Id, - ) -> Option<&HashMap>> { + ) -> Option<&LinkedHashMap>> { self.0.get(comp_name) } @@ -91,7 +92,7 @@ pub struct CompileInvoke { port_names: RefPortMap, /// Mapping from the ports of cells that were removed to the new port on the /// component signature. - removed: HashMap>, + removed: LinkedHashMap>, /// Ref cells in the component. We hold onto these so that our references don't get invalidated ref_cells: Vec>, } @@ -103,7 +104,7 @@ impl ConstructVisitor for CompileInvoke { { Ok(CompileInvoke { port_names: RefPortMap::default(), - removed: HashMap::new(), + removed: LinkedHashMap::new(), ref_cells: Vec::new(), }) } @@ -131,6 +132,7 @@ impl CompileInvoke { /// /// Since this pass eliminates all ref cells in post order, we expect that /// invoked component already had all of its ref cells removed. + fn ref_cells_to_ports( &mut self, inv_cell: RRC, @@ -138,31 +140,37 @@ impl CompileInvoke { ) -> Vec> { let inv_comp = inv_cell.borrow().type_name().unwrap(); let mut assigns = Vec::new(); - for (ref_cell_name, cell) in ref_cells { + for (ref_cell_name, concrete_cell) in ref_cells { log::debug!( "Removing ref cell `{}` with {} ports", ref_cell_name, - cell.borrow().ports.len() + concrete_cell.borrow().ports.len() ); // Mapping from canonical names of the ports of the ref cell to the - // new port defined on the signature of the component + // new port defined on the signature of the component. This has name of ref cell, not arg cell let Some(comp_ports) = self.port_names.get(&inv_comp) else { unreachable!("component `{}` invoked but not already visited by the pass", inv_comp) }; - // The type of the cell is the same as the ref cell so we can - // iterate over its ports and generate bindings for the ref cell. - for pr in &cell.borrow().ports { - let port = pr.borrow(); - if port.has_attribute(ir::BoolAttr::Clk) - || port.has_attribute(ir::BoolAttr::Reset) + // We expect each canonical port in `comp_ports` to exactly match with a port in + //`concrete_cell` based on well-formedness subtype checks. + for canon in comp_ports.keys() { + //only interested in ports attached to the ref cell + if canon.cell != ref_cell_name { + continue; + } + // The given port of the actual, concrete cell passed in + let concrete_port = + Self::get_concrete_port(concrete_cell.clone(), &canon.port); + + if concrete_port.borrow().has_attribute(ir::BoolAttr::Clk) + || concrete_port.borrow().has_attribute(ir::BoolAttr::Reset) { continue; } - let canon = ir::Canonical::new(ref_cell_name, port.name); - let Some(comp_port) = comp_ports.get(&canon) else { + let Some(comp_port) = comp_ports.get(canon) else { unreachable!("port `{}` not found in the signature of {}. Known ports are: {}", canon, inv_comp, @@ -173,7 +181,7 @@ impl CompileInvoke { let ref_port = inv_cell.borrow().get(comp_port.borrow().name); log::debug!("Port `{}` -> `{}`", canon, ref_port.borrow().name); - let old_port = pr.borrow().canonical(); + let old_port = concrete_port.borrow().canonical(); // If the port has been removed already, get the new port from the component's signature let arg_port = if let Some(sig_pr) = self.removed.get(&old_port) { @@ -184,10 +192,10 @@ impl CompileInvoke { ); Rc::clone(sig_pr) } else { - Rc::clone(pr) + Rc::clone(&concrete_port) }; - match port.direction { + match concrete_port.borrow().direction { ir::Direction::Output => { log::debug!( "constructing: {} = {}", @@ -213,11 +221,34 @@ impl CompileInvoke { _ => { unreachable!("Cell should have inout ports"); } - } + }; } } assigns } + + /// Takes in a concrete cell (aka an in_cell/what is passed in to a ref cell at invocation) + /// and returns the concrete port based on just the port of a canonical id. + fn get_concrete_port( + concrete_cell: RRC, + canonical_port: &ir::Id, + ) -> RRC { + let concrete_cell = concrete_cell.borrow(); + concrete_cell + .ports + .iter() + .find(|&concrete_cell_port| { + concrete_cell_port.borrow().name == canonical_port + }) + .unwrap_or_else(|| { + unreachable!( + "port `{}` not found in the cell `{}`", + canonical_port, + concrete_cell.name() + ) + }) + .clone() + } } impl Visitor for CompileInvoke { @@ -287,6 +318,7 @@ impl Visitor for CompileInvoke { // Assigns representing the ref cell connections invoke_group.borrow_mut().assignments.extend( self.ref_cells_to_ports(Rc::clone(&s.comp), s.ref_cells.drain(..)), + // ), //the clone here is questionable? but lets things type check? Maybe change ref_cells_to_ports to expect a reference? ); // comp.go = 1'd1; @@ -361,7 +393,6 @@ impl Visitor for CompileInvoke { _comps: &[ir::Component], ) -> VisResult { let mut builder = ir::Builder::new(comp, ctx); - let invoke_group = builder.add_static_group("static_invoke", s.latency); invoke_group.borrow_mut().assignments.extend( diff --git a/calyx-opt/src/passes/component_iniliner.rs b/calyx-opt/src/passes/component_iniliner.rs index 43fc3e7f9e..0dc605447f 100644 --- a/calyx-opt/src/passes/component_iniliner.rs +++ b/calyx-opt/src/passes/component_iniliner.rs @@ -7,6 +7,7 @@ use calyx_ir::{self as ir, rewriter, GetAttributes, LibrarySignatures, RRC}; use calyx_utils::Error; use ir::Nothing; use itertools::Itertools; +use linked_hash_map::LinkedHashMap; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -71,7 +72,7 @@ impl ComponentInliner { always_inline, new_fsms, control_map: HashMap::default(), - interface_rewrites: HashMap::default(), + interface_rewrites: LinkedHashMap::default(), inlined_cells: Vec::default(), } } @@ -442,7 +443,8 @@ impl Visitor for ComponentInliner { .collect::>(); // Rewrites for the interface ports of inlined cells. - let mut interface_rewrites: rewriter::PortRewriteMap = HashMap::new(); + let mut interface_rewrites: rewriter::PortRewriteMap = + LinkedHashMap::new(); // Track names of cells that were inlined. let mut inlined_cells = HashSet::new(); let mut builder = ir::Builder::new(comp, sigs); diff --git a/calyx-opt/src/passes/discover_external.rs b/calyx-opt/src/passes/discover_external.rs index 467d2428e4..f28371bca8 100644 --- a/calyx-opt/src/passes/discover_external.rs +++ b/calyx-opt/src/passes/discover_external.rs @@ -4,7 +4,7 @@ use calyx_utils::CalyxResult; use ir::RRC; use itertools::Itertools; use linked_hash_map::LinkedHashMap; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; /// A pass to detect cells that have been inlined into the top-level component /// and turn them into real cells marked with [ir::BoolAttr::External]. @@ -241,7 +241,7 @@ impl Visitor for DiscoverExternal { } // Rewrite the ports mentioned in the component signature and remove them - let mut rewrites: ir::rewriter::PortRewriteMap = HashMap::new(); + let mut rewrites: ir::rewriter::PortRewriteMap = LinkedHashMap::new(); for (pre, ports) in port_map { // let prim = sigs.get_primitive(pre_to_prim[&pre]); let cr = pre_to_cells[&pre].clone(); diff --git a/calyx-opt/src/passes/well_formed.rs b/calyx-opt/src/passes/well_formed.rs index ddc9ba06e5..6b05827f54 100644 --- a/calyx-opt/src/passes/well_formed.rs +++ b/calyx-opt/src/passes/well_formed.rs @@ -1,6 +1,6 @@ use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor}; use calyx_ir::{ - self as ir, CellType, Component, GetAttributes, LibrarySignatures, + self as ir, Cell, CellType, Component, GetAttributes, LibrarySignatures, RESERVED_NAMES, }; use calyx_utils::{CalyxResult, Error, WithPos}; @@ -80,12 +80,71 @@ pub struct WellFormed { used_groups: HashSet, /// Names of combinational groups used in the control. used_comb_groups: HashSet, - /// ref cell types of components used in the control. - ref_cell_types: HashMap>, + /// ref cells of components used in the control. Used for type checking. + ref_cells: HashMap>, /// Stack of currently active combinational groups active_comb: ActiveAssignments, } +enum Invoke<'a> { + StaticInvoke(&'a ir::StaticInvoke), + Invoke(&'a ir::Invoke), +} + +impl Invoke<'_> { + fn get_ref_cells(&self) -> &Vec<(ir::Id, ir::RRC)> { + match self { + Invoke::StaticInvoke(s) => &s.ref_cells, + Invoke::Invoke(s) => &s.ref_cells, + } + } + + fn get_attributes(&self) -> &ir::Attributes { + match self { + Invoke::StaticInvoke(s) => s.get_attributes(), + Invoke::Invoke(s) => s.get_attributes(), + } + } +} + +fn require_subtype( + invoke: Invoke, + self_ref_cells: &HashMap>, + id: &ir::Id, +) -> CalyxResult<()> { + let cell_map = &self_ref_cells[id]; + let mut mentioned_cells = HashSet::new(); + for (outcell, incell) in invoke.get_ref_cells().iter() { + if let Some(oc) = cell_map.get(outcell) { + if !subtype(oc, &incell.borrow()) { + return Err(Error::malformed_control(format!( + "The type passed in `{}` is not a subtype of the expected type `{}`.", + incell.borrow().prototype.surface_name().unwrap(), + oc.prototype.surface_name().unwrap() + )) + .with_pos(invoke.get_attributes())); + } else { + mentioned_cells.insert(outcell); + } + } else { + return Err(Error::malformed_control(format!( + "{} does not have ref cell named {}", + id, outcell + ))); + } + } + for id in cell_map.keys() { + if !mentioned_cells.contains(id) { + return Err(Error::malformed_control(format!( + "unmentioned ref cell: {}", + id + )) + .with_pos(invoke.get_attributes())); + } + } + Ok(()) +} + impl ConstructVisitor for WellFormed { fn from(ctx: &ir::Context) -> CalyxResult where @@ -94,10 +153,10 @@ impl ConstructVisitor for WellFormed { let reserved_names = RESERVED_NAMES.iter().map(|s| ir::Id::from(*s)).collect(); - let mut ref_cell_types = HashMap::new(); + let mut ref_cells = HashMap::new(); for comp in ctx.components.iter() { // Non-main components cannot use @external attribute - let cellmap: LinkedHashMap = comp + let cellmap: LinkedHashMap = comp .cells .iter() .filter_map(|cr| { @@ -108,20 +167,20 @@ impl ConstructVisitor for WellFormed { { Some(Err(Error::malformed_structure("Cell cannot be marked `@external` in non-entrypoint component").with_pos(&cell.attributes))) } else if cell.is_reference() { - Some(Ok((cell.name(), cell.prototype.clone()))) + Some(Ok((cell.name(), cell.clone()))) } else { None } }) .collect::>()?; - ref_cell_types.insert(comp.name, cellmap); + ref_cells.insert(comp.name, cellmap); } let w_f = WellFormed { reserved_names, used_groups: HashSet::new(), used_comb_groups: HashSet::new(), - ref_cell_types, + ref_cells, active_comb: ActiveAssignments::default(), }; @@ -191,16 +250,25 @@ where Ok(()) } -fn same_type(proto_out: &CellType, proto_in: &CellType) -> CalyxResult<()> { - if proto_out != proto_in { - Err(Error::malformed_control(format!( - "Unexpected type for ref cell. Expected `{}`, received `{}`", - proto_out.surface_name().unwrap(), - proto_in.surface_name().unwrap(), - ))) - } else { - Ok(()) +/// Returns true if `cell_in` is a subtype of `cell_out`. +/// Currenly this only checks for [`type_equivalence`](#method.calyx_ir::structure::Port::type_equivalent) +/// between ports. It does not fully examine the cells +/// for subtype compatability for things like nested ref cells. +// XXX(nate): Cells don't contain information about their own `ref` cells so we'd need to extract it from `ir:Component` I think? +fn subtype(cell_out: &Cell, cell_in: &Cell) -> bool { + for port in cell_out.ports() { + match cell_in.find(port.borrow().name) { + Some(port_in) => { + if !port.borrow().type_equivalent(&port_in.borrow()) { + return false; + } + } + None => { + return false; + } + } } + true } impl Visitor for WellFormed { @@ -560,32 +628,8 @@ impl Visitor for WellFormed { let cell = s.comp.borrow(); if let CellType::Component { name: id } = &cell.prototype { - let cellmap = &self.ref_cell_types[id]; - let mut mentioned_cells = HashSet::new(); - for (outcell, incell) in s.ref_cells.iter() { - if let Some(t) = cellmap.get(outcell) { - let proto = incell.borrow().prototype.clone(); - same_type(t, &proto) - .map_err(|err| err.with_pos(&s.attributes))?; - mentioned_cells.insert(outcell); - } else { - return Err(Error::malformed_control(format!( - "{} does not have ref cell named {}", - id, outcell - ))); - } - } - for id in cellmap.keys() { - if mentioned_cells.get(id).is_none() { - return Err(Error::malformed_control(format!( - "unmentioned ref cell: {}", - id - )) - .with_pos(&s.attributes)); - } - } + require_subtype(Invoke::Invoke(s), &self.ref_cells, id)?; } - Ok(Action::Continue) } @@ -600,32 +644,8 @@ impl Visitor for WellFormed { let cell = s.comp.borrow(); if let CellType::Component { name: id } = &cell.prototype { - let cellmap = &self.ref_cell_types[id]; - let mut mentioned_cells = HashSet::new(); - for (outcell, incell) in s.ref_cells.iter() { - if let Some(t) = cellmap.get(outcell) { - let proto = incell.borrow().prototype.clone(); - same_type(t, &proto) - .map_err(|err| err.with_pos(&s.attributes))?; - mentioned_cells.insert(outcell); - } else { - return Err(Error::malformed_control(format!( - "{} does not have ref cell named {}", - id, outcell - ))); - } - } - for id in cellmap.keys() { - if mentioned_cells.get(id).is_none() { - return Err(Error::malformed_control(format!( - "unmentioned ref cell: {}", - id - )) - .with_pos(&s.attributes)); - } - } + require_subtype(Invoke::StaticInvoke(s), &self.ref_cells, id)?; } - Ok(Action::Continue) } diff --git a/docs/lang/ref.md b/docs/lang/ref.md index 89dac0784b..965ad6d780 100644 --- a/docs/lang/ref.md +++ b/docs/lang/ref.md @@ -461,7 +461,7 @@ component update_memory() -> () { } ``` -When invoking such a component, the calling component must provide a binding for each defined cell: +When invoking[invoke] such a component, the calling component must provide a binding for each defined cell: ``` component main() -> () { cells { @@ -479,8 +479,70 @@ component main() -> () { } ``` As the example shows, each invocation can take different bindings for each `ref` cell. +In the first invocation, we pass in the concrete cell `m1` while in the second we pass +in `m2`. See [the tutorial][ref-tut] for longer example on how to use this feature. +### Subtyping for `ref` cells + +When providing bindings for [`ref` cells][ref], one must provide a concrete cell that is a +*subtype* of the `ref` cell. A cell `a` is a subtype of cell `b` if the [component][components] +defining `a` has *at least* the same ports as the component defining `b.` + +Consider the following component definitions: +``` +component b(in_1 : 1) -> (out_1 : 1) { + // cells, wires, and control blocks +} + +component a(in_1 : 1, in_2 : 1) -> (out_1 : 1, out_2 : 1){ + // cells, wires, and control blocks +} +``` + +Because the component definition of `a` has the ports `in_1` and `out_1`, a concrete cell of `a` can be bound to a `ref` cell of component `b`: +``` +//Expects a `ref` cell of component `b` +component c() -> () { + cells{ + ref b1 = b(); + } + wires{...} + control{...} +} + + +component main() -> () { + cells { + c_cell = c(); + b_cell = b(); + a_cell = a(); //recall `a` is a subtype of `b` + } + wires { ... } + control { + seq { + // Pass `b_cell` by reference. Both are `b1` and `b_cell` are defined by the component `b` + invoke c[b1=b_cell]()(); + // Pass `a_cell` by reference. The `ref` cell and concrete cell are defined by different components, + // but this is allowed because `a` is a subtype of `b`. + invoke c[b1=a_cell]()(); + } + } +} +``` + +Ports are considered to be equal with respect to subtyping if they have the same +name, width, direction, and attributes. + +> **Note:** The notion of subtyping described above, that only checks for port equivalence between components, is incomplete. +> A complete, correct definition of subtyping would require that for `a` to be a subtype +> of `b`, for every `ref` cell expected in `a`, component `b` must expect a `ref` +> cell that is a subtype of the associated `ref` cell in `a` (note that the relationship between +these nested `ref` cells is opposite the relationship of `a` and `b`). +> +> Because nested `ref` cells are not currently allowed in Calyx, this is not a problem in practice. + + [attributes]: ./attributes.md [components]: #calyx-components [cells]: #cells @@ -490,6 +552,7 @@ See [the tutorial][ref-tut] for longer example on how to use this feature. [continuous]: #continuous-assignments [control]: #the-control-operators [ref]: #ref-cells +[invoke]: #invoke [godoneattr]: ./attributes.md#go-done-clk-and-reset [clkreset]: ./attributes.md#go-done-clk-and-reset [ref-tut]: ./memories-by-reference.md diff --git a/tests/passes/subtyping.expect b/tests/passes/subtyping.expect new file mode 100644 index 0000000000..f8d74d3956 --- /dev/null +++ b/tests/passes/subtyping.expect @@ -0,0 +1,93 @@ +import "primitives/core.futil"; +component small_comp(in_1: 32, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out_1: 64, @done done: 1) { + cells { + add = std_add(32); + pad = std_pad(32, 64); + my_reg = std_reg(64); + } + wires { + group double { + my_reg.write_en = 1'd1; + add.right = in_1; + add.left = in_1; + pad.in = add.out; + my_reg.in = pad.out; + double[done] = my_reg.done; + } + out_1 = my_reg.out; + } + control { + double; + } +} +component big_comp(in_1: 32, in_2: 1, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out_1: 64, out_2: 2, @done done: 1) { + cells { + add = std_add(32); + pad = std_pad(32, 64); + my_reg = std_reg(64); + add2 = std_add(2); + pad2 = std_pad(1, 2); + reg2 = std_reg(2); + } + wires { + group double { + my_reg.write_en = 1'd1; + add.right = in_1; + add.left = in_1; + pad.in = add.out; + my_reg.in = pad.out; + double[done] = my_reg.done; + } + group incr { + reg2.write_en = 1'd1; + add2.right = 2'd1; + pad2.in = in_2; + add2.left = pad2.out; + reg2.in = add2.out; + incr[done] = reg2.done; + } + out_2 = reg2.out; + out_1 = my_reg.out; + } + control { + seq { + incr; + double; + } + } +} +component ref_comp(@go go: 1, @clk clk: 1, @reset reset: 1, small_ref_cell_out_1: 64, small_ref_cell_done: 1) -> (out_ref: 64, @done done: 1, small_ref_cell_in_1: 32, small_ref_cell_go: 1) { + cells { + } + wires { + group invoke0 { + small_ref_cell_go = 1'd1; + invoke0[done] = small_ref_cell_done; + small_ref_cell_in_1 = 32'd10; + out_ref = small_ref_cell_out_1; + } + } + control { + invoke0; + } +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out_main: 64, @done done: 1) { + cells { + my_ref_cell = ref_comp(); + big_cell = big_comp(); + } + wires { + group invoke0 { + big_cell.in_1 = my_ref_cell.small_ref_cell_in_1; + my_ref_cell.small_ref_cell_out_1 = big_cell.out_1; + big_cell.go = my_ref_cell.small_ref_cell_go; + my_ref_cell.small_ref_cell_done = big_cell.done; + my_ref_cell.go = 1'd1; + invoke0[done] = my_ref_cell.done; + out_main = my_ref_cell.out_ref; + } + } + control { + invoke0; + } +} diff --git a/tests/passes/subtyping.futil b/tests/passes/subtyping.futil new file mode 100644 index 0000000000..e55006c0f0 --- /dev/null +++ b/tests/passes/subtyping.futil @@ -0,0 +1,95 @@ +// -p well-formed -p validate -p compile-invoke +import "primitives/core.futil"; + +//Toy program which has a small_comp and a big_comp. +//big_comp is a subtype of small_comp w.r.t port names, and bit widths. +component small_comp(in_1 : 32) -> (out_1: 64) { + cells { + add = std_add(32); + pad = std_pad(32,64); + my_reg = std_reg(64); + } + + wires{ + out_1 = my_reg.out; + group double{ + add.left = in_1; + add.right = in_1; + pad.in = add.out; + my_reg.in = pad.out; + my_reg.write_en = 1'b1; + double[done] = my_reg.done; + } + } + + control{ + double; + } +} + + +component big_comp(in_1 : 32, in_2: 1) -> (out_1: 64, out_2: 2) { + cells { + add = std_add(32); + pad = std_pad(32,64); + my_reg = std_reg(64); + + add2 = std_add(2); + pad2 = std_pad(1,2); + reg2 = std_reg(2); + } + + wires{ + out_1 = my_reg.out; + out_2 = reg2.out; + group double{ + add.left = in_1; + add.right = in_1; + pad.in = add.out; + my_reg.in = pad.out; + my_reg.write_en = 1'b1; + double[done] = my_reg.done; + } + + group incr{ + pad2.in = in_2; + add2.left = pad2.out; + add2.right = 2'b1; + reg2.in = add2.out; + reg2.write_en = 1'b1; + incr[done] = reg2.done; + } + } + + control{ + incr; + double; + } +} + +component ref_comp() -> (out_ref:64) { + cells{ + ref small_ref_cell = small_comp(); // (in_1) -> (out_1) + } + + wires{ + } + + control{ + invoke small_ref_cell(in_1 = 32'd10)(out_1 = out_ref); + } +} + +component main() -> (out_main:64){ + cells{ + my_ref_cell = ref_comp(); + big_cell = big_comp(); + } + + wires{ + } + + control{ + invoke my_ref_cell[small_ref_cell=big_cell]()(out_ref = out_main); //tries to pass a big_comp to a small_comp + } +} diff --git a/tests/passes/well-formed/ref-type-mismatch.expect b/tests/passes/well-formed/ref-type-mismatch.expect index bc687225af..dd1bc65566 100644 --- a/tests/passes/well-formed/ref-type-mismatch.expect +++ b/tests/passes/well-formed/ref-type-mismatch.expect @@ -1,4 +1,4 @@ ---STDERR--- Error: tests/passes/well-formed/ref-type-mismatch.futil 33 | invoke f[m = k1]()(); - | ^^^^^^^^^^^^^^^^^^^^^ Malformed Control: Unexpected type for ref cell. Expected `std_reg(32)`, received `std_reg(16)` + | ^^^^^^^^^^^^^^^^^^^^^ Malformed Control: The type passed in `std_reg(16)` is not a subtype of the expected type `std_reg(32)`.