From ffc56a7f8bd3a342d134be0aec83a8d933fd508a Mon Sep 17 00:00:00 2001 From: Griffin Berlstein Date: Wed, 15 Nov 2023 14:21:32 -0500 Subject: [PATCH] [Cider 2.0] random grab bag of stuff (#1778) * move some of the env stuff * very rough stab at the next code * add the missing seq processing * typo * rearrange * checkpoint with semi-functional std memories * add the stdmem to the primitive builder * unfinished but i don't wanna stash rn * some refactoring shennanigans * flesh out the par portion of the descent * dubious checkpoint * remove all the frills from Value * clippy cleanup * clippy's wrath * remove import * minor cleanup * sketch of new value construct * environment is a module now. this code doesn't work * checkpoint --- interp/src/flatten/flat_ir/base.rs | 44 +- .../src/flatten/flat_ir/control/structures.rs | 2 +- interp/src/flatten/mod.rs | 7 +- interp/src/flatten/primitives/builder.rs | 62 +- interp/src/flatten/primitives/macros.rs | 13 + .../src/flatten/primitives/stateful/math.rs | 1 + .../flatten/primitives/stateful/memories.rs | 356 +++++++- interp/src/flatten/primitives/stateful/mod.rs | 1 + interp/src/flatten/structures/environment.rs | 386 --------- .../src/flatten/structures/environment/mod.rs | 773 ++++++++++++++++++ .../structures/environment/program_counter.rs | 301 +++++++ interp/src/flatten/structures/mod.rs | 1 + interp/src/flatten/structures/values.rs | 30 + interp/src/structures/values.rs | 147 +--- interp/src/tests/values.rs | 8 +- 15 files changed, 1598 insertions(+), 534 deletions(-) create mode 100644 interp/src/flatten/primitives/stateful/math.rs delete mode 100644 interp/src/flatten/structures/environment.rs create mode 100644 interp/src/flatten/structures/environment/mod.rs create mode 100644 interp/src/flatten/structures/environment/program_counter.rs create mode 100644 interp/src/flatten/structures/values.rs diff --git a/interp/src/flatten/flat_ir/base.rs b/interp/src/flatten/flat_ir/base.rs index 1c05794bb8..6745bd6d87 100644 --- a/interp/src/flatten/flat_ir/base.rs +++ b/interp/src/flatten/flat_ir/base.rs @@ -85,7 +85,9 @@ impl_index!(LocalCellOffset); pub struct LocalRefCellOffset(u32); impl_index!(LocalRefCellOffset); -/// Enum used in assignments to encapsulate the different types of port references +/// Enum used in assignments to encapsulate the different types of port +/// references these are always relative to a component's base-point and must be +/// converted to global references when used. #[derive(Debug, Copy, Clone)] pub enum PortRef { /// A port belonging to a non-ref cell/group in the current component or the @@ -135,6 +137,46 @@ impl From for PortRef { } } +/// This is the global analogue to [PortRef] and contains global identifiers +/// after the relative offsets have been transformed via a component base location +pub enum GlobalPortRef { + /// A non-ref port with an exact address + Port(GlobalPortId), + /// A reference port + Ref(GlobalRefPortId), +} + +impl From for GlobalPortRef { + fn from(v: GlobalRefPortId) -> Self { + Self::Ref(v) + } +} + +impl From for GlobalPortRef { + fn from(v: GlobalPortId) -> Self { + Self::Port(v) + } +} + +impl GlobalPortRef { + #[must_use] + pub fn as_port(&self) -> Option<&GlobalPortId> { + if let Self::Port(v) = self { + Some(v) + } else { + None + } + } + + #[must_use] + pub fn as_ref(&self) -> Option<&GlobalRefPortId> { + if let Self::Ref(v) = self { + Some(v) + } else { + None + } + } +} /// An enum wrapping the two different types of port definitions (ref/local) pub enum PortDefinitionRef { Local(PortDefinitionIdx), diff --git a/interp/src/flatten/flat_ir/control/structures.rs b/interp/src/flatten/flat_ir/control/structures.rs index e16a9a3ca9..beb839775d 100644 --- a/interp/src/flatten/flat_ir/control/structures.rs +++ b/interp/src/flatten/flat_ir/control/structures.rs @@ -5,7 +5,7 @@ use crate::flatten::{ structures::{index_trait::impl_index, indexed_map::IndexedMap}, }; -#[derive(Debug, Eq, Copy, Clone, PartialEq, Hash)] +#[derive(Debug, Eq, Copy, Clone, PartialEq, Hash, PartialOrd)] pub struct ControlIdx(u32); impl_index!(ControlIdx); diff --git a/interp/src/flatten/mod.rs b/interp/src/flatten/mod.rs index 8f70fbdd52..1cdb5ed865 100644 --- a/interp/src/flatten/mod.rs +++ b/interp/src/flatten/mod.rs @@ -5,13 +5,14 @@ pub(crate) mod text_utils; use structures::environment::Environment; +use self::structures::environment::Simulator; + pub fn flat_main(ctx: &calyx_ir::Context) { let i_ctx = flat_ir::control::translator::translate(ctx); i_ctx.printer().print_program(); let env = Environment::new(&i_ctx); - env.print_env_stats(); - env.print_env(); - env.print_pc() + let mut sim = Simulator::new(env); + sim._main_test() } diff --git a/interp/src/flatten/primitives/builder.rs b/interp/src/flatten/primitives/builder.rs index 181d4ecbaf..522694573d 100644 --- a/interp/src/flatten/primitives/builder.rs +++ b/interp/src/flatten/primitives/builder.rs @@ -1,7 +1,7 @@ use crate::{ flatten::{ flat_ir::{ - cell_prototype::{CellPrototype, PrimType1}, + cell_prototype::{CellPrototype, MemType, PrimType1}, prelude::{CellInfo, GlobalPortId}, }, structures::environment::Environment, @@ -9,8 +9,8 @@ use crate::{ values::Value, }; -use super::stateful::*; use super::{combinational::*, Primitive}; +use super::{prim_trait::DummyPrimitive, stateful::*}; pub fn build_primitive( env: &mut Environment, @@ -59,7 +59,7 @@ pub fn build_primitive( PrimType1::SignedLe => Box::new(StdSle::new(base_port)), PrimType1::SignedLsh => Box::new(StdSlsh::new(base_port)), PrimType1::SignedRsh => Box::new(StdSrsh::new(base_port)), - PrimType1::MultPipe => todo!(), + PrimType1::MultPipe => Box::new(DummyPrimitive), PrimType1::SignedMultPipe => todo!(), PrimType1::DivPipe => todo!(), PrimType1::SignedDivPipe => todo!(), @@ -103,41 +103,53 @@ pub fn build_primitive( out: _, } => todo!(), CellPrototype::MemD1 { - mem_type: _, - width: _, - size: _, + mem_type, + width, + size, idx_size: _, - } => todo!(), + } => match mem_type { + MemType::Seq => todo!("SeqMem primitives are not currently defined in the flat interpreter"), + MemType::Std => Box::new(StdMemD1::new(base_port, *width, false, *size as usize)) + }, CellPrototype::MemD2 { - mem_type: _, - width: _, - d0_size: _, - d1_size: _, + mem_type, + width, + d0_size, + d1_size, d0_idx_size: _, d1_idx_size: _, - } => todo!(), + } => match mem_type { + MemType::Seq => todo!("SeqMem primitives are not currently defined in the flat interpreter"), + MemType::Std => Box::new(StdMemD2::new(base_port, *width, false, (*d0_size as usize, *d1_size as usize))), + }, CellPrototype::MemD3 { - mem_type: _, - width: _, - d0_size: _, - d1_size: _, - d2_size: _, + mem_type, + width, + d0_size, + d1_size, + d2_size, d0_idx_size: _, d1_idx_size: _, d2_idx_size: _, - } => todo!(), + } => match mem_type { + MemType::Seq => todo!("SeqMem primitives are not currently defined in the flat interpreter"), + MemType::Std => Box::new(StdMemD3::new(base_port, *width, false, (*d0_size as usize, *d1_size as usize, *d2_size as usize))), + }, CellPrototype::MemD4 { - mem_type: _, - width: _, - d0_size: _, - d1_size: _, - d2_size: _, - d3_size: _, + mem_type, + width, + d0_size, + d1_size, + d2_size, + d3_size, d0_idx_size: _, d1_idx_size: _, d2_idx_size: _, d3_idx_size: _, - } => todo!(), + }=> match mem_type { + MemType::Seq => todo!("SeqMem primitives are not currently defined in the flat interpreter"), + MemType::Std => Box::new(StdMemD4::new(base_port, *width, false, (*d0_size as usize, *d1_size as usize, *d2_size as usize, *d3_size as usize))), + }, CellPrototype::Unknown(_, _) => todo!(), } } diff --git a/interp/src/flatten/primitives/macros.rs b/interp/src/flatten/primitives/macros.rs index 0f4159bd31..0c068d7b88 100644 --- a/interp/src/flatten/primitives/macros.rs +++ b/interp/src/flatten/primitives/macros.rs @@ -20,7 +20,20 @@ macro_rules! output { } } +macro_rules! make_getters { + ($base:ident; $( $port:ident : $offset:expr ),+ ) => { + $( + #[inline] + fn $port(&self) -> $crate::flatten::flat_ir::prelude::GlobalPortId { + ($crate::flatten::structures::index_trait::IndexRef::index(&self.$base) + $offset).into() + } + )+ + + } +} + pub(crate) use declare_ports; +pub(crate) use make_getters; pub(crate) use output; pub(crate) use ports; diff --git a/interp/src/flatten/primitives/stateful/math.rs b/interp/src/flatten/primitives/stateful/math.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/interp/src/flatten/primitives/stateful/math.rs @@ -0,0 +1 @@ + diff --git a/interp/src/flatten/primitives/stateful/memories.rs b/interp/src/flatten/primitives/stateful/memories.rs index 11d95c0e11..a6b127af6b 100644 --- a/interp/src/flatten/primitives/stateful/memories.rs +++ b/interp/src/flatten/primitives/stateful/memories.rs @@ -2,7 +2,8 @@ use crate::{ flatten::{ flat_ir::prelude::GlobalPortId, primitives::{ - declare_ports, output, ports, prim_trait::Results, Primitive, + declare_ports, make_getters, output, ports, prim_trait::Results, + Primitive, }, structures::environment::PortMap, }, @@ -73,3 +74,356 @@ impl Primitive for StdReg { true } } + +pub trait MemAddresser { + const NON_ADDRESS_BASE: usize; + + fn calculate_addr( + &self, + port_map: &PortMap, + base_port: GlobalPortId, + ) -> usize; +} + +pub struct MemD1; + +impl MemAddresser for MemD1 { + fn calculate_addr( + &self, + port_map: &PortMap, + base_port: GlobalPortId, + ) -> usize { + let addr0 = if SEQ { + ports![&base_port; addr0: Self::SEQ_ADDR0]; + addr0 + } else { + ports![&base_port; addr0: Self::COMB_ADDR0]; + addr0 + }; + + port_map[addr0].as_usize() + } + + const NON_ADDRESS_BASE: usize = if SEQ { + Self::SEQ_ADDR0 + 1 + } else { + Self::COMB_ADDR0 + 1 + }; +} + +impl MemD1 { + declare_ports![SEQ_ADDR0: 2, COMB_ADDR0: 0]; +} + +pub struct MemD2 { + d1_size: usize, +} + +impl MemD2 { + declare_ports![SEQ_ADDR0: 2, COMB_ADDR0: 0, SEQ_ADDR1: 3, COMB_ADDR1: 1]; +} + +impl MemAddresser for MemD2 { + fn calculate_addr( + &self, + port_map: &PortMap, + base_port: GlobalPortId, + ) -> usize { + let (addr0, addr1) = if SEQ { + ports![&base_port; + addr0: Self::SEQ_ADDR0, + addr1: Self::SEQ_ADDR1]; + (addr0, addr1) + } else { + ports![&base_port; + addr0: Self::COMB_ADDR0, + addr1: Self::COMB_ADDR1]; + (addr0, addr1) + }; + + let a0 = port_map[addr0].as_usize(); + let a1 = port_map[addr1].as_usize(); + + a0 * self.d1_size + a1 + } + + const NON_ADDRESS_BASE: usize = if SEQ { + Self::SEQ_ADDR1 + 1 + } else { + Self::COMB_ADDR1 + 1 + }; +} + +pub struct MemD3 { + d1_size: usize, + d2_size: usize, +} + +impl MemD3 { + declare_ports![SEQ_ADDR0: 2, COMB_ADDR0: 0, + SEQ_ADDR1: 3, COMB_ADDR1: 1, + SEQ_ADDR2: 4, COMB_ADDR2: 2]; +} + +impl MemAddresser for MemD3 { + fn calculate_addr( + &self, + port_map: &PortMap, + base_port: GlobalPortId, + ) -> usize { + let (addr0, addr1, addr2) = if SEQ { + ports![&base_port; + addr0: Self::SEQ_ADDR0, + addr1: Self::SEQ_ADDR1, + addr2: Self::SEQ_ADDR2 + ]; + (addr0, addr1, addr2) + } else { + ports![&base_port; + addr0: Self::COMB_ADDR0, + addr1: Self::COMB_ADDR1, + addr2: Self::COMB_ADDR2 + ]; + + (addr0, addr1, addr2) + }; + + let a0 = port_map[addr0].as_usize(); + let a1 = port_map[addr1].as_usize(); + let a2 = port_map[addr2].as_usize(); + + a0 * (self.d1_size * self.d2_size) + a1 * self.d2_size + a2 + } + + const NON_ADDRESS_BASE: usize = if SEQ { + Self::SEQ_ADDR2 + 1 + } else { + Self::COMB_ADDR2 + 1 + }; +} + +pub struct MemD4 { + d1_size: usize, + d2_size: usize, + d3_size: usize, +} + +impl MemD4 { + declare_ports![ + SEQ_ADDR0: 2, COMB_ADDR0: 0, + SEQ_ADDR1: 3, COMB_ADDR1: 1, + SEQ_ADDR2: 4, COMB_ADDR2: 2, + SEQ_ADDR3: 5, COMB_ADDR3: 3 + ]; +} + +impl MemAddresser for MemD4 { + fn calculate_addr( + &self, + port_map: &PortMap, + base_port: GlobalPortId, + ) -> usize { + let (addr0, addr1, addr2, addr3) = if SEQ { + ports![&base_port; + addr0: Self::SEQ_ADDR0, + addr1: Self::SEQ_ADDR1, + addr2: Self::SEQ_ADDR2, + addr3: Self::SEQ_ADDR3 + ]; + (addr0, addr1, addr2, addr3) + } else { + ports![&base_port; + addr0: Self::COMB_ADDR0, + addr1: Self::COMB_ADDR1, + addr2: Self::COMB_ADDR2, + addr3: Self::COMB_ADDR3 + ]; + + (addr0, addr1, addr2, addr3) + }; + + let a0 = port_map[addr0].as_usize(); + let a1 = port_map[addr1].as_usize(); + let a2 = port_map[addr2].as_usize(); + let a3 = port_map[addr3].as_usize(); + + a0 * (self.d1_size * self.d2_size * self.d3_size) + + a1 * (self.d2_size * self.d3_size) + + a2 * self.d3_size + + a3 + } + + const NON_ADDRESS_BASE: usize = if SEQ { + Self::SEQ_ADDR3 + 1 + } else { + Self::COMB_ADDR3 + 1 + }; +} + +pub struct StdMem { + base_port: GlobalPortId, + internal_state: Vec, + allow_invalid_access: bool, + width: u32, + addresser: M, +} + +impl StdMem { + declare_ports![ + WRITE_DATA: M::NON_ADDRESS_BASE + 1, + WRITE_EN: M::NON_ADDRESS_BASE + 2, + CLK: M::NON_ADDRESS_BASE + 3, + RESET: M::NON_ADDRESS_BASE + 4, + READ_DATA: M::NON_ADDRESS_BASE + 5, + DONE: M::NON_ADDRESS_BASE + 6 + ]; + + make_getters![base_port; + write_data: Self::WRITE_DATA, + write_en: Self::WRITE_EN, + reset_port: Self::RESET, + read_data: Self::READ_DATA, + done: Self::DONE + ]; +} + +impl Primitive for StdMem { + fn exec_comb(&self, port_map: &PortMap) -> Results { + let addr = self.addresser.calculate_addr(port_map, self.base_port); + let read_data = self.read_data(); + if addr < self.internal_state.len() { + Ok(output![read_data: self.internal_state[addr].clone()]) + } else { + // throw error on cycle boundary rather than here + Ok(output![read_data: Value::zeroes(self.width)]) + } + } + + fn exec_cycle(&mut self, port_map: &PortMap) -> Results { + let reset = port_map[self.reset_port()].as_bool(); + let write_en = port_map[self.write_en()].as_bool(); + let addr = self.addresser.calculate_addr(port_map, self.base_port); + let (read_data, done) = (self.read_data(), self.done()); + + if write_en && !reset { + let write_data = port_map[self.write_data()].clone(); + self.internal_state[addr] = write_data; + Ok( + output![read_data: self.internal_state[addr].clone(), done: Value::bit_high()], + ) + } else { + Ok( + output![read_data: self.internal_state[addr].clone(), done: Value::bit_low()], + ) + } + } + + fn reset(&mut self, _port_map: &PortMap) -> Results { + let (read_data, done) = (self.read_data(), self.done()); + Ok( + output![read_data: Value::zeroes(self.width), done: Value::bit_low()], + ) + } + + fn serialize( + &self, + _code: Option, + ) -> Serializable { + todo!("StdMemD1::serialize") + } + + fn has_serializable_state(&self) -> bool { + true + } +} + +// type aliases +pub type StdMemD1 = StdMem>; +pub type StdMemD2 = StdMem>; +pub type StdMemD3 = StdMem>; +pub type StdMemD4 = StdMem>; + +impl StdMemD1 { + pub fn new( + base: GlobalPortId, + width: u32, + allow_invalid: bool, + size: usize, + ) -> Self { + let internal_state = vec![Value::zeroes(width); size]; + + Self { + base_port: base, + internal_state, + allow_invalid_access: allow_invalid, + width, + addresser: MemD1::, + } + } +} + +impl StdMemD2 { + pub fn new( + base: GlobalPortId, + width: u32, + allow_invalid: bool, + size: (usize, usize), + ) -> Self { + let internal_state = vec![Value::zeroes(width); size.0 * size.1]; + + Self { + base_port: base, + internal_state, + allow_invalid_access: allow_invalid, + width, + addresser: MemD2:: { d1_size: size.1 }, + } + } +} + +impl StdMemD3 { + pub fn new( + base: GlobalPortId, + width: u32, + allow_invalid: bool, + size: (usize, usize, usize), + ) -> Self { + let internal_state = + vec![Value::zeroes(width); size.0 * size.1 * size.2]; + + Self { + base_port: base, + internal_state, + allow_invalid_access: allow_invalid, + width, + addresser: MemD3:: { + d1_size: size.1, + d2_size: size.2, + }, + } + } +} + +impl StdMemD4 { + pub fn new( + base: GlobalPortId, + width: u32, + allow_invalid: bool, + size: (usize, usize, usize, usize), + ) -> Self { + let internal_state = + vec![Value::zeroes(width); size.0 * size.1 * size.2 * size.3]; + + Self { + base_port: base, + internal_state, + allow_invalid_access: allow_invalid, + width, + addresser: MemD4:: { + d1_size: size.1, + d2_size: size.2, + d3_size: size.3, + }, + } + } +} diff --git a/interp/src/flatten/primitives/stateful/mod.rs b/interp/src/flatten/primitives/stateful/mod.rs index 66aaf7520c..d21b3caaff 100644 --- a/interp/src/flatten/primitives/stateful/mod.rs +++ b/interp/src/flatten/primitives/stateful/mod.rs @@ -1,3 +1,4 @@ +pub mod math; pub mod memories; pub use memories::*; diff --git a/interp/src/flatten/structures/environment.rs b/interp/src/flatten/structures/environment.rs deleted file mode 100644 index 81d1ae0392..0000000000 --- a/interp/src/flatten/structures/environment.rs +++ /dev/null @@ -1,386 +0,0 @@ -use ahash::{HashMap, HashMapExt}; -use itertools::Itertools; -use smallvec::SmallVec; - -use super::{context::Context, indexed_map::IndexedMap}; -use crate::{ - flatten::{ - flat_ir::prelude::{ - BaseIndices, ComponentIdx, ControlIdx, ControlNode, GlobalCellId, - GlobalPortId, GlobalRefCellId, GlobalRefPortId, - }, - primitives::{self, Primitive}, - structures::index_trait::IndexRef, - }, - values::Value, -}; -use std::fmt::Debug; - -pub(crate) type PortMap = IndexedMap; -pub(crate) type CellMap = IndexedMap; -pub(crate) type RefCellMap = IndexedMap>; -pub(crate) type RefPortMap = IndexedMap>; - -pub(crate) struct ComponentLedger { - pub(crate) index_bases: BaseIndices, - pub(crate) comp_id: ComponentIdx, -} - -pub(crate) enum CellLedger { - Primitive { - // wish there was a better option with this one - cell_dyn: Box, - }, - Component(ComponentLedger), -} - -impl CellLedger { - fn comp(idx: ComponentIdx, env: &Environment) -> Self { - Self::Component(ComponentLedger { - index_bases: BaseIndices::new( - env.ports.peek_next_idx(), - (env.cells.peek_next_idx().index() + 1).into(), - env.ref_cells.peek_next_idx(), - env.ref_ports.peek_next_idx(), - ), - comp_id: idx, - }) - } - - pub fn as_comp(&self) -> Option<&ComponentLedger> { - match self { - Self::Component(comp) => Some(comp), - _ => None, - } - } -} - -impl Debug for CellLedger { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Primitive { .. } => f.debug_struct("Primitive").finish(), - Self::Component(ComponentLedger { - index_bases, - comp_id, - }) => f - .debug_struct("Component") - .field("index_bases", index_bases) - .field("comp_id", comp_id) - .finish(), - } - } -} - -/// Simple struct containing both the component instance and the active leaf -/// node in the component -#[derive(Debug)] -pub struct ControlPoint { - pub comp: GlobalCellId, - pub control_leaf: ControlIdx, -} - -impl ControlPoint { - pub fn new(comp: GlobalCellId, control_leaf: ControlIdx) -> Self { - Self { comp, control_leaf } - } -} - -/// The number of control points to preallocate for the program counter. -/// Using 1 for now, as this is the same size as using a vec, but this can -/// change in the future and probably should. -const CONTROL_POINT_PREALLOCATE: usize = 1; - -/// The program counter for the whole program execution. Wraps over a vector of -/// the active leaf statements for each component instance. -#[derive(Debug, Default)] -pub(crate) struct ProgramCounter { - vec: SmallVec<[ControlPoint; CONTROL_POINT_PREALLOCATE]>, -} - -impl ProgramCounter { - pub fn new(ctx: &Context) -> Self { - let root = ctx.entry_point; - // TODO: this relies on the fact that we construct the root cell-ledger - // as the first possible cell in the program. If that changes this will break. - let root_cell = GlobalCellId::new(0); - - let mut vec = SmallVec::new(); - if let Some(current) = ctx.primary[root].control { - let mut work_queue: Vec = Vec::from([current]); - let mut backtrack_map = HashMap::new(); - - while let Some(current) = work_queue.pop() { - match &ctx.primary[current] { - ControlNode::Empty(_) => { - vec.push(ControlPoint::new(root_cell, current)) - } - ControlNode::Enable(_) => { - vec.push(ControlPoint::new(root_cell, current)) - } - ControlNode::Seq(s) => match s - .stms() - .iter() - .find(|&x| !backtrack_map.contains_key(x)) - { - Some(n) => { - backtrack_map.insert(*n, current); - work_queue.push(*n); - } - None => { - if let Some(b) = backtrack_map.get(¤t) { - work_queue.push(*b) - } - } - }, - ControlNode::Par(p) => { - for node in p.stms() { - work_queue.push(*node); - } - } - ControlNode::If(_) => { - vec.push(ControlPoint::new(root_cell, current)) - } - ControlNode::While(_) => { - vec.push(ControlPoint::new(root_cell, current)) - } - ControlNode::Invoke(_) => { - vec.push(ControlPoint::new(root_cell, current)) - } - } - } - } else { - todo!( - "Flat interpreter does not support control-less components yet" - ) - } - - Self { vec } - } - - pub fn iter(&self) -> std::slice::Iter<'_, ControlPoint> { - self.vec.iter() - } - - pub fn is_done(&self) -> bool { - self.vec.is_empty() - } -} - -impl<'a> IntoIterator for &'a ProgramCounter { - type Item = &'a ControlPoint; - - type IntoIter = std::slice::Iter<'a, ControlPoint>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -#[derive(Debug)] -pub struct Environment<'a> { - /// A map from global port IDs to their current values. - pub(crate) ports: PortMap, - /// A map from global cell IDs to their current state and execution info. - cells: CellMap, - /// A map from global ref cell IDs to the cell they reference, if any. - ref_cells: RefCellMap, - /// A map from global ref port IDs to the port they reference, if any. - ref_ports: RefPortMap, - - /// The program counter for the whole program execution. - pcs: ProgramCounter, - - /// The immutable context. This is retained for ease of use. - ctx: &'a Context, -} - -impl<'a> Environment<'a> { - pub fn new(ctx: &'a Context) -> Self { - let root = ctx.entry_point; - let aux = &ctx.secondary[root]; - - let mut env = Self { - ports: PortMap::with_capacity(aux.port_offset_map.count()), - cells: CellMap::with_capacity(aux.cell_offset_map.count()), - ref_cells: RefCellMap::with_capacity( - aux.ref_cell_offset_map.count(), - ), - ref_ports: RefPortMap::with_capacity( - aux.ref_port_offset_map.count(), - ), - pcs: ProgramCounter::new(ctx), - ctx, - }; - - let root_node = CellLedger::comp(root, &env); - let root = env.cells.push(root_node); - env.layout_component(root); - - env - } - - fn layout_component(&mut self, comp: GlobalCellId) { - let ComponentLedger { - index_bases, - comp_id, - } = self.cells[comp] - .as_comp() - .expect("Called layout component with a non-component cell."); - let comp_aux = &self.ctx.secondary[*comp_id]; - - let comp_id = *comp_id; - - // first layout the signature - for sig_port in comp_aux.signature.iter() { - let width = self.ctx.lookup_port_def(&comp_id, sig_port).width; - let idx = self.ports.push(Value::zeroes(width)); - debug_assert_eq!(index_bases + sig_port, idx); - } - // second group ports - for group_idx in comp_aux.definitions.groups() { - // TODO Griffin: The sanity checks here are assuming that go & done - // are defined in that order. This could break if the IR changes the - // order on hole ports in groups. - - //go - let go = self.ports.push(Value::bit_low()); - debug_assert_eq!(go, index_bases + self.ctx.primary[group_idx].go); - - //done - let done = self.ports.push(Value::bit_low()); - debug_assert_eq!( - done, - index_bases + self.ctx.primary[group_idx].done - ); - } - - for (cell_off, def_idx) in comp_aux.cell_offset_map.iter() { - let info = &self.ctx.secondary[*def_idx]; - if !info.prototype.is_component() { - let port_base = self.ports.peek_next_idx(); - for port in info.ports.iter() { - let width = self.ctx.lookup_port_def(&comp_id, port).width; - let idx = self.ports.push(Value::zeroes(width)); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + port, - idx - ); - } - let cell_dyn = - primitives::build_primitive(self, info, port_base); - let cell = self.cells.push(CellLedger::Primitive { cell_dyn }); - - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + cell_off, - cell - ); - } else { - let child_comp = info.prototype.as_component().unwrap(); - let child_comp = CellLedger::comp(*child_comp, self); - - let cell = self.cells.push(child_comp); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + cell_off, - cell - ); - - self.layout_component(cell); - } - } - - // ref cells and ports are initialized to None - for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { - let info = &self.ctx.secondary[*def_idx]; - for port_idx in info.ports.iter() { - let port_actual = self.ref_ports.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + port_idx, - port_actual - ) - } - let cell_actual = self.ref_cells.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, - cell_actual - ) - } - } - - pub fn print_env(&self) { - let root_idx = GlobalCellId::new(0); - let mut hierarchy = Vec::new(); - self.print_component(root_idx, &mut hierarchy) - } - - fn print_component( - &self, - target: GlobalCellId, - hierarchy: &mut Vec, - ) { - let info = self.cells[target].as_comp().unwrap(); - let comp = &self.ctx.secondary[info.comp_id]; - hierarchy.push(target); - - // This funky iterator chain first pulls the first element (the - // entrypoint) and extracts its name. Subsequent element are pairs of - // global offsets produced by a staggered iteration, yielding `(root, - // child)` then `(child, grandchild)` and so on. All the strings are - // finally collected and concatenated with a `.` separator to produce - // the fully qualified name prefix for the given component instance. - let name_prefix = hierarchy - .first() - .iter() - .map(|x| { - let info = self.cells[**x].as_comp().unwrap(); - let prior_comp = &self.ctx.secondary[info.comp_id]; - &self.ctx.secondary[prior_comp.name] - }) - .chain(hierarchy.iter().zip(hierarchy.iter().skip(1)).map( - |(l, r)| { - let info = self.cells[*l].as_comp().unwrap(); - let prior_comp = &self.ctx.secondary[info.comp_id]; - let local_target = r - (&info.index_bases); - - let def_idx = &prior_comp.cell_offset_map[local_target]; - - let id = &self.ctx.secondary[*def_idx]; - &self.ctx.secondary[id.name] - }, - )) - .join("."); - - for (cell_off, def_idx) in comp.cell_offset_map.iter() { - let definition = &self.ctx.secondary[*def_idx]; - - println!("{}.{}", name_prefix, self.ctx.secondary[definition.name]); - for port in definition.ports.iter() { - let definition = - &self.ctx.secondary[comp.port_offset_map[port]]; - println!( - " {}: {}", - self.ctx.secondary[definition.name], - self.ports[&info.index_bases + port] - ); - } - - if definition.prototype.is_component() { - let child_target = &info.index_bases + cell_off; - self.print_component(child_target, hierarchy); - } - } - - hierarchy.pop(); - } - - pub fn print_env_stats(&self) { - println!("Environment Stats:"); - println!(" Ports: {}", self.ports.len()); - println!(" Cells: {}", self.cells.len()); - println!(" Ref Cells: {}", self.ref_cells.len()); - println!(" Ref Ports: {}", self.ref_ports.len()); - } - - pub fn print_pc(&self) { - println!("{:?}", self.pcs) - } -} diff --git a/interp/src/flatten/structures/environment/mod.rs b/interp/src/flatten/structures/environment/mod.rs new file mode 100644 index 0000000000..7814567690 --- /dev/null +++ b/interp/src/flatten/structures/environment/mod.rs @@ -0,0 +1,773 @@ +mod program_counter; + +use itertools::Itertools; + +use self::program_counter::ProgramCounter; + +use super::{ + context::Context, index_trait::IndexRange, indexed_map::IndexedMap, +}; +use crate::{ + errors::InterpreterResult, + flatten::{ + flat_ir::{ + prelude::{ + Assignment, AssignmentIdx, BaseIndices, ComponentIdx, + ControlIdx, ControlNode, GlobalCellId, GlobalPortId, + GlobalPortRef, GlobalRefCellId, GlobalRefPortId, GuardIdx, + PortRef, + }, + wires::guards::Guard, + }, + primitives::{self, Primitive}, + structures::index_trait::IndexRef, + }, + values::Value, +}; +use std::{collections::VecDeque, fmt::Debug}; + +pub(crate) type PortMap = IndexedMap; +pub(crate) type CellMap = IndexedMap; +pub(crate) type RefCellMap = IndexedMap>; +pub(crate) type RefPortMap = IndexedMap>; +type AssignmentRange = IndexRange; + +pub(crate) struct ComponentLedger { + pub(crate) index_bases: BaseIndices, + pub(crate) comp_id: ComponentIdx, +} + +impl ComponentLedger { + /// Convert a relative offset to a global one. Perhaps should take an owned + /// value rather than a pointer + pub fn convert_to_global(&self, port: &PortRef) -> GlobalPortRef { + match port { + PortRef::Local(l) => (&self.index_bases + l).into(), + PortRef::Ref(r) => (&self.index_bases + r).into(), + } + } +} + +/// An enum encapsulating cell functionality. It is either a pointer to a +/// primitive or information about a calyx component instance +pub(crate) enum CellLedger { + Primitive { + // wish there was a better option with this one + cell_dyn: Box, + }, + Component(ComponentLedger), +} + +impl CellLedger { + fn new_comp(idx: ComponentIdx, env: &Environment) -> Self { + Self::Component(ComponentLedger { + index_bases: BaseIndices::new( + env.ports.peek_next_idx(), + (env.cells.peek_next_idx().index() + 1).into(), + env.ref_cells.peek_next_idx(), + env.ref_ports.peek_next_idx(), + ), + comp_id: idx, + }) + } + + pub fn as_comp(&self) -> Option<&ComponentLedger> { + match self { + Self::Component(comp) => Some(comp), + _ => None, + } + } + + #[inline] + pub fn unwrap_comp(&self) -> &ComponentLedger { + self.as_comp() + .expect("Unwrapped cell ledger as component but received primitive") + } +} + +impl Debug for CellLedger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Primitive { .. } => f.debug_struct("Primitive").finish(), + Self::Component(ComponentLedger { + index_bases, + comp_id, + }) => f + .debug_struct("Component") + .field("index_bases", index_bases) + .field("comp_id", comp_id) + .finish(), + } + } +} + +#[derive(Debug)] +pub struct Environment<'a> { + /// A map from global port IDs to their current values. + pub(crate) ports: PortMap, + /// A map from global cell IDs to their current state and execution info. + cells: CellMap, + /// A map from global ref cell IDs to the cell they reference, if any. + ref_cells: RefCellMap, + /// A map from global ref port IDs to the port they reference, if any. + ref_ports: RefPortMap, + + /// The program counter for the whole program execution. + pc: ProgramCounter, + + /// The immutable context. This is retained for ease of use. + ctx: &'a Context, +} + +impl<'a> Environment<'a> { + pub fn new(ctx: &'a Context) -> Self { + let root = ctx.entry_point; + let aux = &ctx.secondary[root]; + + let mut env = Self { + ports: PortMap::with_capacity(aux.port_offset_map.count()), + cells: CellMap::with_capacity(aux.cell_offset_map.count()), + ref_cells: RefCellMap::with_capacity( + aux.ref_cell_offset_map.count(), + ), + ref_ports: RefPortMap::with_capacity( + aux.ref_port_offset_map.count(), + ), + pc: ProgramCounter::new(ctx), + ctx, + }; + + let root_node = CellLedger::new_comp(root, &env); + let root = env.cells.push(root_node); + env.layout_component(root); + + env + } + + /// Internal function used to layout a given component from a cell id + /// + /// Layout is handled in the following order: + /// 1. component signature (input/output) + /// 2. group hole ports + /// 3. cells + ports, primitive + /// 4. sub-components + /// 5. ref-cells & ports + fn layout_component(&mut self, comp: GlobalCellId) { + let ComponentLedger { + index_bases, + comp_id, + } = self.cells[comp] + .as_comp() + .expect("Called layout component with a non-component cell."); + let comp_aux = &self.ctx.secondary[*comp_id]; + + let comp_id = *comp_id; + + // first layout the signature + for sig_port in comp_aux.signature.iter() { + let width = self.ctx.lookup_port_def(&comp_id, sig_port).width; + let idx = self.ports.push(Value::zeroes(width)); + debug_assert_eq!(index_bases + sig_port, idx); + } + // second group ports + for group_idx in comp_aux.definitions.groups() { + //go + let go = self.ports.push(Value::bit_low()); + + //done + let done = self.ports.push(Value::bit_low()); + + // quick sanity check asserts + let go_actual = index_bases + self.ctx.primary[group_idx].go; + let done_actual = index_bases + self.ctx.primary[group_idx].done; + // Case 1 - Go defined before done + if self.ctx.primary[group_idx].go < self.ctx.primary[group_idx].done + { + debug_assert_eq!(done, done_actual); + debug_assert_eq!(go, go_actual); + } + // Case 2 - Done defined before go + else { + // in this case go is defined after done, so our variable names + // are backward, but this is not a problem since they are + // initialized to the same value + debug_assert_eq!(go, done_actual); + debug_assert_eq!(done, go_actual); + } + } + + for (cell_off, def_idx) in comp_aux.cell_offset_map.iter() { + let info = &self.ctx.secondary[*def_idx]; + if !info.prototype.is_component() { + let port_base = self.ports.peek_next_idx(); + for port in info.ports.iter() { + let width = self.ctx.lookup_port_def(&comp_id, port).width; + let idx = self.ports.push(Value::zeroes(width)); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + port, + idx + ); + } + let cell_dyn = + primitives::build_primitive(self, info, port_base); + let cell = self.cells.push(CellLedger::Primitive { cell_dyn }); + + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + cell_off, + cell + ); + } else { + let child_comp = info.prototype.as_component().unwrap(); + let child_comp = CellLedger::new_comp(*child_comp, self); + + let cell = self.cells.push(child_comp); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + cell_off, + cell + ); + + self.layout_component(cell); + } + } + + // ref cells and ports are initialized to None + for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { + let info = &self.ctx.secondary[*def_idx]; + for port_idx in info.ports.iter() { + let port_actual = self.ref_ports.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + port_idx, + port_actual + ) + } + let cell_actual = self.ref_cells.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, + cell_actual + ) + } + } +} + +// ===================== Environment print implementations ===================== +impl<'a> Environment<'a> { + pub fn print_env(&self) { + let root_idx = GlobalCellId::new(0); + let mut hierarchy = Vec::new(); + self.print_component(root_idx, &mut hierarchy) + } + + fn print_component( + &self, + target: GlobalCellId, + hierarchy: &mut Vec, + ) { + let info = self.cells[target].as_comp().unwrap(); + let comp = &self.ctx.secondary[info.comp_id]; + hierarchy.push(target); + + // This funky iterator chain first pulls the first element (the + // entrypoint) and extracts its name. Subsequent element are pairs of + // global offsets produced by a staggered iteration, yielding `(root, + // child)` then `(child, grandchild)` and so on. All the strings are + // finally collected and concatenated with a `.` separator to produce + // the fully qualified name prefix for the given component instance. + let name_prefix = hierarchy + .first() + .iter() + .map(|x| { + let info = self.cells[**x].as_comp().unwrap(); + let prior_comp = &self.ctx.secondary[info.comp_id]; + &self.ctx.secondary[prior_comp.name] + }) + .chain(hierarchy.iter().zip(hierarchy.iter().skip(1)).map( + |(l, r)| { + let info = self.cells[*l].as_comp().unwrap(); + let prior_comp = &self.ctx.secondary[info.comp_id]; + let local_target = r - (&info.index_bases); + + let def_idx = &prior_comp.cell_offset_map[local_target]; + + let id = &self.ctx.secondary[*def_idx]; + &self.ctx.secondary[id.name] + }, + )) + .join("."); + + for (cell_off, def_idx) in comp.cell_offset_map.iter() { + let definition = &self.ctx.secondary[*def_idx]; + + println!("{}.{}", name_prefix, self.ctx.secondary[definition.name]); + for port in definition.ports.iter() { + let definition = + &self.ctx.secondary[comp.port_offset_map[port]]; + println!( + " {}: {}", + self.ctx.secondary[definition.name], + self.ports[&info.index_bases + port] + ); + } + + if definition.prototype.is_component() { + let child_target = &info.index_bases + cell_off; + self.print_component(child_target, hierarchy); + } + } + + hierarchy.pop(); + } + + pub fn print_env_stats(&self) { + println!("Environment Stats:"); + println!(" Ports: {}", self.ports.len()); + println!(" Cells: {}", self.cells.len()); + println!(" Ref Cells: {}", self.ref_cells.len()); + println!(" Ref Ports: {}", self.ref_ports.len()); + } + + pub fn print_pc(&self) { + println!("{:?}", self.pc) + } +} + +/// A wrapper struct for the environment that provides the functions used to +/// simulate the actual program +pub struct Simulator<'a> { + env: Environment<'a>, +} + +impl<'a> Simulator<'a> { + pub fn new(env: Environment<'a>) -> Self { + Self { env } + } + + pub fn print_env(&self) { + self.env.print_env() + } + + pub fn ctx(&self) -> &Context { + self.env.ctx + } +} + +// =========================== simulation functions =========================== +impl<'a> Simulator<'a> { + /// Finds the next control point from a finished control point. If there is + /// no next control point, returns None. + /// + /// If given an If/While statement this will assume the entire if/while node + /// is finished executing and will ascend to the parent context. Evaluating + /// the if/while condition and moving to the appropriate body must be + /// handled elsewhere. + // fn find_next_control_point( + // &self, + // target: &ControlPoint, + // ) -> NextControlPoint { + // let comp = target.comp; + // let comp_idx = self.env.cells[comp].as_comp().unwrap().comp_id; + // let root_ctrl = self.env.ctx.primary[comp_idx].control.expect( + // "Called `find_next_control_point` on a component with no control. This is an error, please report it", + // ); + + // // here's the goal: + // // we want to first walk the control tree for this component and find + // // the given control point and the path through the tree to get there. + // // Once we have this, we move back along the path to find the next + // // node to run (either a terminal node in an `invoke` or `enable` or a + // // non-terminal while/if condition check). Since this involves + // // backtracking, in the limit this means backtracking all the way to the + // // root of the component in which case the component has finished + // // executing. + + // let cont = self.extract_next_search(root_ctrl); + + // let mut search_stack = Vec::from([SearchNode { + // node: root_ctrl, + // next: cont, + // }]); + + // while let Some(mut node) = search_stack.pop() { + // if node.node == target.control_leaf { + // // found the node! we return it to the stack which is now the + // // path from the root to our finished node + // search_stack.push(node); + // break; + // } else if let Some(next_node) = node.next.pop_front() { + // let next_search_node = SearchNode { + // node: next_node, + // next: self.extract_next_search(next_node), + // }; + // // return the now modified original + // search_stack.push(node); + // // push the descendent next, so that the search continues + // // depth first + // search_stack.push(next_search_node); + // } else { + // // This node was not the one we wanted and none of its + // // children (if any) were either, we must return to the + // // parent which means dropping the node, i.e. doing nothing here + // } + // } + + // if search_stack.is_empty() { + // // The reason this should never happen is that this implies a + // // controlpoint was constructed for a fully-structural component + // // instance which means something went wrong with the construction + // // as such an instance could not have a control program to reference + // panic!("Could not find control point in component, this should never happen. Please report this error.") + // } + + // // phase two, backtrack to find the next node to run + + // // remove the deepest node (i.e. our target) + // search_stack.pop(); + + // let mut immediate_next_node = None; + + // while let Some(node) = search_stack.pop() { + // match &self.ctx().primary[node.node] { + // ControlNode::Seq(_) => { + // if let Some(next) = node.next.get(0) { + // // the target node will have been popped off the + // // list during the search meaning the next node left + // // over from the search is the next node to run + // immediate_next_node = Some(*next); + // // exit to descend the list + // break; + // } else { + // // no next node, go to parent context + // } + // }, + // ControlNode::Par(_) => { + // // par arm needs to wait until all arms are finished + // return NextControlPoint::FinishedParChild(ControlPoint::new(comp, node.node)); + // }, + // ControlNode::If(_) => { + // // do nothing, go to parent context + // }, + // ControlNode::While(_) => { + // // need to recheck condition so the while itself is next + // return NextControlPoint::Next(ControlPoint::new(comp, node.node)); + // }, + // // + // ControlNode::Empty(_) + // | ControlNode::Enable(_) + // | ControlNode::Invoke(_) => unreachable!("terminal nodes cannot be the parents of a node. If this happens something has gone horribly wrong and should be reported"), + // } + // } + + // // phase 3, take the immediate next node and descend to find its leaf + + // if let Some(node) = immediate_next_node { + // // we reuse the existing search stack without resetting it to allow + // // backtracking further if the immediate next node has no actual + // // leaves under it, e.g. a seq of empty seqs + // // TODO Griffin: double check this aspect later as it might + // // complicate things or introduce errors + // self.descend_to_leaf(node, &mut search_stack, comp) + // } else { + // // if we exit without finding the next node then it does not exist + // NextControlPoint::None + // } + // } + + /// pull out the next nodes to search when + fn extract_next_search(&self, idx: ControlIdx) -> VecDeque { + match &self.env.ctx.primary[idx] { + ControlNode::Seq(s) => s.stms().iter().copied().collect(), + ControlNode::Par(p) => p.stms().iter().copied().collect(), + ControlNode::If(i) => vec![i.tbranch(), i.fbranch()].into(), + ControlNode::While(w) => vec![w.body()].into(), + _ => VecDeque::new(), + } + } + + fn lookup_global_port_id(&self, port: GlobalPortRef) -> GlobalPortId { + match port { + GlobalPortRef::Port(p) => p, + // TODO Griffin: Please make sure this error message is correct with + // respect to the compiler + GlobalPortRef::Ref(r) => self.env.ref_ports[r].expect("A ref port is being queried without a supplied ref-cell. This is an error?"), + } + } + + fn get_global_idx( + &self, + port: &PortRef, + comp: GlobalCellId, + ) -> GlobalPortId { + let ledger = self.env.cells[comp].unwrap_comp(); + self.lookup_global_port_id(ledger.convert_to_global(port)) + } + + fn get_value(&self, port: &PortRef, comp: GlobalCellId) -> &Value { + let port_idx = self.get_global_idx(port, comp); + &self.env.ports[port_idx] + } + + // fn descend_to_leaf( + // &self, + // // the node (possibly terminal) which we want to start evaluating + // immediate_next_node: ControlIdx, + // search_stack: &mut Vec, + // comp: GlobalCellId, + // ) -> NextControlPoint { + // search_stack.push(SearchNode { + // node: immediate_next_node, + // next: self.extract_next_search(immediate_next_node), + // }); + + // while let Some(mut node) = search_stack.pop() { + // match &self.ctx().primary[node.node] { + // ControlNode::Seq(_) => { + // if let Some(next) = node.next.pop_front() { + // search_stack.push(node); + // let next_search_node = SearchNode { + // node: next, + // next: self.extract_next_search(next), + // }; + // search_stack.push(next_search_node); + // } else { + // // this seq does not contain any more nodes. + // // Currently only possible if the seq is empty + // } + // }, + + // ControlNode::Par(p) => { + // let mut ctrl_points = vec![]; + // let mut pars_activated = vec![]; + + // let mut this_par = (ControlPoint::new(comp, node.node), p.stms().len() as u32); + + // // TODO Griffin: Maybe consider making this not + // // recursive in the future + // for arm in p.stms().iter().map( |x| { + // self.descend_to_leaf(*x, &mut vec![], comp) + // }) { + // match arm { + // NextControlPoint::None => { + // this_par.1 -= 1; + // }, + // NextControlPoint::Next(c) => ctrl_points.push(c), + // NextControlPoint::FinishedParChild(_) => unreachable!("I think this impossible"), + // NextControlPoint::StartedParChild(nodes, pars) => { + // ctrl_points.extend(nodes); + // pars_activated.extend(pars); + // }, + // } + // } + + // if this_par.1 != 0 { + // pars_activated.push(this_par); + // return NextControlPoint::StartedParChild(ctrl_points, pars_activated) + // } else { + // // there were no next nodes under this par, so we + // // ascend the search tree and continue + // } + // } + + // // functionally terminals for the purposes of needing to be + // // seen in the control program and given extra treatment + // ControlNode::If(_) + // | ControlNode::While(_) + // // actual terminals + // | ControlNode::Invoke(_) + // | ControlNode::Enable(_) + // // might not want this here in the future, but makes sense + // // if we think about annotations on empty groups. + // | ControlNode::Empty(_)=> { + // return NextControlPoint::Next(ControlPoint::new(comp, node.node)) + // } + // } + // } + // NextControlPoint::None + // } + + // may want to make this iterate directly if it turns out that the vec + // allocation is too expensive in this context + fn get_assignments(&self) -> AssignmentBundle { + // maybe should give this a capacity equivalent to the number of + // elements in the program counter? It would be a bit of an over + // approximation + let mut out = AssignmentBundle::new(); + for node in self.env.pc.iter() { + match &self.ctx().primary[node.control_leaf] { + ControlNode::Empty(_) => { + // don't need to add any assignments here + } + ControlNode::Enable(e) => { + out.assigns.push((node.comp, self.ctx().primary[e.group()].assignments)) + } + + ControlNode::Invoke(_) => todo!("invokes not yet implemented"), + // The reason this shouldn't happen is that the program counter + // should've processed these nodes into their children and + // stored the needed auxillary data for par structures + ControlNode::If(_) | ControlNode::While(_) => panic!("If/While nodes are present in the control program when `get_assignments` is called. This is an error, please report it."), + ControlNode::Seq(_) | ControlNode::Par(_) => unreachable!(), + } + } + + out + } + + pub fn step(&mut self) -> InterpreterResult<()> { + // place to keep track of what groups we need to conclude at the end of + // this step. These are indices into the program counter + + // we want to iterate through all the nodes present in the program counter + + // first we need to check for conditional control nodes + + self.simulate_combinational(); + + todo!() + } + + fn evaluate_guard(&self, guard: GuardIdx, comp: GlobalCellId) -> bool { + let guard = &self.ctx().primary[guard]; + match guard { + Guard::True => true, + Guard::Or(a, b) => { + self.evaluate_guard(*a, comp) || self.evaluate_guard(*b, comp) + } + Guard::And(a, b) => { + self.evaluate_guard(*a, comp) || self.evaluate_guard(*b, comp) + } + Guard::Not(n) => !self.evaluate_guard(*n, comp), + Guard::Comp(c, a, b) => { + let comp_v = self.env.cells[comp].unwrap_comp(); + + let a = self.lookup_global_port_id(comp_v.convert_to_global(a)); + let b = self.lookup_global_port_id(comp_v.convert_to_global(b)); + + let a_val = &self.env.ports[a]; + let b_val = &self.env.ports[b]; + match c { + calyx_ir::PortComp::Eq => a_val == b_val, + calyx_ir::PortComp::Neq => a_val != b_val, + calyx_ir::PortComp::Gt => a_val > b_val, + calyx_ir::PortComp::Lt => a_val < b_val, + calyx_ir::PortComp::Geq => a_val >= b_val, + calyx_ir::PortComp::Leq => a_val <= b_val, + } + } + Guard::Port(p) => { + let comp_v = self.env.cells[comp].unwrap_comp(); + let p_idx = + self.lookup_global_port_id(comp_v.convert_to_global(p)); + self.env.ports[p_idx].as_bool() + } + } + } + + fn simulate_combinational(&mut self) { + let assigns = self.get_assignments(); + let mut has_changed = true; + + // This is an upper-bound, i.e. if every assignment succeeds then there + // will be this many entries + let mut updates_vec: Vec<(GlobalPortId, Value)> = + Vec::with_capacity(assigns.len()); + + while has_changed { + let updates = assigns.iter_over_assignments(self.ctx()).filter_map( + |(comp_idx, assign)| { + if self.evaluate_guard(assign.guard, comp_idx) { + let val = self.get_value(&assign.src, comp_idx); + Some(( + self.get_global_idx(&assign.dst, comp_idx), + val.clone(), + )) + } else { + None + } + }, + ); + + // want to buffer all updates before committing. It's not ideal to + // be doing this in a tight loop. + updates_vec.extend(updates); + + for (dest, val) in updates_vec.drain(..) { + if self.env.ports[dest] != val { + has_changed = true + } + self.env.ports[dest] = val; + } + } + } + + pub fn _main_test(&mut self) { + self.env.print_pc(); + for _x in self.env.pc.iter() { + // println!("{:?} next {:?}", x, self.find_next_control_point(x)) + } + println!("{:?}", self.get_assignments()) + } +} + +/// A collection of assignments represented using a series of half-open ranges +/// via [AssignmentRange] +#[derive(Debug)] +struct AssignmentBundle { + assigns: Vec<(GlobalCellId, AssignmentRange)>, +} + +impl AssignmentBundle { + fn new() -> Self { + Self { + assigns: Vec::new(), + } + } + + fn with_capacity(size: usize) -> Self { + Self { + assigns: Vec::with_capacity(size), + } + } + + #[inline] + pub fn push(&mut self, value: (GlobalCellId, AssignmentRange)) { + self.assigns.push(value) + } + + pub fn iter( + &self, + ) -> impl Iterator { + self.assigns.iter() + } + + pub fn iter_over_indices( + &self, + ) -> impl Iterator + '_ { + self.assigns + .iter() + .flat_map(|(c, x)| x.iter().map(|y| (*c, y))) + } + + pub fn iter_over_assignments<'a>( + &'a self, + ctx: &'a Context, + ) -> impl Iterator { + self.iter_over_indices() + .map(|(c, idx)| (c, &ctx.primary[idx])) + } + + /// The total number of assignments. Not the total number of index ranges! + pub fn len(&self) -> usize { + self.assigns + .iter() + .fold(0, |acc, (_, range)| acc + range.size()) + } +} + +impl FromIterator<(GlobalCellId, AssignmentRange)> for AssignmentBundle { + fn from_iter>( + iter: T, + ) -> Self { + Self { + assigns: iter.into_iter().collect(), + } + } +} diff --git a/interp/src/flatten/structures/environment/program_counter.rs b/interp/src/flatten/structures/environment/program_counter.rs new file mode 100644 index 0000000000..190e789365 --- /dev/null +++ b/interp/src/flatten/structures/environment/program_counter.rs @@ -0,0 +1,301 @@ +use std::num::NonZeroU32; + +use ahash::{HashMap, HashMapExt}; + +use super::super::context::Context; +use crate::flatten::{ + flat_ir::prelude::{ControlIdx, ControlNode, GlobalCellId}, + structures::index_trait::{impl_index_nonzero, IndexRef}, +}; + +use itertools::{FoldWhile, Itertools}; + +/// Simple struct containing both the component instance and the active leaf +/// node in the component +#[derive(Debug, Hash, Eq, PartialEq)] +pub struct ControlPoint { + pub comp: GlobalCellId, + pub control_leaf: ControlIdx, +} + +impl ControlPoint { + pub fn new(comp: GlobalCellId, control_leaf: ControlIdx) -> Self { + Self { comp, control_leaf } + } +} + +#[derive(Debug)] +enum NextControlPoint { + /// no + None, + /// This is the node to run next. The nice friendly singular case + Next(ControlPoint), + /// We just finished the child of this par block and need to decrement its + /// count + FinishedParChild(ControlPoint), + /// We passed through one or more par nodes to reach this leaf (or leaves) + StartedParChild(Vec, Vec<(ControlPoint, u32)>), +} + +/// An index for searching up and down a tree. This is used to index into +/// various control nodes. For If blocks the true branch is denoted by 0 and +/// the false by 1. The same is true for while blocks. For seq and par blocks, +/// it represents the current index into their statement vector. It is not +/// meaningful for other control types. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SearchIndex(NonZeroU32); +impl_index_nonzero!(SearchIndex); + +pub struct SearchNode { + pub node: ControlIdx, + pub search_index: Option, +} + +impl SearchIndex { + const TRUE_BRANCH: usize = 0; + const FALSE_BRANCH: usize = 1; + + /// Returns the next index, i.e. the current index incremented by 1 + fn next(&self) -> Self { + Self::new(self.index() + 1) + } + + fn is_true_branch(&self) -> bool { + self.index() == Self::TRUE_BRANCH + } + + fn is_false_branch(&self) -> bool { + self.index() == Self::FALSE_BRANCH + } +} + +/// A path from a control node (usually root) to some descendent node/leaf in the control tree +pub struct SearchPath { + path: Vec, +} + +impl SearchPath { + fn new() -> Self { + SearchPath { path: vec![] } + } + + pub fn source_node(&self) -> Option<&SearchNode> { + self.path.get(0) + } + + pub fn len(&self) -> usize { + self.path.len() + } + + pub fn is_empty(&self) -> bool { + self.path.is_empty() + } + + pub fn find_path_to_node( + start: ControlIdx, + target: ControlIdx, + context: &Context, + ) -> Self { + let mut current_path = Self::new(); + current_path.path.push(SearchNode { + node: start, + search_index: None, + }); + + while let Some(node) = current_path.path.last_mut() { + if node.node == target { + break; + } + + match &context.primary.control[node.node] { + ControlNode::Empty(_) + | ControlNode::Enable(_) + | ControlNode::Invoke(_) => { + // in this case we reached a terminal node which was not the + // target since we did not break in the above case. So we + // simply remove the current lowest node and ascend the + // stack to continue the DFS. + current_path.path.pop(); + } + ControlNode::Seq(s) => { + if let Some(idx) = &mut node.search_index { + if idx.index() < (s.stms().len() - 1) { + *idx = idx.next(); + } else { + current_path.path.pop(); + continue; + } + } else if !s.stms().is_empty() { + let new_idx = SearchIndex::new(0); + node.search_index = Some(new_idx); + } else { + current_path.path.pop(); + continue; + } + + // unwrap is safe since by this point it has been forced to + // be a Some variant + let new_node = s.stms()[node.search_index.unwrap().index()]; + current_path.path.push(SearchNode { + node: new_node, + search_index: None, + }) + } + // TODO Griffin: figure out how to deduplicate these arms + ControlNode::Par(p) => { + if let Some(idx) = &mut node.search_index { + if idx.index() < (p.stms().len() - 1) { + *idx = idx.next(); + } else { + current_path.path.pop(); + continue; + } + } else if !p.stms().is_empty() { + let new_idx = SearchIndex::new(0); + node.search_index = Some(new_idx); + } else { + current_path.path.pop(); + continue; + } + + // unwrap is safe since by this point it has been forced to + // be a Some variant + let new_node = p.stms()[node.search_index.unwrap().index()]; + current_path.path.push(SearchNode { + node: new_node, + search_index: None, + }) + } + ControlNode::If(_) => todo!(), + ControlNode::While(_) => todo!(), + } + } + + current_path + } + + pub fn find_path_from_root(target: ControlIdx, context: &Context) -> Self { + let root = context + .primary + .components + .iter() + .fold_while(ControlIdx::new(0), |current_root, (_, comp_info)| { + if let Some(index) = comp_info.control { + if index >= current_root && index < target { + FoldWhile::Continue(index) + } else { + FoldWhile::Done(current_root) + } + } else { + FoldWhile::Continue(current_root) + } + }) + .into_inner(); + + Self::find_path_to_node(root, target, context) + } +} + +/// The number of control points to preallocate for the program counter. +const CONTROL_POINT_PREALLOCATE: usize = 16; + +/// The number of children that have yet to finish for a given par arm. I have +/// this a u16 at the moment which is hopefully fine? More than 65,535 parallel +/// children would be a lot. +pub type ChildCount = u16; + +/// The program counter for the whole program execution. Wraps over a vector of +/// the active leaf statements for each component instance. +#[derive(Debug, Default)] +pub(crate) struct ProgramCounter { + vec: Vec, + par_map: HashMap, +} + +// we need a few things from the program counter + +impl ProgramCounter { + pub fn new(ctx: &Context) -> Self { + let root = ctx.entry_point; + // this relies on the fact that we construct the root cell-ledger + // as the first possible cell in the program. If that changes this will break. + let root_cell = GlobalCellId::new(0); + let mut par_map: HashMap = HashMap::new(); + + let mut vec = Vec::with_capacity(CONTROL_POINT_PREALLOCATE); + if let Some(current) = ctx.primary[root].control { + let mut work_queue: Vec = Vec::from([current]); + let mut backtrack_map = HashMap::new(); + + while let Some(current) = work_queue.pop() { + match &ctx.primary[current] { + ControlNode::Empty(_) => { + vec.push(ControlPoint::new(root_cell, current)) + } + ControlNode::Enable(_) => { + vec.push(ControlPoint::new(root_cell, current)) + } + ControlNode::Seq(s) => match s + .stms() + .iter() + .find(|&x| !backtrack_map.contains_key(x)) + { + Some(n) => { + backtrack_map.insert(*n, current); + work_queue.push(*n); + } + None => { + if let Some(b) = backtrack_map.get(¤t) { + work_queue.push(*b) + } + } + }, + ControlNode::Par(p) => { + par_map.insert( + ControlPoint::new(root_cell, current), + p.stms().len().try_into().expect( + "number of par arms does not fit into the default size value. Please let us know so that we can adjust the datatype accordingly", + ), + ); + for node in p.stms() { + work_queue.push(*node); + } + } + ControlNode::If(_) => { + vec.push(ControlPoint::new(root_cell, current)) + } + ControlNode::While(_) => { + vec.push(ControlPoint::new(root_cell, current)) + } + ControlNode::Invoke(_) => { + vec.push(ControlPoint::new(root_cell, current)) + } + } + } + } else { + todo!( + "Flat interpreter does not support control-less components yet" + ) + } + + Self { vec, par_map } + } + + pub fn iter(&self) -> std::slice::Iter<'_, ControlPoint> { + self.vec.iter() + } + + pub fn is_done(&self) -> bool { + self.vec.is_empty() + } +} + +impl<'a> IntoIterator for &'a ProgramCounter { + type Item = &'a ControlPoint; + + type IntoIter = std::slice::Iter<'a, ControlPoint>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} diff --git a/interp/src/flatten/structures/mod.rs b/interp/src/flatten/structures/mod.rs index af7edb104b..feccaf3e8f 100644 --- a/interp/src/flatten/structures/mod.rs +++ b/interp/src/flatten/structures/mod.rs @@ -4,3 +4,4 @@ pub mod index_trait; pub mod indexed_map; mod printer; pub mod sparse_map; +pub mod values; diff --git a/interp/src/flatten/structures/values.rs b/interp/src/flatten/structures/values.rs new file mode 100644 index 0000000000..0c4b5ee6d7 --- /dev/null +++ b/interp/src/flatten/structures/values.rs @@ -0,0 +1,30 @@ +use crate::values::Value as BvValue; + +pub enum Value { + /// An undefined value from which it is dangerous to read. + Undefined, + /// A defined value that can be computed with + Defined(DefinedValue), +} + +pub enum DefinedValue { + Large(BvValue), +} + +impl Value { + /// Returns `true` if the value is [`Undefined`]. + /// + /// [`Undefined`]: Value::Undefined + #[must_use] + pub fn is_undefined(&self) -> bool { + matches!(self, Self::Undefined) + } + + /// Returns `true` if the value is [`Defined`]. + /// + /// [`Defined`]: Value::Defined + #[must_use] + pub fn is_defined(&self) -> bool { + matches!(self, Self::Defined(..)) + } +} diff --git a/interp/src/structures/values.rs b/interp/src/structures/values.rs index bcebcc46fe..a66e552c1f 100644 --- a/interp/src/structures/values.rs +++ b/interp/src/structures/values.rs @@ -1,6 +1,5 @@ use std::ops::Not; -use std::rc::Rc; -use std::{cell::RefCell, fmt::Write, ops::Index}; +use std::{fmt::Write, ops::Index}; use bitvec::prelude::*; use fraction::Fraction; @@ -9,8 +8,6 @@ use itertools::Itertools; use serde::de::{self, Deserialize, Visitor}; use serde::Serialize; -use calyx_ir as ir; - pub type BitString = BitVec; /// Retrieves the unsigned fixed point representation of `v`. This splits the representation into @@ -256,8 +253,6 @@ impl InputNumber { } } -type Signed = Rc>>; -type Unsigned = Rc>>; #[derive(Clone, Debug)] /// The type of all inputs and outputs to all components in Calyx. /// Wraps a BitVector. @@ -265,20 +260,12 @@ pub struct Value { // Lsb0 means the 0th index contains the LSB. This is useful because // a 7-bit bitvector and 17-bit bitvector representing the number 6 have // ones in the same index. - vec: Rc, - - unsigned: Unsigned, - - signed: Signed, + vec: BitString, } impl From for Value { fn from(bv: BitString) -> Self { - Self { - vec: Rc::new(bv), - unsigned: Unsigned::default(), - signed: Signed::default(), - } + Self { vec: bv } } } @@ -307,7 +294,7 @@ impl Value { } pub fn clone_bit_vec(&self) -> BitString { - (*self.vec).clone() + self.vec.clone() } pub fn bv_ref(&self) -> &BitString { @@ -334,9 +321,7 @@ impl Value { pub fn zeroes>(bitwidth: I) -> Value { let input_num: InputNumber = bitwidth.into(); Value { - vec: Rc::new(bitvec![usize, Lsb0; 0; input_num.as_usize()]), - unsigned: ir::rrc(Some(0_u8.into())), - signed: ir::rrc(Some(0.into())), + vec: bitvec![usize, Lsb0; 0; input_num.as_usize()], } } @@ -382,34 +367,13 @@ impl Value { && !Value::unsigned_value_fits_in(&bv, width); bv.resize(width, init.is_negative()); - ( - Value { - vec: Rc::new(bv), - signed: ir::rrc(None), - unsigned: ir::rrc(None), - }, - flag, - ) + (Value { vec: bv }, flag) } #[inline] pub fn from_bv(bv: BitString) -> Self { bv.into() } - - /// Returns a Value containing a vector of length 0, effectively returning - /// a cleared vector. - // TODO (Griffin): Depricate this. - pub fn clear(&self) -> Self { - let mut vec = (*self.vec).clone(); - vec.truncate(0); - Value { - vec: Rc::new(vec), - signed: Signed::default(), - unsigned: Unsigned::default(), - } - } - /// Returns a Value truncated to length new_size. /// /// # Example @@ -418,19 +382,13 @@ impl Value { /// let val_4_4 = Value::from(4, 16).truncate(4); /// ``` pub fn truncate(&self, new_size: usize) -> Value { - let mut vec = (*self.vec).clone(); + let mut vec = self.vec.clone(); vec.truncate(new_size); - Value { - vec: Rc::new(vec), - signed: Signed::default(), - unsigned: Unsigned::default(), - } + Value { vec } } pub fn truncate_in_place(&mut self, new_size: usize) { - Rc::make_mut(&mut self.vec).truncate(new_size); - self.signed = Default::default(); - self.unsigned = Unsigned::default(); + self.vec.truncate(new_size); } /// Zero-extend the vector to length ext. @@ -441,15 +399,11 @@ impl Value { /// let val_4_16 = Value::from(4, 4).ext(16); /// ``` pub fn ext(&self, ext: usize) -> Value { - let mut vec = (*self.vec).clone(); + let mut vec = self.vec.clone(); for _x in 0..(ext - vec.len()) { vec.push(false); } - Value { - vec: Rc::new(vec), - signed: self.signed.clone(), - unsigned: self.unsigned.clone(), - } + Value { vec } } /// Sign-extend the vector to length ext. @@ -461,16 +415,12 @@ impl Value { /// let val_31_5 = Value::from(15, 4).sext(5); /// ``` pub fn sext(&self, ext: usize) -> Value { - let mut vec = (*self.vec).clone(); + let mut vec = self.vec.clone(); let sign = vec[vec.len() - 1]; for _x in 0..(ext - vec.len()) { vec.push(sign); } - Value { - vec: Rc::new(vec), - signed: Signed::default(), - unsigned: Unsigned::default(), - } + Value { vec } } /// Converts value into u64 type. @@ -628,51 +578,36 @@ impl Value { } pub fn as_signed(&self) -> IBig { - let memo_ref = self.signed.borrow(); - if let Some(v) = &*memo_ref { - v.clone() - } else { - drop(memo_ref); - let mut acc: IBig = 0.into(); - - // skip the msb for the moment - for bit in self.vec.iter().take(self.vec.len() - 1).rev() { - acc <<= 1; - let bit: IBig = (*bit).into(); - acc |= bit - } + let mut acc: IBig = 0.into(); - if let Some(bit) = self.vec.last() { - if *bit { - let neg: IBig = (-1).into(); - let two: IBig = (2).into(); + // skip the msb for the moment + for bit in self.vec.iter().take(self.vec.len() - 1).rev() { + acc <<= 1; + let bit: IBig = (*bit).into(); + acc |= bit + } - acc += neg * two.pow(self.vec.len() - 1) - } + if let Some(bit) = self.vec.last() { + if *bit { + let neg: IBig = (-1).into(); + let two: IBig = (2).into(); + + acc += neg * two.pow(self.vec.len() - 1) } - let mut memo_ref = self.signed.borrow_mut(); - *memo_ref = Some(acc.clone()); - acc } + + acc } pub fn as_unsigned(&self) -> UBig { - let memo_ref = self.unsigned.borrow(); - - if let Some(v) = &*memo_ref { - v.clone() - } else { - drop(memo_ref); - let mut acc: UBig = 0_u32.into(); - for bit in self.vec.iter().rev() { - acc <<= 1; - let bit: UBig = (*bit).into(); - acc |= bit; - } - let mut memo_ref = self.unsigned.borrow_mut(); - *memo_ref = Some(acc.clone()); - acc + let mut acc: UBig = 0_u32.into(); + for bit in self.vec.iter().rev() { + acc <<= 1; + let bit: UBig = (*bit).into(); + acc |= bit; } + + acc } /// Interprets a 1bit value as a bool, will not panic for non-1-bit values @@ -700,11 +635,7 @@ impl Value { assert!(upper_idx < self.vec.len()); let new_bv = (self.vec[lower_idx..=upper_idx]).into(); - Value { - vec: Rc::new(new_bv), - signed: Signed::default(), - unsigned: Unsigned::default(), - } + Value { vec: new_bv } } /// Returns a value containing the sliced region \[lower,upper\] @@ -713,11 +644,7 @@ impl Value { assert!(upper_idx < self.vec.len()); let new_bv = BitVec::from_bitslice(&self.vec[lower_idx..=upper_idx]); - Value { - vec: Rc::new(new_bv), - signed: Signed::default(), - unsigned: Unsigned::default(), - } + Value { vec: new_bv } } } diff --git a/interp/src/tests/values.rs b/interp/src/tests/values.rs index 72f24a4245..c7e715e301 100644 --- a/interp/src/tests/values.rs +++ b/interp/src/tests/values.rs @@ -24,13 +24,7 @@ mod val_test { println!("15 with bit width 4: {}", v_15_4); assert_eq!(v_31_4.as_u64(), v_15_4.as_u64()); } - #[test] - fn clear() { - let v_15_4 = Value::from(15, 4); - let v_15_4 = v_15_4.clear(); - println!("15 with bit width 4 AFTER clear: {}", v_15_4); - assert_eq!(v_15_4.as_u64(), 0); - } + #[test] fn ext() { let v_15_4 = Value::from(15, 4);