From 76a52e24c041c72fb024cbeb85afa2001ee82ca7 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 00:19:06 -0600 Subject: [PATCH 001/119] Update lib.rs --- starlight/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 20504b80..c1b8634d 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -1,11 +1,17 @@ -//! This is a RTL (Register Transfer Level) description library. Instead of the -//! typical DSL (Domain Specific Language) approach, this allows RTL +//! This is a DSL (Domain Specific Language) that can describe combinational +//! logic and temporal logic. This allows RTL (Register Transfer Level) //! descriptions in ordinary Rust code with all the features that Rust provides. //! -//! This crate still has a considerable amount of WIP stuff +//! This crate still has a considerable amount of WIP stuff needed to evolve +//! into a proper HDL (Hardware Description Language). //! //! See the documentation of `awint`/`awint_dag` which is used as the backend -//! for this. +//! for this. `awint` is the base library that operations are modeled off of. +//! `awint_dag` allows for recording a DAG of arbitrary bitwidth integer +//! operations. `starlight` lowers high level operations down into a DAG of +//! simple lookup tables, and also adds on temporal structs like `Loop`s. It can +//! optimize, evaluate, and retroactively change values in the `DAG` for various +//! purposes. //! //! ``` //! use std::num::NonZeroUsize; From 0f18953b3a44f2453a9d71fe9094fa1d05188406 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 13:03:36 -0600 Subject: [PATCH 002/119] swap TNode and LNode names --- README.md | 2 +- starlight/src/awi_structs/epoch.rs | 36 +++--- starlight/src/ensemble/debug.rs | 60 +++++----- starlight/src/ensemble/lnode.rs | 80 ++++++++++++- starlight/src/ensemble/optimize.rs | 178 ++++++++++++++--------------- starlight/src/ensemble/state.rs | 26 ++--- starlight/src/ensemble/tnode.rs | 80 +------------ starlight/src/ensemble/together.rs | 132 ++++++++++----------- starlight/src/ensemble/value.rs | 112 +++++++++--------- starlight/src/lib.rs | 4 +- testcrate/tests/basic.rs | 14 +-- 11 files changed, 362 insertions(+), 362 deletions(-) diff --git a/README.md b/README.md index 96fabf2a..8b7838e7 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ // Now the combinational logic is described in a DAG of lookup tables that we // could use for various purposes for state in epoch0.ensemble().stator.states.vals() { - awi::assert!(state.lowered_to_tnodes); + awi::assert!(state.lowered_to_lnodes); } // "retroactively" assign the input with a non-opaque value diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 28bad472..e968c6e3 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -314,13 +314,13 @@ impl EpochShared { Ensemble::handle_requests_with_lower_capability(self)?; // first evaluate all loop drivers let lock = self.epoch_data.borrow(); - let mut adv = lock.ensemble.lnodes.advancer(); + let mut adv = lock.ensemble.tnodes.advancer(); drop(lock); loop { let lock = self.epoch_data.borrow(); - if let Some(p_lnode) = adv.advance(&lock.ensemble.lnodes) { - let lnode = lock.ensemble.lnodes.get(p_lnode).unwrap(); - let p_driver = lnode.p_driver; + if let Some(p_tnode) = adv.advance(&lock.ensemble.tnodes) { + let tnode = lock.ensemble.tnodes.get(p_tnode).unwrap(); + let p_driver = tnode.p_driver; drop(lock); Ensemble::calculate_value_with_lower_capability(self, p_driver)?; } else { @@ -329,11 +329,11 @@ impl EpochShared { } // second do all loopback changes let mut lock = self.epoch_data.borrow_mut(); - let mut adv = lock.ensemble.lnodes.advancer(); - while let Some(p_lnode) = adv.advance(&lock.ensemble.lnodes) { - let lnode = lock.ensemble.lnodes.get(p_lnode).unwrap(); - let val = lock.ensemble.backrefs.get_val(lnode.p_driver).unwrap().val; - let p_self = lnode.p_self; + let mut adv = lock.ensemble.tnodes.advancer(); + while let Some(p_tnode) = adv.advance(&lock.ensemble.tnodes) { + let tnode = lock.ensemble.tnodes.get(p_tnode).unwrap(); + let val = lock.ensemble.backrefs.get_val(tnode.p_driver).unwrap().val; + let p_self = tnode.p_self; lock.ensemble.change_value(p_self, val).unwrap(); } Ok(()) @@ -344,18 +344,18 @@ impl EpochShared { let mut lock = self.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - let mut adv = ensemble.lnodes.advancer(); - while let Some(p_lnode) = adv.advance(&ensemble.lnodes) { - let lnode = ensemble.lnodes.get(p_lnode).unwrap(); - let p_driver = lnode.p_driver; + let mut adv = ensemble.tnodes.advancer(); + while let Some(p_tnode) = adv.advance(&ensemble.tnodes) { + let tnode = ensemble.tnodes.get(p_tnode).unwrap(); + let p_driver = tnode.p_driver; ensemble.calculate_value(p_driver)?; } // second do all loopback changes - let mut adv = ensemble.lnodes.advancer(); - while let Some(p_lnode) = adv.advance(&ensemble.lnodes) { - let lnode = ensemble.lnodes.get(p_lnode).unwrap(); - let val = ensemble.backrefs.get_val(lnode.p_driver).unwrap().val; - let p_self = lnode.p_self; + let mut adv = ensemble.tnodes.advancer(); + while let Some(p_tnode) = adv.advance(&ensemble.tnodes) { + let tnode = ensemble.tnodes.get(p_tnode).unwrap(); + let val = ensemble.backrefs.get_val(tnode.p_driver).unwrap().val; + let p_self = tnode.p_self; ensemble.change_value(p_self, val).unwrap(); } Ok(()) diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index f5dbe3bc..8f690b94 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -6,7 +6,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, Equiv, PBack, PLNode, PNote, Referent, State, TNode}, + ensemble::{Ensemble, Equiv, LNode, PBack, PNote, PTNode, Referent, State}, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, Epoch, @@ -54,7 +54,7 @@ impl DebugNodeTrait for State { this.rc, this.extern_rc, short(this.lowered_to_elementary), - short(this.lowered_to_tnodes) + short(this.lowered_to_lnodes) )); if let Some(ref e) = this.err { let s = format!("{e}"); @@ -77,10 +77,10 @@ pub struct StateBit { } #[derive(Debug, Clone)] -pub struct LNodeTmp { +pub struct TNodeTmp { p_self: PBack, p_driver: PBack, - p_lnode: PLNode, + p_tnode: PTNode, } #[derive(Debug, Clone)] @@ -94,8 +94,8 @@ pub struct NoteTmp { #[derive(Debug, Clone)] pub enum NodeKind { StateBit(StateBit), - TNode(TNode), - LNode(LNodeTmp), + LNode(LNode), + TNode(TNodeTmp), Equiv(Equiv, Vec), Note(NoteTmp), Remove, @@ -119,8 +119,8 @@ impl DebugNodeTrait for NodeKind { } }, }, - NodeKind::TNode(tnode) => DebugNode { - sources: tnode + NodeKind::LNode(lnode) => DebugNode { + sources: lnode .inp .iter() .enumerate() @@ -128,30 +128,30 @@ impl DebugNodeTrait for NodeKind { .collect(), center: { let mut v = vec![format!("{:?}", p_this)]; - if let Some(ref lut) = tnode.lut { + if let Some(ref lut) = lnode.lut { v.push(format!("{:?} ", lut)); } - if let Some(lowered_from) = tnode.lowered_from { + if let Some(lowered_from) = lnode.lowered_from { v.push(format!("{:?}", lowered_from)); } v }, sinks: vec![], }, - NodeKind::LNode(lnode) => DebugNode { + NodeKind::TNode(tnode) => DebugNode { sources: vec![ - (lnode.p_self, "self".to_owned()), - (lnode.p_driver, "driver".to_owned()), + (tnode.p_self, "self".to_owned()), + (tnode.p_driver, "driver".to_owned()), ], center: { let mut v = vec![format!("{:?}", p_this)]; - v.push(format!("{:?}", lnode.p_lnode)); + v.push(format!("{:?}", tnode.p_tnode)); v }, sinks: vec![], }, - NodeKind::Equiv(equiv, p_tnodes) => DebugNode { - sources: p_tnodes.iter().map(|p| (*p, String::new())).collect(), + NodeKind::Equiv(equiv, p_lnodes) => DebugNode { + sources: p_lnodes.iter().map(|p| (*p, String::new())).collect(), center: { vec![ format!("{:?}", equiv.p_self_equiv), @@ -179,7 +179,7 @@ impl Ensemble { pub fn backrefs_to_chain_arena(&self) -> ChainArena { let mut chain_arena = ChainArena::new(); self.backrefs - .clone_keys_to_chain_arena(&mut chain_arena, |_, p_tnode| *p_tnode); + .clone_keys_to_chain_arena(&mut chain_arena, |_, p_lnode| *p_lnode); chain_arena } @@ -192,8 +192,8 @@ impl Ensemble { let mut v = vec![]; let mut adv = self.backrefs.advancer_surject(p_self); while let Some(p) = adv.advance(&self.backrefs) { - if let Referent::ThisTNode(_) = self.backrefs.get_key(p).unwrap() { - // get every TNode that is in this equivalence + if let Referent::ThisLNode(_) = self.backrefs.get_key(p).unwrap() { + // get every LNode that is in this equivalence v.push(p); } } @@ -216,26 +216,26 @@ impl Ensemble { }) } } - Referent::ThisTNode(p_tnode) => { - let mut tnode = self.tnodes.get(*p_tnode).unwrap().clone(); - // forward to the `PBack`s of TNodes - for inp in &mut tnode.inp { + Referent::ThisLNode(p_lnode) => { + let mut lnode = self.lnodes.get(*p_lnode).unwrap().clone(); + // forward to the `PBack`s of LNodes + for inp in &mut lnode.inp { if let Referent::Input(_) = self.backrefs.get_key(*inp).unwrap() { let p_input = self.backrefs.get_val(*inp).unwrap().p_self_equiv; *inp = p_input; } } - NodeKind::TNode(tnode) + NodeKind::LNode(lnode) } - Referent::ThisLNode(p_lnode) => { - let lnode = self.lnodes.get(*p_lnode).unwrap(); + Referent::ThisTNode(p_tnode) => { + let tnode = self.tnodes.get(*p_tnode).unwrap(); // forward to the `PBack`s - let p_self = self.backrefs.get_val(lnode.p_self).unwrap().p_self_equiv; - let p_driver = self.backrefs.get_val(lnode.p_driver).unwrap().p_self_equiv; - NodeKind::LNode(LNodeTmp { + let p_self = self.backrefs.get_val(tnode.p_self).unwrap().p_self_equiv; + let p_driver = self.backrefs.get_val(tnode.p_driver).unwrap().p_self_equiv; + NodeKind::TNode(TNodeTmp { p_self, p_driver, - p_lnode: *p_lnode, + p_tnode: *p_tnode, }) } Referent::Note(p_note) => { diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 5a6555f1..28c65b48 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -1,19 +1,87 @@ -use awint::awint_dag::triple_arena::ptr_struct; +use std::num::NonZeroUsize; -use crate::ensemble::PBack; +use awint::{ + awint_dag::{smallvec, PState}, + Awi, Bits, +}; +use smallvec::SmallVec; + +use crate::{ensemble::PBack, triple_arena::ptr_struct}; // We use this because our algorithms depend on generation counters ptr_struct!(PLNode); -/// A temporal loopback node +/// A lookup table node #[derive(Debug, Clone)] pub struct LNode { pub p_self: PBack, - pub p_driver: PBack, + /// Inputs + pub inp: SmallVec<[PBack; 4]>, + /// Lookup Table that outputs one bit + pub lut: Option, + pub lowered_from: Option, } impl LNode { - pub fn new(p_self: PBack, p_driver: PBack) -> Self { - Self { p_self, p_driver } + pub fn new(p_self: PBack, lowered_from: Option) -> Self { + Self { + p_self, + inp: SmallVec::new(), + lut: None, + lowered_from, + } + } + + /// Reduce a LUT in half by saving entries indexed by setting the `i`th + /// input bit to `bit` + pub fn reduce_lut(lut: &Bits, i: usize, bit: bool) -> Awi { + assert!(lut.bw().is_power_of_two()); + let next_bw = lut.bw() / 2; + let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); + let w = 1 << i; + let mut from = 0; + let mut to = 0; + while to < next_bw { + next_lut + .field(to, lut, if bit { from + w } else { from }, w) + .unwrap(); + from += 2 * w; + to += w; + } + next_lut + } + + /// Returns an equivalent reduced LUT (with the `i`th index removed) if the + /// LUT output is independent with respect to the `i`th bit + #[must_use] + pub fn reduce_independent_lut(lut: &Bits, i: usize) -> Option { + let nzbw = lut.nzbw(); + assert!(nzbw.get().is_power_of_two()); + let next_bw = nzbw.get() / 2; + let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); + let mut tmp0 = Awi::zero(next_nzbw); + let mut tmp1 = Awi::zero(next_nzbw); + let w = 1 << i; + // LUT if the `i`th bit were 0 + let mut from = 0; + let mut to = 0; + while to < next_bw { + tmp0.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + // LUT if the `i`th bit were 1 + from = w; + to = 0; + while to < next_bw { + tmp1.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + if tmp0 == tmp1 { + Some(tmp0) + } else { + None + } } } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 53a7286c..946f673a 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -10,7 +10,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, PBack, PLNode, PTNode, Referent, TNode, Value}, + ensemble::{Ensemble, LNode, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, SmallMap, }; @@ -28,33 +28,33 @@ pub enum Optimization { Preinvestigate(PBack), /// Removes an entire equivalence class because it is unused RemoveEquiv(PBack), - /// This needs to point to the `Referent::ThisTNode` of the identity - /// `TNode`. If an equivalence is an identity function, any referents should + /// This needs to point to the `Referent::ThisLNode` of the identity + /// `LNode`. If an equivalence is an identity function, any referents should /// use its inputs instead. This is high priority because the principle /// source of a value needs to be known for various optimizations /// involving deduplication to work (such as early LUT simplification), and /// also because it eliminates useless identities early. ForwardEquiv(PBack), - /// Removes all `TNode`s from an equivalence that has had a constant + /// Removes all `LNode`s from an equivalence that has had a constant /// assigned to it, and notifies all referents. ConstifyEquiv(PBack), - /// Removes a `TNode` because there is at least one other `TNode` in the + /// Removes a `LNode` because there is at least one other `LNode` in the /// equivalence that is stricly better - RemoveTNode(PBack), + RemoveLNode(PBack), /// If a backref is removed, investigate this equivalence. Note that /// `InvestigateUsed`s overwrite each other when multiple ones are fired on /// the same equivalence. // TODO should this one be moved up? Needs to be benchmarked. InvestigateUsed(PBack), /// If an input was constified - InvestigateConst(PTNode), + InvestigateConst(PLNode), /// If a driver was constified - InvestigateLoopDriverConst(PLNode), + InvestigateLoopDriverConst(PTNode), /// The optimization state that equivalences are set to after the /// preinvestigation finds nothing InvestigateEquiv0(PBack), //InvertInput - // (?) not sure if fusion + ordinary `const_eval_tnode` handles all cases cleanly, + // (?) not sure if fusion + ordinary `const_eval_lnode` handles all cases cleanly, // might only do fission for routing //Fission // A fusion involving the number of inputs that will result @@ -84,23 +84,23 @@ impl Ensemble { /// Removes all `Const` inputs and assigns `Const` result if possible. /// Returns if a `Const` result was assigned (`Optimization::ConstifyEquiv` /// needs to be run by the caller). - pub fn const_eval_tnode(&mut self, p_tnode: PTNode) -> bool { - let tnode = self.tnodes.get_mut(p_tnode).unwrap(); - if let Some(original_lut) = &tnode.lut { + pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> bool { + let lnode = self.lnodes.get_mut(p_lnode).unwrap(); + if let Some(original_lut) = &lnode.lut { let mut lut = original_lut.clone(); // acquire LUT inputs, for every constant input reduce the LUT - let len = usize::from(u8::try_from(tnode.inp.len()).unwrap()); + let len = usize::from(u8::try_from(lnode.inp.len()).unwrap()); for i in (0..len).rev() { - let p_inp = tnode.inp[i]; + let p_inp = lnode.inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); if let Value::Const(val) = equiv.val { // we will be removing the input, mark it to be investigated self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); self.backrefs.remove_key(p_inp).unwrap(); - tnode.inp.remove(i); + lnode.inp.remove(i); - lut = TNode::reduce_lut(&lut, i, val); + lut = LNode::reduce_lut(&lut, i, val); } } @@ -109,8 +109,8 @@ impl Ensemble { // we have to reset every time because the removals can mess up any range of // indexes let mut set = SmallMap::new(); - for i in 0..tnode.inp.len() { - let p_inp = tnode.inp[i]; + for i in 0..lnode.inp.len() { + let p_inp = lnode.inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); match set.insert(equiv.p_self_equiv.inx(), i) { Ok(()) => (), @@ -127,8 +127,8 @@ impl Ensemble { } self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(tnode.inp[j]).unwrap(); - tnode.inp.remove(j); + self.backrefs.remove_key(lnode.inp[j]).unwrap(); + lnode.inp.remove(j); lut = next_lut; continue 'outer } @@ -138,13 +138,13 @@ impl Ensemble { } // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing - let len = tnode.inp.len(); + let len = lnode.inp.len(); for i in (0..len).rev() { if lut.bw() > 1 { - if let Some(reduced) = TNode::reduce_independent_lut(&lut, i) { + if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { // independent of the `i`th bit lut = reduced; - let p_inp = tnode.inp.remove(i); + let p_inp = lnode.inp.remove(i); let equiv = self.backrefs.get_val(p_inp).unwrap(); self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); @@ -152,41 +152,41 @@ impl Ensemble { } } } - // sort inputs so that `TNode`s can be compared later + // sort inputs so that `LNode`s can be compared later // TODO? // input independence automatically reduces all zeros and all ones LUTs, so just // need to check if the LUT is one bit for constant generation if lut.bw() == 1 { - let equiv = self.backrefs.get_val_mut(tnode.p_self).unwrap(); + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); equiv.val = Value::Const(lut.to_bool()); // fix the `lut` to its new state, do this even if we are doing the constant // optimization - tnode.lut = Some(lut); + lnode.lut = Some(lut); true } else if (lut.bw() == 2) && lut.get(1).unwrap() { // the only `lut.bw() == 2` cases that survive independence removal is identity // and inversion. If it is identity, register this for forwarding - tnode.lut = None; + lnode.lut = None; self.optimizer - .insert(Optimization::ForwardEquiv(tnode.p_self)); + .insert(Optimization::ForwardEquiv(lnode.p_self)); false } else { - tnode.lut = Some(lut); + lnode.lut = Some(lut); false } - } else if tnode.inp.len() == 1 { + } else if lnode.inp.len() == 1 { // wire propogation - let input_equiv = self.backrefs.get_val_mut(tnode.inp[0]).unwrap(); + let input_equiv = self.backrefs.get_val_mut(lnode.inp[0]).unwrap(); if let Value::Const(val) = input_equiv.val { - let equiv = self.backrefs.get_val_mut(tnode.p_self).unwrap(); + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); equiv.val = Value::Const(val); self.optimizer .insert(Optimization::ConstifyEquiv(equiv.p_self_equiv)); true } else { self.optimizer - .insert(Optimization::ForwardEquiv(tnode.p_self)); + .insert(Optimization::ForwardEquiv(lnode.p_self)); false } } else { @@ -196,10 +196,10 @@ impl Ensemble { /// Assigns `Const` result if possible. /// Returns if a `Const` result was assigned. - pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> bool { - let lnode = self.lnodes.get(p_lnode).unwrap(); - let p_self = lnode.p_self; - let p_driver = lnode.p_driver; + pub fn const_eval_tnode(&mut self, p_tnode: PTNode) -> bool { + let tnode = self.tnodes.get(p_tnode).unwrap(); + let p_self = tnode.p_self; + let p_driver = tnode.p_driver; let equiv = self.backrefs.get_val(p_driver).unwrap(); if let Value::Const(val) = equiv.val { self.backrefs.get_val_mut(p_self).unwrap().val = Value::Const(val); @@ -222,21 +222,21 @@ impl Ensemble { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv => (), - Referent::ThisLNode(p_lnode) => { + Referent::ThisTNode(p_tnode) => { // avoid checking more if it was already determined to be constant - if !is_const && self.const_eval_lnode(p_lnode) { + if !is_const && self.const_eval_tnode(p_tnode) { is_const = true; } } - Referent::ThisTNode(p_tnode) => { + Referent::ThisLNode(p_lnode) => { // avoid checking more if it was already determined to be constant - if !is_const && self.const_eval_tnode(p_tnode) { + if !is_const && self.const_eval_lnode(p_lnode) { is_const = true; } } Referent::ThisStateBit(p_state, _) => { let state = &self.stator.states[p_state]; - // the state bits can always be disregarded on a per-tnode basis unless they are + // the state bits can always be disregarded on a per-lnode basis unless they are // being used externally if state.extern_rc != 0 { non_self_rc += 1; @@ -247,7 +247,7 @@ impl Ensemble { // the way `LoopDriver` networks with no real dependencies will work, is // that const propogation and other simplifications will eventually result // in a single node equivalence that drives itself, which we can remove - let p_back_driver = self.lnodes.get(p_driver).unwrap().p_self; + let p_back_driver = self.tnodes.get(p_driver).unwrap().p_self; if !self.backrefs.in_same_set(p_back, p_back_driver).unwrap() { non_self_rc += 1; } @@ -269,7 +269,7 @@ impl Ensemble { } /// Does not perform the final step - /// `ensemble.backrefs.remove(tnode.p_self).unwrap()` which is important for + /// `ensemble.backrefs.remove(lnode.p_self).unwrap()` which is important for /// `Advancer`s. pub fn remove_state_bit_not_p_self(&mut self, p_state: PState, i_bit: usize) { let p_bit = self @@ -288,11 +288,11 @@ impl Ensemble { } /// Does not perform the final step - /// `ensemble.backrefs.remove(tnode.p_self).unwrap()` which is important for + /// `ensemble.backrefs.remove(lnode.p_self).unwrap()` which is important for /// `Advancer`s. - pub fn remove_tnode_not_p_self(&mut self, p_tnode: PTNode) { - let tnode = self.tnodes.remove(p_tnode).unwrap(); - for inp in tnode.inp { + pub fn remove_lnode_not_p_self(&mut self, p_lnode: PLNode) { + let lnode = self.lnodes.remove(p_lnode).unwrap(); + for inp in lnode.inp { let p_equiv = self.backrefs.get_val(inp).unwrap().p_self_equiv; self.optimizer .insert(Optimization::InvestigateUsed(p_equiv)); @@ -301,14 +301,14 @@ impl Ensemble { } /// Does not perform the final step - /// `ensemble.backrefs.remove(lnode.p_self).unwrap()` which is important for + /// `ensemble.backrefs.remove(tnode.p_self).unwrap()` which is important for /// `Advancer`s. - pub fn remove_lnode_not_p_self(&mut self, p_lnode: PLNode) { - let lnode = self.lnodes.remove(p_lnode).unwrap(); - let p_equiv = self.backrefs.get_val(lnode.p_driver).unwrap().p_self_equiv; + pub fn remove_tnode_not_p_self(&mut self, p_tnode: PTNode) { + let tnode = self.tnodes.remove(p_tnode).unwrap(); + let p_equiv = self.backrefs.get_val(tnode.p_driver).unwrap().p_self_equiv; self.optimizer .insert(Optimization::InvestigateUsed(p_equiv)); - self.backrefs.remove_key(lnode.p_driver).unwrap(); + self.backrefs.remove_key(tnode.p_driver).unwrap(); } /// Also removes all states @@ -343,7 +343,7 @@ impl Ensemble { } else { return }; - // remove all associated TNodes first + // remove all associated LNodes first let mut adv = self.backrefs.advancer_surject(p_back); while let Some(p_back) = adv.advance(&self.backrefs) { match self.backrefs.get_key(p_back).unwrap() { @@ -351,12 +351,12 @@ impl Ensemble { Referent::ThisStateBit(p_state, bit_i) => { self.remove_state_bit_not_p_self(*p_state, *bit_i); } - Referent::ThisTNode(p_tnode) => { - self.remove_tnode_not_p_self(*p_tnode); - } Referent::ThisLNode(p_lnode) => { self.remove_lnode_not_p_self(*p_lnode); } + Referent::ThisTNode(p_tnode) => { + self.remove_tnode_not_p_self(*p_tnode); + } _ => unreachable!(), } } @@ -365,12 +365,12 @@ impl Ensemble { } Optimization::ForwardEquiv(p_ident) => { let p_source = if let Some(referent) = self.backrefs.get_key(p_ident) { - if let Referent::ThisTNode(p_tnode) = referent { - let tnode = &self.tnodes[p_tnode]; - assert_eq!(tnode.inp.len(), 1); + if let Referent::ThisLNode(p_lnode) = referent { + let lnode = &self.lnodes[p_lnode]; + assert_eq!(lnode.inp.len(), 1); // do not use directly, use the `p_self_equiv` since this backref will be // removed when `p_ident` is process in the loop - let p_back = tnode.inp[0]; + let p_back = lnode.inp[0]; self.backrefs.get_val(p_back).unwrap().p_self_equiv } else { unreachable!() @@ -383,12 +383,12 @@ impl Ensemble { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv => (), - Referent::ThisTNode(p_tnode) => { - self.remove_tnode_not_p_self(p_tnode); - } Referent::ThisLNode(p_lnode) => { self.remove_lnode_not_p_self(p_lnode); } + Referent::ThisTNode(p_tnode) => { + self.remove_tnode_not_p_self(p_tnode); + } Referent::ThisStateBit(p_state, i_bit) => { let p_bit = self.stator.states[p_state].p_self_bits[i_bit] .as_mut() @@ -400,9 +400,9 @@ impl Ensemble { *p_bit = p_back_new; } Referent::Input(p_input) => { - let tnode = self.tnodes.get_mut(p_input).unwrap(); + let lnode = self.lnodes.get_mut(p_input).unwrap(); let mut found = false; - for inp in &mut tnode.inp { + for inp in &mut lnode.inp { if *inp == p_back { let p_back_new = self .backrefs @@ -416,13 +416,13 @@ impl Ensemble { assert!(found); } Referent::LoopDriver(p_driver) => { - let lnode = self.lnodes.get_mut(p_driver).unwrap(); - assert_eq!(lnode.p_driver, p_back); + let tnode = self.tnodes.get_mut(p_driver).unwrap(); + assert_eq!(tnode.p_driver, p_back); let p_back_new = self .backrefs .insert_key(p_source, Referent::LoopDriver(p_driver)) .unwrap(); - lnode.p_driver = p_back_new; + tnode.p_driver = p_back_new; } Referent::Note(p_note) => { // here we see a major advantage of the backref system @@ -453,21 +453,21 @@ impl Ensemble { if !self.backrefs.contains(p_back) { return }; - // for removing `ThisTNode` safely + // for removing `ThisLNode` safely let mut remove = SmallVec::<[PBack; 16]>::new(); - // remove all associated TNodes + // remove all associated LNodes let mut adv = self.backrefs.advancer_surject(p_back); while let Some(p_back) = adv.advance(&self.backrefs) { match self.backrefs.get_key(p_back).unwrap() { Referent::ThisEquiv => (), - Referent::ThisTNode(p_tnode) => { - self.remove_tnode_not_p_self(*p_tnode); - remove.push(p_back); - } Referent::ThisLNode(p_lnode) => { self.remove_lnode_not_p_self(*p_lnode); remove.push(p_back); } + Referent::ThisTNode(p_tnode) => { + self.remove_tnode_not_p_self(*p_tnode); + remove.push(p_back); + } Referent::ThisStateBit(..) => (), Referent::Input(p_inp) => { self.optimizer @@ -484,7 +484,7 @@ impl Ensemble { self.backrefs.remove_key(p_back).unwrap(); } } - Optimization::RemoveTNode(p_back) => { + Optimization::RemoveLNode(p_back) => { if !self.backrefs.contains(p_back) { return } @@ -500,11 +500,11 @@ impl Ensemble { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv => (), - Referent::ThisTNode(_) => (), Referent::ThisLNode(_) => (), + Referent::ThisTNode(_) => (), Referent::ThisStateBit(p_state, _) => { let state = &self.stator.states[p_state]; - // the state bits can always be disregarded on a per-tnode basis unless + // the state bits can always be disregarded on a per-lnode basis unless // they are being used externally if state.extern_rc != 0 { found_use = true; @@ -515,7 +515,7 @@ impl Ensemble { break } Referent::LoopDriver(p_driver) => { - let p_back_driver = self.lnodes.get(p_driver).unwrap().p_self; + let p_back_driver = self.tnodes.get(p_driver).unwrap().p_self; if !self.backrefs.in_same_set(p_back, p_back_driver).unwrap() { found_use = true; break @@ -531,23 +531,23 @@ impl Ensemble { self.optimizer.insert(Optimization::RemoveEquiv(p_back)); } } - Optimization::InvestigateConst(p_tnode) => { - if !self.tnodes.contains(p_tnode) { + Optimization::InvestigateConst(p_lnode) => { + if !self.lnodes.contains(p_lnode) { return }; - if self.const_eval_tnode(p_tnode) { + if self.const_eval_lnode(p_lnode) { self.optimizer.insert(Optimization::ConstifyEquiv( - self.tnodes.get(p_tnode).unwrap().p_self, + self.lnodes.get(p_lnode).unwrap().p_self, )); } } - Optimization::InvestigateLoopDriverConst(p_lnode) => { - if !self.lnodes.contains(p_lnode) { + Optimization::InvestigateLoopDriverConst(p_tnode) => { + if !self.tnodes.contains(p_tnode) { return }; - if self.const_eval_lnode(p_lnode) { + if self.const_eval_tnode(p_tnode) { self.optimizer.insert(Optimization::ConstifyEquiv( - self.lnodes.get(p_lnode).unwrap().p_self, + self.tnodes.get(p_tnode).unwrap().p_self, )); } } @@ -555,9 +555,9 @@ impl Ensemble { /*if !self.backrefs.contains(p_back) { return };*/ - // TODO eliminate equal TNodes, combine equal equivalences etc. + // TODO eliminate equal LNodes, combine equal equivalences etc. - // TODO compare TNodes + // TODO compare LNodes // TODO compress inverters by inverting inx table // TODO fusion of structures like // H(F(a, b), G(a, b)) definitely or any case like H(F(a, b), a) diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index ff776336..64e3bc07 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -40,9 +40,9 @@ pub struct State { /// operations and roots). Note that a DFS might set this before actually /// being lowered. pub lowered_to_elementary: bool, - /// If the `State` has been lowered from elementary `State`s to `TNode`s. + /// If the `State` has been lowered from elementary `State`s to `LNode`s. /// Note that a DFS might set this before actually being lowered. - pub lowered_to_tnodes: bool, + pub lowered_to_lnodes: bool, } impl State { @@ -206,16 +206,16 @@ impl Ensemble { } /// Assuming that the rootward tree from `p_state` is lowered down to the - /// elementary `Op`s, this will create the `TNode` network - pub fn dfs_lower_elementary_to_tnodes(&mut self, p_state: PState) -> Result<(), EvalError> { + /// elementary `Op`s, this will create the `LNode` network + pub fn dfs_lower_elementary_to_lnodes(&mut self, p_state: PState) -> Result<(), EvalError> { if let Some(state) = self.stator.states.get(p_state) { - if state.lowered_to_tnodes { + if state.lowered_to_lnodes { return Ok(()) } } else { return Err(EvalError::InvalidPtr) } - self.stator.states[p_state].lowered_to_tnodes = true; + self.stator.states[p_state].lowered_to_lnodes = true; let mut path: Vec<(usize, PState)> = vec![(0, p_state)]; loop { let (i, p_state) = path[path.len() - 1]; @@ -252,22 +252,22 @@ impl Ensemble { path.last_mut().unwrap().0 += 1; } else if i >= ops.len() { // checked all sources - lower_elementary_to_tnodes_intermediate(self, p_state)?; + lower_elementary_to_lnodes_intermediate(self, p_state)?; path.pop().unwrap(); if path.is_empty() { break } } else { let p_next = ops[i]; - if self.stator.states[p_next].lowered_to_tnodes { + if self.stator.states[p_next].lowered_to_lnodes { // in the case of circular cases with `Loop`s, if the DFS goes around and does // not encounter a root, the argument needs to be initialized or else any branch - // of `lower_elementary_to_tnodes_intermediate` could fail + // of `lower_elementary_to_lnodes_intermediate` could fail self.initialize_state_bits_if_needed(p_next).unwrap(); // do not visit path.last_mut().unwrap().0 += 1; } else { - self.stator.states[p_next].lowered_to_tnodes = true; + self.stator.states[p_next].lowered_to_lnodes = true; path.push((0, p_next)); } } @@ -275,13 +275,13 @@ impl Ensemble { Ok(()) } - /// Lowers the rootward tree from `p_state` down to `TNode`s + /// Lowers the rootward tree from `p_state` down to `LNode`s pub fn dfs_lower(epoch_shared: &EpochShared, p_state: PState) -> Result<(), EvalError> { Ensemble::dfs_lower_states_to_elementary(epoch_shared, p_state)?; let mut lock = epoch_shared.epoch_data.borrow_mut(); // the state can get removed by the above step if lock.ensemble.stator.states.contains(p_state) { - let res = lock.ensemble.dfs_lower_elementary_to_tnodes(p_state); + let res = lock.ensemble.dfs_lower_elementary_to_lnodes(p_state); res.unwrap(); } Ok(()) @@ -311,7 +311,7 @@ impl Ensemble { } } -fn lower_elementary_to_tnodes_intermediate( +fn lower_elementary_to_lnodes_intermediate( this: &mut Ensemble, p_state: PState, ) -> Result<(), EvalError> { diff --git a/starlight/src/ensemble/tnode.rs b/starlight/src/ensemble/tnode.rs index e679ab18..2591882d 100644 --- a/starlight/src/ensemble/tnode.rs +++ b/starlight/src/ensemble/tnode.rs @@ -1,87 +1,19 @@ -use std::num::NonZeroUsize; +use awint::awint_dag::triple_arena::ptr_struct; -use awint::{ - awint_dag::{smallvec, PState}, - Awi, Bits, -}; -use smallvec::SmallVec; - -use crate::{ensemble::PBack, triple_arena::ptr_struct}; +use crate::ensemble::PBack; // We use this because our algorithms depend on generation counters ptr_struct!(PTNode); -/// A lookup table node +/// A temporal loopback node #[derive(Debug, Clone)] pub struct TNode { pub p_self: PBack, - /// Inputs - pub inp: SmallVec<[PBack; 4]>, - /// Lookup Table that outputs one bit - pub lut: Option, - pub lowered_from: Option, + pub p_driver: PBack, } impl TNode { - pub fn new(p_self: PBack, lowered_from: Option) -> Self { - Self { - p_self, - inp: SmallVec::new(), - lut: None, - lowered_from, - } - } - - /// Reduce a LUT in half by saving entries indexed by setting the `i`th - /// input bit to `bit` - pub fn reduce_lut(lut: &Bits, i: usize, bit: bool) -> Awi { - assert!(lut.bw().is_power_of_two()); - let next_bw = lut.bw() / 2; - let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); - let w = 1 << i; - let mut from = 0; - let mut to = 0; - while to < next_bw { - next_lut - .field(to, lut, if bit { from + w } else { from }, w) - .unwrap(); - from += 2 * w; - to += w; - } - next_lut - } - - /// Returns an equivalent reduced LUT (with the `i`th index removed) if the - /// LUT output is independent with respect to the `i`th bit - #[must_use] - pub fn reduce_independent_lut(lut: &Bits, i: usize) -> Option { - let nzbw = lut.nzbw(); - assert!(nzbw.get().is_power_of_two()); - let next_bw = nzbw.get() / 2; - let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); - let mut tmp0 = Awi::zero(next_nzbw); - let mut tmp1 = Awi::zero(next_nzbw); - let w = 1 << i; - // LUT if the `i`th bit were 0 - let mut from = 0; - let mut to = 0; - while to < next_bw { - tmp0.field(to, lut, from, w).unwrap(); - from += 2 * w; - to += w; - } - // LUT if the `i`th bit were 1 - from = w; - to = 0; - while to < next_bw { - tmp1.field(to, lut, from, w).unwrap(); - from += 2 * w; - to += w; - } - if tmp0 == tmp1 { - Some(tmp0) - } else { - None - } + pub fn new(p_self: PBack, p_driver: PBack) -> Self { + Self { p_self, p_driver } } } diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index e5c61080..4e305599 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -44,16 +44,16 @@ impl Equiv { pub enum Referent { /// Self equivalence class referent ThisEquiv, - /// Self referent, used by all the `Tnode`s of an equivalence class - ThisTNode(PTNode), - /// Self referent for an `LNode` + /// Self referent, used by all the `LNode`s of an equivalence class ThisLNode(PLNode), + /// Self referent for an `TNode` + ThisTNode(PTNode), /// Self referent to a particular bit of a `State` ThisStateBit(PState, usize), /// Referent is using this for registering an input dependency - Input(PTNode), + Input(PLNode), /// Referent is using this for a loop driver - LoopDriver(PLNode), + LoopDriver(PTNode), /// Referent is a note Note(PNote), } @@ -63,8 +63,8 @@ pub struct Ensemble { pub backrefs: SurjectArena, pub notes: Arena, pub stator: Stator, - pub tnodes: Arena, pub lnodes: Arena, + pub tnodes: Arena, pub evaluator: Evaluator, pub optimizer: Optimizer, pub debug_counter: u64, @@ -76,8 +76,8 @@ impl Ensemble { backrefs: SurjectArena::new(), notes: Arena::new(), stator: Stator::new(), - tnodes: Arena::new(), lnodes: Arena::new(), + tnodes: Arena::new(), evaluator: Evaluator::new(), optimizer: Optimizer::new(), debug_counter: 0, @@ -147,29 +147,29 @@ impl Ensemble { } } } - for (p_tnode, tnode) in &self.tnodes { - if let Some(Referent::ThisTNode(p_self)) = self.backrefs.get_key(tnode.p_self) { - if p_tnode != *p_self { + for (p_lnode, lnode) in &self.lnodes { + if let Some(Referent::ThisLNode(p_self)) = self.backrefs.get_key(lnode.p_self) { + if p_lnode != *p_self { return Err(EvalError::OtherString(format!( - "{tnode:?}.p_self roundtrip fail" + "{lnode:?}.p_self roundtrip fail" ))) } } else { return Err(EvalError::OtherString(format!( - "{tnode:?}.p_self is invalid" + "{lnode:?}.p_self is invalid" ))) } } - for (p_lnode, lnode) in &self.lnodes { - if let Some(Referent::ThisLNode(p_self)) = self.backrefs.get_key(lnode.p_self) { - if p_lnode != *p_self { + for (p_tnode, tnode) in &self.tnodes { + if let Some(Referent::ThisTNode(p_self)) = self.backrefs.get_key(tnode.p_self) { + if p_tnode != *p_self { return Err(EvalError::OtherString(format!( - "{lnode:?}.p_self roundtrip fail" + "{tnode:?}.p_self roundtrip fail" ))) } } else { return Err(EvalError::OtherString(format!( - "{lnode:?}.p_self is invalid" + "{tnode:?}.p_self is invalid" ))) } } @@ -178,11 +178,11 @@ impl Ensemble { let invalid = match referent { // already checked Referent::ThisEquiv => false, - Referent::ThisTNode(_) => false, Referent::ThisLNode(_) => false, + Referent::ThisTNode(_) => false, Referent::ThisStateBit(..) => false, - Referent::Input(p_input) => !self.tnodes.contains(*p_input), - Referent::LoopDriver(p_driver) => !self.lnodes.contains(*p_driver), + Referent::Input(p_input) => !self.lnodes.contains(*p_input), + Referent::LoopDriver(p_driver) => !self.tnodes.contains(*p_driver), Referent::Note(p_note) => !self.notes.contains(*p_note), }; if invalid { @@ -190,47 +190,47 @@ impl Ensemble { } } // other kinds of validity - for p_tnode in self.tnodes.ptrs() { - let tnode = self.tnodes.get(p_tnode).unwrap(); - for p_input in &tnode.inp { + for p_lnode in self.lnodes.ptrs() { + let lnode = self.lnodes.get(p_lnode).unwrap(); + for p_input in &lnode.inp { if let Some(referent) = self.backrefs.get_key(*p_input) { if let Referent::Input(referent) = referent { - if !self.tnodes.contains(*referent) { + if !self.lnodes.contains(*referent) { return Err(EvalError::OtherString(format!( - "{p_tnode}: {tnode:?} input {p_input} referrent {referent} is \ + "{p_lnode}: {lnode:?} input {p_input} referrent {referent} is \ invalid" ))) } } else { return Err(EvalError::OtherString(format!( - "{p_tnode}: {tnode:?} input {p_input} has incorrect referrent" + "{p_lnode}: {lnode:?} input {p_input} has incorrect referrent" ))) } } else { return Err(EvalError::OtherString(format!( - "{p_tnode}: {tnode:?} input {p_input} is invalid" + "{p_lnode}: {lnode:?} input {p_input} is invalid" ))) } } } - for p_lnode in self.lnodes.ptrs() { - let lnode = self.lnodes.get(p_lnode).unwrap(); - if let Some(referent) = self.backrefs.get_key(lnode.p_driver) { + for p_tnode in self.tnodes.ptrs() { + let tnode = self.tnodes.get(p_tnode).unwrap(); + if let Some(referent) = self.backrefs.get_key(tnode.p_driver) { if let Referent::LoopDriver(p_driver) = referent { - if !self.lnodes.contains(*p_driver) { + if !self.tnodes.contains(*p_driver) { return Err(EvalError::OtherString(format!( - "{p_lnode}: {lnode:?} loop driver referrent {p_driver} is invalid" + "{p_tnode}: {tnode:?} loop driver referrent {p_driver} is invalid" ))) } } else { return Err(EvalError::OtherString(format!( - "{p_lnode}: {lnode:?} loop driver has incorrect referrent" + "{p_tnode}: {tnode:?} loop driver has incorrect referrent" ))) } } else { return Err(EvalError::OtherString(format!( - "{p_lnode}: {lnode:?} loop driver {} is invalid", - lnode.p_driver + "{p_tnode}: {tnode:?} loop driver {} is invalid", + tnode.p_driver ))) } } @@ -261,14 +261,14 @@ impl Ensemble { let fail = match referent { // already checked Referent::ThisEquiv => false, - Referent::ThisTNode(p_tnode) => { - let tnode = self.tnodes.get(*p_tnode).unwrap(); - p_back != tnode.p_self - } Referent::ThisLNode(p_lnode) => { let lnode = self.lnodes.get(*p_lnode).unwrap(); p_back != lnode.p_self } + Referent::ThisTNode(p_tnode) => { + let tnode = self.tnodes.get(*p_tnode).unwrap(); + p_back != tnode.p_self + } Referent::ThisStateBit(p_state, inx) => { let state = self.stator.states.get(*p_state).unwrap(); let p_bit = state.p_self_bits.get(*inx).unwrap(); @@ -279,9 +279,9 @@ impl Ensemble { } } Referent::Input(p_input) => { - let tnode = self.tnodes.get(*p_input).unwrap(); + let lnode = self.lnodes.get(*p_input).unwrap(); let mut found = false; - for p_back1 in &tnode.inp { + for p_back1 in &lnode.inp { if *p_back1 == p_back { found = true; break @@ -289,9 +289,9 @@ impl Ensemble { } !found } - Referent::LoopDriver(p_lnode) => { - let lnode = self.lnodes.get(*p_lnode).unwrap(); - lnode.p_driver != p_back + Referent::LoopDriver(p_tnode) => { + let tnode = self.tnodes.get(*p_tnode).unwrap(); + tnode.p_driver != p_back } Referent::Note(p_note) => { let note = self.notes.get(*p_note).unwrap(); @@ -312,9 +312,9 @@ impl Ensemble { } } // non-pointer invariants - for tnode in self.tnodes.vals() { - if let Some(ref lut) = tnode.lut { - if tnode.inp.is_empty() { + for lnode in self.lnodes.vals() { + if let Some(ref lut) = lnode.lut { + if lnode.inp.is_empty() { return Err(EvalError::OtherStr("no inputs for lookup table")) } if !lut.bw().is_power_of_two() { @@ -322,14 +322,14 @@ impl Ensemble { "lookup table is not a power of two in bitwidth", )) } - if (lut.bw().trailing_zeros() as usize) != tnode.inp.len() { + if (lut.bw().trailing_zeros() as usize) != lnode.inp.len() { return Err(EvalError::OtherStr( "number of inputs does not correspond to lookup table size", )) } - } else if tnode.inp.len() != 1 { + } else if lnode.inp.len() != 1 { return Err(EvalError::OtherStr( - "`TNode` with no lookup table has more or less than one input", + "`LNode` with no lookup table has more or less than one input", )) } } @@ -371,7 +371,7 @@ impl Ensemble { rc: 0, extern_rc: 0, lowered_to_elementary: false, - lowered_to_tnodes: false, + lowered_to_lnodes: false, }) } @@ -406,7 +406,7 @@ impl Ensemble { Some(()) } - /// Inserts a `TNode` with `lit` value and returns a `PBack` to it + /// Inserts a `LNode` with `lit` value and returns a `PBack` to it pub fn make_literal(&mut self, lit: Option) -> PBack { self.backrefs.insert_with(|p_self_equiv| { ( @@ -416,7 +416,7 @@ impl Ensemble { }) } - /// Makes a single output bit lookup table `TNode` and returns a `PBack` to + /// Makes a single output bit lookup table `LNode` and returns a `PBack` to /// it. Returns `None` if the table length is incorrect or any of the /// `p_inxs` are invalid. #[must_use] @@ -443,21 +443,21 @@ impl Ensemble { Equiv::new(p_self_equiv, Value::Unknown), ) }); - self.tnodes.insert_with(|p_tnode| { + self.lnodes.insert_with(|p_lnode| { let p_self = self .backrefs - .insert_key(p_equiv, Referent::ThisTNode(p_tnode)) + .insert_key(p_equiv, Referent::ThisLNode(p_lnode)) .unwrap(); - let mut tnode = TNode::new(p_self, lowered_from); - tnode.lut = Some(Awi::from(table)); + let mut lnode = LNode::new(p_self, lowered_from); + lnode.lut = Some(Awi::from(table)); for p_inx in p_inxs { let p_back = self .backrefs - .insert_key(p_inx.unwrap(), Referent::Input(p_tnode)) + .insert_key(p_inx.unwrap(), Referent::Input(p_lnode)) .unwrap(); - tnode.inp.push(p_back); + lnode.inp.push(p_back); } - tnode + lnode }); Some(p_equiv) } @@ -469,21 +469,21 @@ impl Ensemble { p_looper: PBack, p_driver: PBack, init_val: Value, - ) -> Option { - let p_lnode = self.lnodes.insert_with(|p_lnode| { + ) -> Option { + let p_tnode = self.tnodes.insert_with(|p_tnode| { let p_driver = self .backrefs - .insert_key(p_driver, Referent::LoopDriver(p_lnode)) + .insert_key(p_driver, Referent::LoopDriver(p_tnode)) .unwrap(); let p_self = self .backrefs - .insert_key(p_looper, Referent::ThisLNode(p_lnode)) + .insert_key(p_looper, Referent::ThisTNode(p_tnode)) .unwrap(); - LNode::new(p_self, p_driver) + TNode::new(p_self, p_driver) }); // in order for the value to register correctly self.change_value(p_looper, init_val).unwrap(); - Some(p_lnode) + Some(p_tnode) } pub fn union_equiv(&mut self, p_equiv0: PBack, p_equiv1: PBack) -> Result<(), EvalError> { diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 1056c6fd..2ae41a65 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -9,7 +9,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, PBack, PLNode, PTNode, Referent, TNode}, + ensemble::{Ensemble, LNode, PBack, PLNode, PTNode, Referent}, epoch::EpochShared, }; @@ -85,17 +85,17 @@ pub enum EvalPhase { } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct RequestTNode { +pub struct RequestLNode { pub depth: i64, pub number_a: u8, - pub p_back_tnode: PBack, + pub p_back_lnode: PBack, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct RequestLNode { +pub struct RequestTNode { pub depth: i64, pub number_a: u8, - pub p_back_lnode: PBack, + pub p_back_tnode: PBack, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -108,11 +108,11 @@ pub struct Change { #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Eval { Investigate0(i64, PBack), - ChangeTNode(PTNode), ChangeLNode(PLNode), + ChangeTNode(PTNode), Change(Change), - RequestTNode(RequestTNode), RequestLNode(RequestLNode), + RequestTNode(RequestTNode), /// When we have run out of normal things this will activate lowering Investigate1(PBack), } @@ -163,13 +163,13 @@ impl Evaluator { impl Ensemble { /// If the returned vector is empty, evaluation was successful, otherwise /// what is needed for evaluation is returned - pub fn try_eval_tnode(&mut self, p_tnode: PTNode, depth: i64) -> Vec { + pub fn try_eval_lnode(&mut self, p_lnode: PLNode, depth: i64) -> Vec { let mut res = vec![]; // read current inputs - let tnode = self.tnodes.get(p_tnode).unwrap(); - let p_equiv = self.backrefs.get_val(tnode.p_self).unwrap().p_self_equiv; - if let Some(original_lut) = &tnode.lut { - let len = u8::try_from(tnode.inp.len()).unwrap(); + let lnode = self.lnodes.get(p_lnode).unwrap(); + let p_equiv = self.backrefs.get_val(lnode.p_self).unwrap().p_self_equiv; + if let Some(original_lut) = &lnode.lut { + let len = u8::try_from(lnode.inp.len()).unwrap(); let len = usize::from(len); // the nominal value of the inputs let mut inp = Awi::zero(NonZeroUsize::new(len).unwrap()); @@ -179,7 +179,7 @@ impl Ensemble { // corresponding bits are set if the input is `Value::Unknown` let mut unknown = inp.clone(); for i in 0..len { - let p_inp = tnode.inp[i]; + let p_inp = lnode.inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); if let Value::Const(val) = equiv.val { fixed.set(i, true).unwrap(); @@ -200,7 +200,7 @@ impl Ensemble { for i in 0..len { if fixed.get(i).unwrap() && unknown.get(i).unwrap() - && TNode::reduce_independent_lut(&lut, i).is_none() + && LNode::reduce_independent_lut(&lut, i).is_none() { self.evaluator.insert(Eval::Change(Change { depth, @@ -213,7 +213,7 @@ impl Ensemble { // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { - lut = TNode::reduce_lut(&lut, i, inp.get(i).unwrap()); + lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); } } // if the LUT is all ones or all zeros, we can know that any unfixed or @@ -241,22 +241,22 @@ impl Ensemble { skip += 1; } else if unknown.get(i).unwrap() { // assume unchanging - lut = TNode::reduce_lut(&lut, i, inp.get(i).unwrap()); + lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); // } else {} }*/ for i in (0..len).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { - res.push(RequestTNode { + res.push(RequestLNode { depth: depth - 1, number_a: 0, - p_back_tnode: tnode.inp[i], + p_back_lnode: lnode.inp[i], }); } } } else { - // TNode without LUT - let p_inp = tnode.inp[0]; + // LNode without LUT + let p_inp = lnode.inp[0]; let equiv = self.backrefs.get_val(p_inp).unwrap(); if let Value::Const(val) = equiv.val { self.evaluator.insert(Eval::Change(Change { @@ -272,10 +272,10 @@ impl Ensemble { value: equiv.val, })); } else { - res.push(RequestTNode { + res.push(RequestLNode { depth: depth - 1, number_a: 0, - p_back_tnode: tnode.inp[0], + p_back_lnode: lnode.inp[0], }); } } @@ -284,11 +284,11 @@ impl Ensemble { /// If the returned vector is empty, evaluation was successful, otherwise /// what is needed for evaluation is returned - pub fn try_eval_lnode(&mut self, p_lnode: PLNode, depth: i64) -> Option { + pub fn try_eval_tnode(&mut self, p_tnode: PTNode, depth: i64) -> Option { // read current inputs - let lnode = self.lnodes.get(p_lnode).unwrap(); - let p_equiv = self.backrefs.get_val(lnode.p_self).unwrap().p_self_equiv; - let p_driver = lnode.p_driver; + let tnode = self.tnodes.get(p_tnode).unwrap(); + let p_equiv = self.backrefs.get_val(tnode.p_self).unwrap().p_self_equiv; + let p_driver = tnode.p_driver; let equiv = self.backrefs.get_val(p_driver).unwrap(); if let Value::Const(val) = equiv.val { self.evaluator.insert(Eval::Change(Change { @@ -306,10 +306,10 @@ impl Ensemble { })); None } else { - Some(RequestLNode { + Some(RequestTNode { depth: depth - 1, number_a: 0, - p_back_lnode: p_driver, + p_back_tnode: p_driver, }) } } @@ -411,7 +411,7 @@ impl Ensemble { if let Some(p_state) = lock.ensemble.stator.states_to_lower.pop() { if let Some(state) = lock.ensemble.stator.states.get(p_state) { // first check that it has not already been lowered - if !state.lowered_to_tnodes { + if !state.lowered_to_lnodes { drop(lock); Ensemble::dfs_lower(epoch_shared, p_state)?; let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -457,14 +457,14 @@ impl Ensemble { let evaluation = self.evaluator.evaluations.remove(p_eval).unwrap().0; match evaluation { Eval::Investigate0(depth, p_equiv) => self.eval_investigate0(p_equiv, depth), - Eval::ChangeTNode(p_tnode) => { + Eval::ChangeLNode(p_lnode) => { // the initial investigate handles all input requests // TODO get priorities right - let _ = self.try_eval_tnode(p_tnode, 0); - } - Eval::ChangeLNode(p_lnode) => { let _ = self.try_eval_lnode(p_lnode, 0); } + Eval::ChangeTNode(p_tnode) => { + let _ = self.try_eval_tnode(p_tnode, 0); + } Eval::Change(change) => { let equiv = self.backrefs.get_val_mut(change.p_equiv).unwrap(); equiv.change_visit = self.evaluator.change_visit_gen(); @@ -480,40 +480,40 @@ impl Ensemble { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv - | Referent::ThisTNode(_) | Referent::ThisLNode(_) + | Referent::ThisTNode(_) | Referent::ThisStateBit(..) => (), - Referent::Input(p_tnode) => { - let tnode = self.tnodes.get(p_tnode).unwrap(); - let p_self = tnode.p_self; + Referent::Input(p_lnode) => { + let lnode = self.lnodes.get(p_lnode).unwrap(); + let p_self = lnode.p_self; let equiv = self.backrefs.get_val(p_self).unwrap(); if (equiv.request_visit == self.evaluator.request_visit_gen()) && (equiv.change_visit != self.evaluator.change_visit_gen()) { // only go leafward to the given input if it was in the request // front and it hasn't been updated by some other route - self.evaluator.insert(Eval::ChangeTNode(p_tnode)); + self.evaluator.insert(Eval::ChangeLNode(p_lnode)); } } - Referent::LoopDriver(p_lnode) => { - let lnode = self.lnodes.get(p_lnode).unwrap(); - let p_self = lnode.p_self; + Referent::LoopDriver(p_tnode) => { + let tnode = self.tnodes.get(p_tnode).unwrap(); + let p_self = tnode.p_self; let equiv = self.backrefs.get_val(p_self).unwrap(); if (equiv.request_visit == self.evaluator.request_visit_gen()) && (equiv.change_visit != self.evaluator.change_visit_gen()) { // only go leafward to the given input if it was in the request // front and it hasn't been updated by some other route - self.evaluator.insert(Eval::ChangeLNode(p_lnode)); + self.evaluator.insert(Eval::ChangeTNode(p_tnode)); } } Referent::Note(_) => (), } } } - Eval::RequestTNode(request) => { - if let Referent::Input(_) = self.backrefs.get_key(request.p_back_tnode).unwrap() { - let equiv = self.backrefs.get_val(request.p_back_tnode).unwrap(); + Eval::RequestLNode(request) => { + if let Referent::Input(_) = self.backrefs.get_key(request.p_back_lnode).unwrap() { + let equiv = self.backrefs.get_val(request.p_back_lnode).unwrap(); if equiv.request_visit != self.evaluator.request_visit_gen() { self.evaluator .insert(Eval::Investigate0(request.depth, equiv.p_self_equiv)); @@ -522,11 +522,11 @@ impl Ensemble { unreachable!() } } - Eval::RequestLNode(request) => { + Eval::RequestTNode(request) => { if let Referent::LoopDriver(_) = - self.backrefs.get_key(request.p_back_lnode).unwrap() + self.backrefs.get_key(request.p_back_tnode).unwrap() { - let equiv = self.backrefs.get_val(request.p_back_lnode).unwrap(); + let equiv = self.backrefs.get_val(request.p_back_tnode).unwrap(); if equiv.request_visit != self.evaluator.request_visit_gen() { self.evaluator .insert(Eval::Investigate0(request.depth, equiv.p_self_equiv)); @@ -548,7 +548,7 @@ impl Ensemble { // no need to do anything return } - // eval but is only inserted if nothing like the TNode evaluation is able to + // eval but is only inserted if nothing like the LNode evaluation is able to // prove early value setting let mut insert_if_no_early_exit = vec![]; let mut saw_node = false; @@ -558,20 +558,20 @@ impl Ensemble { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv => (), - Referent::ThisTNode(p_tnode) => { - let v = self.try_eval_tnode(p_tnode, depth); + Referent::ThisLNode(p_lnode) => { + let v = self.try_eval_lnode(p_lnode, depth); if v.is_empty() { // early exit because evaluation was successful return } for request in v { - insert_if_no_early_exit.push(Eval::RequestTNode(request)); + insert_if_no_early_exit.push(Eval::RequestLNode(request)); } saw_node = true; } - Referent::ThisLNode(p_lnode) => { - if let Some(request) = self.try_eval_lnode(p_lnode, depth) { - insert_if_no_early_exit.push(Eval::RequestLNode(request)); + Referent::ThisTNode(p_tnode) => { + if let Some(request) = self.try_eval_tnode(p_tnode, depth) { + insert_if_no_early_exit.push(Eval::RequestTNode(request)); } else { // early exit because evaluation was successful return @@ -589,7 +589,7 @@ impl Ensemble { if !saw_node { let mut will_lower = false; if let Some(p_state) = saw_state { - if !self.stator.states[p_state].lowered_to_tnodes { + if !self.stator.states[p_state].lowered_to_lnodes { will_lower = true; self.stator.states_to_lower.push(p_state); } diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index c1b8634d..c304558b 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -97,7 +97,7 @@ //! // Now the combinational logic is described in a DAG of lookup tables that we //! // could use for various purposes //! for state in epoch0.ensemble().stator.states.vals() { -//! awi::assert!(state.lowered_to_tnodes); +//! awi::assert!(state.lowered_to_lnodes); //! } //! //! // "retroactively" assign the input with a non-opaque value @@ -165,7 +165,7 @@ #![allow(clippy::comparison_chain)] mod awi_structs; -/// Internals used by this crate to deal with states and TNode DAGs +/// Data structure internals used by this crate pub mod ensemble; pub mod lower; mod misc; diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index cbdf9c96..be18fd70 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -147,14 +147,14 @@ fn luts() { let ensemble = epoch0.ensemble(); - // assert that there is at most one TNode with constant inputs optimized away - let mut tnodes = ensemble.tnodes.vals(); - if let Some(tnode) = tnodes.next() { - inp_bits += tnode.inp.len(); - assert!(tnode.inp.len() <= opaque_set.count_ones()); - assert!(tnodes.next().is_none()); + // assert that there is at most one LNode with constant inputs optimized away + let mut lnodes = ensemble.lnodes.vals(); + if let Some(lnode) = lnodes.next() { + inp_bits += lnode.inp.len(); + assert!(lnode.inp.len() <= opaque_set.count_ones()); + assert!(lnodes.next().is_none()); } - assert!(tnodes.next().is_none()); + assert!(lnodes.next().is_none()); } } } From 5faba83f76e8857bc4d8881009c0473dde0d19ad Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 13:05:05 -0600 Subject: [PATCH 003/119] Update tnode.rs --- starlight/src/ensemble/tnode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starlight/src/ensemble/tnode.rs b/starlight/src/ensemble/tnode.rs index 2591882d..c5246685 100644 --- a/starlight/src/ensemble/tnode.rs +++ b/starlight/src/ensemble/tnode.rs @@ -5,7 +5,7 @@ use crate::ensemble::PBack; // We use this because our algorithms depend on generation counters ptr_struct!(PTNode); -/// A temporal loopback node +/// A temporal node, currently just used for loopbacks #[derive(Debug, Clone)] pub struct TNode { pub p_self: PBack, From e07402905e4600a77d91a36abed9a42b018f63dc Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 13:25:40 -0600 Subject: [PATCH 004/119] rename `Note` to `RNode` --- starlight/src/awi_structs/epoch.rs | 16 ++-- starlight/src/awi_structs/eval_awi.rs | 22 +++--- starlight/src/awi_structs/lazy_awi.rs | 30 ++++---- starlight/src/ensemble.rs | 4 +- starlight/src/ensemble/debug.rs | 26 +++---- starlight/src/ensemble/optimize.rs | 14 ++-- starlight/src/ensemble/{note.rs => rnode.rs} | 81 +++++++++----------- starlight/src/ensemble/together.rs | 32 ++++---- starlight/src/ensemble/value.rs | 4 +- testcrate/tests/basic.rs | 2 +- testcrate/tests/fuzz_elementary.rs | 2 +- 11 files changed, 114 insertions(+), 119 deletions(-) rename starlight/src/ensemble/{note.rs => rnode.rs} (58%) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index e968c6e3..83f563a9 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -168,28 +168,28 @@ impl EpochShared { .assertions .bits[i]; let p_state = eval_awi.state(); - let p_note = eval_awi.p_note(); + let p_rnode = eval_awi.p_rnode(); drop(epoch_data); - let val = Ensemble::calculate_thread_local_note_value(p_note, 0)?; + let val = Ensemble::calculate_thread_local_rnode_value(p_rnode, 0)?; if let Some(val) = val.known_value() { if !val { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { return Err(EvalError::OtherString(format!( - "an assertion bit evaluated to false, failed on {p_note} {:?}", + "an assertion bit evaluated to false, failed on {p_rnode} {:?}", s ))) } else { return Err(EvalError::OtherString(format!( - "an assertion bit evaluated to false, failed on {p_note} {p_state}" + "an assertion bit evaluated to false, failed on {p_rnode} {p_state}" ))) } } } else if unknown.is_none() { // get the earliest failure to evaluate, should be closest to the root cause. // Wait for all bits to be checked for falsity - unknown = Some((p_note, p_state)); + unknown = Some((p_rnode, p_state)); } if val.is_const() { // remove the assertion @@ -209,19 +209,19 @@ impl EpochShared { } } if strict { - if let Some((p_note, p_state)) = unknown { + if let Some((p_rnode, p_state)) = unknown { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { return Err(EvalError::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ - {p_note} {:?}", + {p_rnode} {:?}", s ))) } else { return Err(EvalError::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ - {p_note} {p_state}" + {p_rnode} {p_state}" ))) } } diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index cb082352..4cf534a7 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -7,7 +7,7 @@ use awint::{ use crate::{ awi, - ensemble::{Ensemble, PNote}, + ensemble::{Ensemble, PRNode}, epoch::get_current_epoch, }; @@ -27,7 +27,7 @@ use crate::{ /// current `Epoch`. pub struct EvalAwi { p_state: PState, - p_note: PNote, + p_rnode: PRNode, } impl Drop for EvalAwi { @@ -36,7 +36,7 @@ impl Drop for EvalAwi { if !panicking() { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); - let res = lock.ensemble.remove_note(self.p_note); + let res = lock.ensemble.remove_rnode(self.p_rnode); if res.is_err() { panic!( "most likely, an `EvalAwi` created in one `Epoch` was dropped in another" @@ -58,7 +58,7 @@ impl Lineage for EvalAwi { } impl Clone for EvalAwi { - /// This makes another note to the same state that `self` pointed to. + /// This makes another rnode to the same state that `self` pointed to. #[track_caller] fn clone(&self) -> Self { Self::from_state(self.p_state) @@ -94,15 +94,15 @@ impl EvalAwi { ); pub fn nzbw(&self) -> NonZeroUsize { - Ensemble::get_thread_local_note_nzbw(self.p_note).unwrap() + Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() } pub fn bw(&self) -> usize { self.nzbw().get() } - pub fn p_note(&self) -> PNote { - self.p_note + pub fn p_rnode(&self) -> PRNode { + self.p_rnode } /// Used internally to create `EvalAwi`s @@ -114,15 +114,15 @@ impl EvalAwi { pub fn from_state(p_state: PState) -> Self { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); - match lock.ensemble.note_pstate(p_state) { - Some(p_note) => { + match lock.ensemble.make_rnode_for_pstate(p_state) { + Some(p_rnode) => { lock.ensemble .stator .states .get_mut(p_state) .unwrap() .inc_extern_rc(); - Self { p_state, p_note } + Self { p_state, p_rnode } } None => { panic!( @@ -146,7 +146,7 @@ impl EvalAwi { let nzbw = self.nzbw(); let mut res = awi::Awi::zero(nzbw); for bit_i in 0..res.bw() { - let val = Ensemble::calculate_thread_local_note_value(self.p_note, bit_i)?; + let val = Ensemble::calculate_thread_local_rnode_value(self.p_rnode, bit_i)?; if let Some(val) = val.known_value() { res.set(bit_i, val).unwrap(); } else { diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index 45457182..d036c2e1 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -13,7 +13,7 @@ use awint::{ use crate::{ awi, - ensemble::{Ensemble, PNote}, + ensemble::{Ensemble, PRNode}, epoch::get_current_epoch, }; @@ -34,7 +34,7 @@ use crate::{ /// current `Epoch` pub struct LazyAwi { opaque: dag::Awi, - p_note: PNote, + p_rnode: PRNode, } impl Drop for LazyAwi { @@ -46,7 +46,7 @@ impl Drop for LazyAwi { .epoch_data .borrow_mut() .ensemble - .remove_note(self.p_note); + .remove_rnode(self.p_rnode); if res.is_err() { panic!("most likely, a `LazyAwi` created in one `Epoch` was dropped in another") } @@ -68,7 +68,7 @@ impl LazyAwi { } pub fn nzbw(&self) -> NonZeroUsize { - Ensemble::get_thread_local_note_nzbw(self.p_note).unwrap() + Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() } pub fn bw(&self) -> usize { @@ -77,14 +77,14 @@ impl LazyAwi { pub fn opaque(w: NonZeroUsize) -> Self { let opaque = dag::Awi::opaque(w); - let p_note = get_current_epoch() + let p_rnode = get_current_epoch() .unwrap() .epoch_data .borrow_mut() .ensemble - .note_pstate(opaque.state()) + .make_rnode_for_pstate(opaque.state()) .unwrap(); - Self { opaque, p_note } + Self { opaque, p_rnode } } // TODO it probably does need to be an extra `Awi` in the `Opaque` variant, @@ -121,7 +121,7 @@ impl LazyAwi { /// if this is being called after the corresponding Epoch is dropped and /// states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_note_value(self.p_note, rhs) + Ensemble::change_thread_local_rnode_value(self.p_rnode, rhs) } } @@ -166,7 +166,7 @@ forward_debug_fmt!(LazyAwi); #[derive(Clone, Copy)] pub struct LazyInlAwi { opaque: dag::InlAwi, - p_note: PNote, + p_rnode: PRNode, } #[macro_export] @@ -202,27 +202,27 @@ impl LazyInlAwi { self.nzbw().get() } - pub fn p_note(&self) -> PNote { - self.p_note + pub fn p_rnode(&self) -> PRNode { + self.p_rnode } pub fn opaque() -> Self { let opaque = dag::InlAwi::opaque(); - let p_note = get_current_epoch() + let p_rnode = get_current_epoch() .unwrap() .epoch_data .borrow_mut() .ensemble - .note_pstate(opaque.state()) + .make_rnode_for_pstate(opaque.state()) .unwrap(); - Self { opaque, p_note } + Self { opaque, p_rnode } } /// Retroactively-assigns by `rhs`. Returns `None` if bitwidths mismatch or /// if this is being called after the corresponding Epoch is dropped and /// states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_note_value(self.p_note, rhs) + Ensemble::change_thread_local_rnode_value(self.p_rnode, rhs) } } diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index e806a012..baa291f1 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -1,16 +1,16 @@ #[cfg(feature = "debug")] mod debug; mod lnode; -mod note; mod optimize; +mod rnode; mod state; mod tnode; mod together; mod value; pub use lnode::{LNode, PLNode}; -pub use note::{Note, PNote}; pub use optimize::Optimizer; +pub use rnode::{PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; pub use together::{Ensemble, Equiv, PBack, Referent}; diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 8f690b94..481c1ea9 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -6,7 +6,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, Equiv, LNode, PBack, PNote, PTNode, Referent, State}, + ensemble::{Ensemble, Equiv, LNode, PBack, PRNode, PTNode, Referent, State}, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, Epoch, @@ -84,10 +84,10 @@ pub struct TNodeTmp { } #[derive(Debug, Clone)] -pub struct NoteTmp { +pub struct RNodeTmp { p_self: PBack, p_equiv: PBack, - p_note: PNote, + p_rnode: PRNode, i: u64, } @@ -97,7 +97,7 @@ pub enum NodeKind { LNode(LNode), TNode(TNodeTmp), Equiv(Equiv, Vec), - Note(NoteTmp), + RNode(RNodeTmp), Remove, } @@ -160,12 +160,12 @@ impl DebugNodeTrait for NodeKind { }, sinks: vec![], }, - NodeKind::Note(note) => DebugNode { - sources: vec![(note.p_equiv, String::new())], + NodeKind::RNode(rnode) => DebugNode { + sources: vec![(rnode.p_equiv, String::new())], center: { vec![ - format!("{}", note.p_self), - format!("{} [{}]", note.p_note, note.i), + format!("{}", rnode.p_self), + format!("{} [{}]", rnode.p_rnode, rnode.i), ] }, sinks: vec![], @@ -238,19 +238,19 @@ impl Ensemble { p_tnode: *p_tnode, }) } - Referent::Note(p_note) => { - let note = self.notes.get(*p_note).unwrap(); + Referent::ThisRNode(p_rnode) => { + let rnode = self.rnodes.get(*p_rnode).unwrap(); let mut inx = u64::MAX; - for (i, bit) in note.bits.iter().enumerate() { + for (i, bit) in rnode.bits.iter().enumerate() { if *bit == Some(p_self) { inx = u64::try_from(i).unwrap(); } } let equiv = self.backrefs.get_val(p_self).unwrap(); - NodeKind::Note(NoteTmp { + NodeKind::RNode(RNodeTmp { p_self, p_equiv: equiv.p_self_equiv, - p_note: *p_note, + p_rnode: *p_rnode, i: inx, }) } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 946f673a..577a4577 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -255,7 +255,7 @@ impl Ensemble { // TODO check for const through loop, but there should be a // parameter to enable } - Referent::Note(_) => non_self_rc += 1, + Referent::ThisRNode(_) => non_self_rc += 1, } } if non_self_rc == 0 { @@ -424,16 +424,16 @@ impl Ensemble { .unwrap(); tnode.p_driver = p_back_new; } - Referent::Note(p_note) => { + Referent::ThisRNode(p_rnode) => { // here we see a major advantage of the backref system - let note = self.notes.get_mut(p_note).unwrap(); + let rnode = self.rnodes.get_mut(p_rnode).unwrap(); let mut found = false; - for bit in &mut note.bits { + for bit in &mut rnode.bits { if let Some(bit) = bit { if *bit == p_back { let p_back_new = self .backrefs - .insert_key(p_source, Referent::Note(p_note)) + .insert_key(p_source, Referent::ThisRNode(p_rnode)) .unwrap(); *bit = p_back_new; found = true; @@ -477,7 +477,7 @@ impl Ensemble { self.optimizer .insert(Optimization::InvestigateLoopDriverConst(*p_driver)); } - Referent::Note(_) => (), + Referent::ThisRNode(_) => (), } } for p_back in remove { @@ -521,7 +521,7 @@ impl Ensemble { break } } - Referent::Note(_) => { + Referent::ThisRNode(_) => { found_use = true; break } diff --git a/starlight/src/ensemble/note.rs b/starlight/src/ensemble/rnode.rs similarity index 58% rename from starlight/src/ensemble/note.rs rename to starlight/src/ensemble/rnode.rs index 17d64e56..8f269cf8 100644 --- a/starlight/src/ensemble/note.rs +++ b/starlight/src/ensemble/rnode.rs @@ -8,54 +8,49 @@ use crate::{ epoch::get_current_epoch, }; -ptr_struct!(PNote); +ptr_struct!(PRNode); +/// Reference node, used for external references kept alive after `State` +/// pruning #[derive(Debug, Clone)] -pub struct Note { +pub struct RNode { pub bits: Vec>, } -impl Note { +impl RNode { pub fn new() -> Self { Self { bits: vec![] } } } impl Ensemble { - /// Sets up an extra reference to `p_refer` #[must_use] - pub fn make_note(&mut self, p_note: PNote, p_refer: PBack) -> Option { - let p_equiv = self.backrefs.get_val(p_refer)?.p_self_equiv; - let p_back_new = self - .backrefs - .insert_key(p_equiv, Referent::Note(p_note)) - .unwrap(); - Some(p_back_new) - } - - #[must_use] - pub fn note_pstate(&mut self, p_state: PState) -> Option { + pub fn make_rnode_for_pstate(&mut self, p_state: PState) -> Option { self.initialize_state_bits_if_needed(p_state)?; - let p_note = self.notes.insert(Note::new()); + let p_rnode = self.rnodes.insert(RNode::new()); let len = self.stator.states[p_state].p_self_bits.len(); for i in 0..len { let p_bit = self.stator.states[p_state].p_self_bits[i]; if let Some(p_bit) = p_bit { - let p_back = self.make_note(p_note, p_bit).unwrap(); - self.notes[p_note].bits.push(Some(p_back)); + let p_equiv = self.backrefs.get_val(p_bit)?.p_self_equiv; + let p_back_new = self + .backrefs + .insert_key(p_equiv, Referent::ThisRNode(p_rnode)) + .unwrap(); + self.rnodes[p_rnode].bits.push(Some(p_back_new)); } else { - self.notes[p_note].bits.push(None); + self.rnodes[p_rnode].bits.push(None); } } - Some(p_note) + Some(p_rnode) } - pub fn remove_note(&mut self, p_note: PNote) -> Result<(), EvalError> { - if let Some(note) = self.notes.remove(p_note) { - for p_back in note.bits { + pub fn remove_rnode(&mut self, p_rnode: PRNode) -> Result<(), EvalError> { + if let Some(rnode) = self.rnodes.remove(p_rnode) { + for p_back in rnode.bits { if let Some(p_back) = p_back { let referent = self.backrefs.remove_key(p_back).unwrap().0; - assert!(matches!(referent, Referent::Note(_))); + assert!(matches!(referent, Referent::ThisRNode(_))); } } Ok(()) @@ -64,33 +59,33 @@ impl Ensemble { } } - pub fn get_thread_local_note_nzbw(p_note: PNote) -> Result { + pub fn get_thread_local_rnode_nzbw(p_rnode: PRNode) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(note) = ensemble.notes.get(p_note) { - Ok(NonZeroUsize::new(note.bits.len()).unwrap()) + if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + Ok(NonZeroUsize::new(rnode.bits.len()).unwrap()) } else { - Err(EvalError::OtherStr("could not find thread local `Note`")) + Err(EvalError::OtherStr("could not find thread local `RNode`")) } } - pub fn change_thread_local_note_value( - p_note: PNote, + pub fn change_thread_local_rnode_value( + p_rnode: PRNode, bits: &awi::Bits, ) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(note) = ensemble.notes.get(p_note) { - if note.bits.len() != bits.bw() { + if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + if rnode.bits.len() != bits.bw() { return Err(EvalError::WrongBitwidth); } } else { - return Err(EvalError::OtherStr("could not find thread local `Note`")) + return Err(EvalError::OtherStr("could not find thread local `RNode`")) } for bit_i in 0..bits.bw() { - let p_back = ensemble.notes[p_note].bits[bit_i]; + let p_back = ensemble.rnodes[p_rnode].bits[bit_i]; if let Some(p_back) = p_back { ensemble .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) @@ -100,28 +95,28 @@ impl Ensemble { Ok(()) } - pub fn calculate_thread_local_note_value( - p_note: PNote, + pub fn calculate_thread_local_rnode_value( + p_rnode: PRNode, bit_i: usize, ) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - let p_back = if let Some(note) = ensemble.notes.get(p_note) { - if bit_i >= note.bits.len() { + let p_back = if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + if bit_i >= rnode.bits.len() { return Err(EvalError::OtherStr( - "something went wrong with note bitwidth", + "something went wrong with rnode bitwidth", )); } - if let Some(p_back) = note.bits[bit_i] { + if let Some(p_back) = rnode.bits[bit_i] { p_back } else { return Err(EvalError::OtherStr( - "something went wrong, found `Note` for evaluator but a bit was denoted", + "something went wrong, found `RNode` for evaluator but a bit was pruned", )) } } else { - return Err(EvalError::OtherStr("could not find thread local `Note`")) + return Err(EvalError::OtherStr("could not find thread local `RNode`")) }; if ensemble.stator.states.is_empty() { // optimization after total pruning from `optimization` @@ -133,7 +128,7 @@ impl Ensemble { } } -impl Default for Note { +impl Default for RNode { fn default() -> Self { Self::new() } diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 4e305599..8b8b499b 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -10,7 +10,7 @@ use awint::{ use crate::{ ensemble::{ - value::Evaluator, LNode, Note, Optimizer, PLNode, PNote, PTNode, State, Stator, TNode, + value::Evaluator, LNode, Optimizer, PLNode, PRNode, PTNode, RNode, State, Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, @@ -54,14 +54,14 @@ pub enum Referent { Input(PLNode), /// Referent is using this for a loop driver LoopDriver(PTNode), - /// Referent is a note - Note(PNote), + /// Referent is an `RNode` + ThisRNode(PRNode), } #[derive(Debug, Clone)] pub struct Ensemble { pub backrefs: SurjectArena, - pub notes: Arena, + pub rnodes: Arena, pub stator: Stator, pub lnodes: Arena, pub tnodes: Arena, @@ -74,7 +74,7 @@ impl Ensemble { pub fn new() -> Self { Self { backrefs: SurjectArena::new(), - notes: Arena::new(), + rnodes: Arena::new(), stator: Stator::new(), lnodes: Arena::new(), tnodes: Arena::new(), @@ -183,7 +183,7 @@ impl Ensemble { Referent::ThisStateBit(..) => false, Referent::Input(p_input) => !self.lnodes.contains(*p_input), Referent::LoopDriver(p_driver) => !self.tnodes.contains(*p_driver), - Referent::Note(p_note) => !self.notes.contains(*p_note), + Referent::ThisRNode(p_rnode) => !self.rnodes.contains(*p_rnode), }; if invalid { return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) @@ -234,23 +234,23 @@ impl Ensemble { ))) } } - for note in self.notes.vals() { - for p_back in ¬e.bits { + for rnode in self.rnodes.vals() { + for p_back in &rnode.bits { if let Some(p_back) = p_back { if let Some(referent) = self.backrefs.get_key(*p_back) { - if let Referent::Note(p_note) = referent { - if !self.notes.contains(*p_note) { + if let Referent::ThisRNode(p_rnode) = referent { + if !self.rnodes.contains(*p_rnode) { return Err(EvalError::OtherString(format!( - "{note:?} backref {p_note} is invalid" + "{rnode:?} backref {p_rnode} is invalid" ))) } } else { return Err(EvalError::OtherString(format!( - "{note:?} backref {p_back} has incorrect referrent" + "{rnode:?} backref {p_back} has incorrect referrent" ))) } } else { - return Err(EvalError::OtherString(format!("note {p_back} is invalid"))) + return Err(EvalError::OtherString(format!("rnode {p_back} is invalid"))) } } } @@ -293,10 +293,10 @@ impl Ensemble { let tnode = self.tnodes.get(*p_tnode).unwrap(); tnode.p_driver != p_back } - Referent::Note(p_note) => { - let note = self.notes.get(*p_note).unwrap(); + Referent::ThisRNode(p_rnode) => { + let rnode = self.rnodes.get(*p_rnode).unwrap(); let mut found = false; - for bit in ¬e.bits { + for bit in &rnode.bits { if *bit == Some(p_back) { found = true; break diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 2ae41a65..76210b35 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -507,7 +507,7 @@ impl Ensemble { self.evaluator.insert(Eval::ChangeTNode(p_tnode)); } } - Referent::Note(_) => (), + Referent::ThisRNode(_) => (), } } } @@ -583,7 +583,7 @@ impl Ensemble { } Referent::Input(_) => (), Referent::LoopDriver(_) => (), - Referent::Note(_) => (), + Referent::ThisRNode(_) => (), } } if !saw_node { diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index be18fd70..8c97320f 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -30,7 +30,7 @@ fn lazy_awi() -> Option<()> { } // cleans up everything not still used by `LazyAwi`s, `LazyAwi`s deregister - // notes when dropped + // rnodes when dropped drop(epoch0); Some(()) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 2d7571e1..9c8fab22 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -181,4 +181,4 @@ fn fuzz_elementary() { } } -// TODO need a version with loops and random notes +// TODO need a version with loops and random rnodes From 838e39221dde7d8ebc54d2a5a123aae85e1c34dc Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 13:29:36 -0600 Subject: [PATCH 005/119] Update lazy_awi.rs --- starlight/src/awi_structs/lazy_awi.rs | 30 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index d036c2e1..c4888017 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -37,6 +37,7 @@ pub struct LazyAwi { p_rnode: PRNode, } +// NOTE: when changing this also remember to change `LazyInlAwi` impl Drop for LazyAwi { fn drop(&mut self) { // prevent invoking recursive panics and a buffer overrun @@ -163,7 +164,7 @@ forward_debug_fmt!(LazyAwi); /// The same as [LazyAwi](crate::LazyAwi), except that it allows for checking /// bitwidths at compile time. -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct LazyInlAwi { opaque: dag::InlAwi, p_rnode: PRNode, @@ -183,6 +184,27 @@ macro_rules! lazy_inlawi_ty { }; } +impl Drop for LazyInlAwi { + fn drop(&mut self) { + // prevent invoking recursive panics and a buffer overrun + if !panicking() { + if let Some(epoch) = get_current_epoch() { + let res = epoch + .epoch_data + .borrow_mut() + .ensemble + .remove_rnode(self.p_rnode); + if res.is_err() { + panic!( + "most likely, a `LazyInlAwi` created in one `Epoch` was dropped in another" + ) + } + } + // else the epoch has been dropped + } + } +} + impl Lineage for LazyInlAwi { fn state(&self) -> PState { self.opaque.state() @@ -195,17 +217,13 @@ impl LazyInlAwi { } pub fn nzbw(&self) -> NonZeroUsize { - self.opaque.nzbw() + Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() } pub fn bw(&self) -> usize { self.nzbw().get() } - pub fn p_rnode(&self) -> PRNode { - self.p_rnode - } - pub fn opaque() -> Self { let opaque = dag::InlAwi::opaque(); let p_rnode = get_current_epoch() From a9ae1229b756291bd8ababe7dc3b4f05943db86a Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 12 Dec 2023 20:48:48 -0600 Subject: [PATCH 006/119] Begin work on router --- starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/cgraph.rs | 0 starlight/src/ensemble/rnode.rs | 4 ++-- starlight/src/lib.rs | 1 + starlight/src/route.rs | 7 ++++++ starlight/src/route/channel.rs | 41 ++++++++++++++++++++++++++++++++ starlight/src/route/path.rs | 22 +++++++++++++++++ starlight/src/route/router.rs | 14 +++++++++++ 8 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 starlight/src/ensemble/cgraph.rs create mode 100644 starlight/src/route.rs create mode 100644 starlight/src/route/channel.rs create mode 100644 starlight/src/route/path.rs create mode 100644 starlight/src/route/router.rs diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index baa291f1..e6888db0 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -9,7 +9,7 @@ mod together; mod value; pub use lnode::{LNode, PLNode}; -pub use optimize::Optimizer; +pub use optimize::{Optimizer, POpt}; pub use rnode::{PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; diff --git a/starlight/src/ensemble/cgraph.rs b/starlight/src/ensemble/cgraph.rs new file mode 100644 index 00000000..e69de29b diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 8f269cf8..d8bfe099 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -10,8 +10,8 @@ use crate::{ ptr_struct!(PRNode); -/// Reference node, used for external references kept alive after `State` -/// pruning +/// Reference/Register/Report node, used for external references kept alive +/// after `State` pruning #[derive(Debug, Clone)] pub struct RNode { pub bits: Vec>, diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index c304558b..1456968f 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -169,6 +169,7 @@ mod awi_structs; pub mod ensemble; pub mod lower; mod misc; +pub mod route; pub use awi_structs::{epoch, Assertions, Epoch, EvalAwi, LazyAwi, LazyInlAwi, Loop, Net}; #[cfg(feature = "debug")] pub use awint::awint_dag::triple_arena_render; diff --git a/starlight/src/route.rs b/starlight/src/route.rs new file mode 100644 index 00000000..2fef7dc1 --- /dev/null +++ b/starlight/src/route.rs @@ -0,0 +1,7 @@ +mod channel; +mod path; +mod router; + +pub use channel::{CEdge, CNode, Program}; +pub use path::{HyperPath, PHyperPath, Path}; +pub use router::Router; diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs new file mode 100644 index 00000000..e4248139 --- /dev/null +++ b/starlight/src/route/channel.rs @@ -0,0 +1,41 @@ +use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; + +ptr_struct!(PCNode; PCEdge); + +/// A channel node +#[derive(Debug, Clone)] +pub struct CNode { + /// hierarchical capability, routing starts at some single top level node + /// and descends + subnodes: SmallVec<[PCNode; 2]>, + /// The hierarchy is like a dual overlapping binary tree one + supernodes: SmallVec<[PCNode; 2]>, + /// at the bottom of the hierarchy are always unit `CNode`s marking a single + /// value point. + p_equiv: Option, +} + +/// A description of bits to set in order to achieve some desired edge behavior. +/// For now we unconditionally specify bits, in the future it should be more +/// detailed to allow for more close by programs to coexist +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Program { + bits: SmallVec<[(PBack, bool); 4]>, +} + +/// An edge between channels +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CEdge { + /// The source `CNode`, this is readonly but bidirectional `Net`s can be + /// represented with two `CEdge`s going both ways + source: PCNode, + /// The sink `CNode` + sink: PCNode, + /// Describes the required program to route a value (could be the `p_equiv` + /// in a unit `CNode` or bulk routing through higher level `CNode`s) from + /// the source to the sink. + program: Program, + // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for + // wide edges delay_weight: u64, + //lagrangian_weight: u64, +} diff --git a/starlight/src/route/path.rs b/starlight/src/route/path.rs new file mode 100644 index 00000000..34e74c76 --- /dev/null +++ b/starlight/src/route/path.rs @@ -0,0 +1,22 @@ +use crate::{ + route::channel::{PCEdge, PCNode}, + triple_arena::ptr_struct, +}; + +ptr_struct!(PHyperPath); + +/// A single path from a source to sink across multiple `CEdge`s +#[derive(Debug, Clone)] +pub struct Path { + sink: PCNode, + edges: Vec, + //critical_multiplier: u64, +} + +/// Represents the "hyperpath" that a logical bit will take from a `source` node +/// to one ore more `sink` nodes. Sinks can have different priorities. +#[derive(Debug, Clone)] +pub struct HyperPath { + source: PCNode, + paths: Vec, +} diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs new file mode 100644 index 00000000..ec8ed451 --- /dev/null +++ b/starlight/src/route/router.rs @@ -0,0 +1,14 @@ +use crate::{ + route::{ + channel::{PCEdge, PCNode}, + CEdge, CNode, HyperPath, PHyperPath, + }, + triple_arena::{Arena, OrdArena}, +}; + +#[derive(Debug, Clone)] +pub struct Router { + cnodes: Arena, + cedges: OrdArena, + hyperpaths: Arena, +} From 1476da00097c6f69b0616e2406e74c3b0deae239 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 13 Dec 2023 15:46:13 -0600 Subject: [PATCH 007/119] more planning --- starlight/src/route.rs | 2 +- starlight/src/route/channel.rs | 131 +++++++++++++++++++++++++++++++-- starlight/src/route/router.rs | 30 ++++++-- 3 files changed, 148 insertions(+), 15 deletions(-) diff --git a/starlight/src/route.rs b/starlight/src/route.rs index 2fef7dc1..2590280d 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -2,6 +2,6 @@ mod channel; mod path; mod router; -pub use channel::{CEdge, CNode, Program}; +pub use channel::{CEdge, CNode, Channeler, Program}; pub use path::{HyperPath, PHyperPath, Path}; pub use router::Router; diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index e4248139..adf54943 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,18 +1,22 @@ +use std::cmp::Ordering; + +use awint::awint_dag::{ + smallvec::smallvec, + triple_arena::{Arena, ArenaTrait, OrdArena}, +}; + use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; -ptr_struct!(PCNode; PCEdge); +ptr_struct!(PCNode; PCEdge; PBackToCNode); /// A channel node #[derive(Debug, Clone)] pub struct CNode { - /// hierarchical capability, routing starts at some single top level node + /// hierarchical capability /// and descends subnodes: SmallVec<[PCNode; 2]>, /// The hierarchy is like a dual overlapping binary tree one supernodes: SmallVec<[PCNode; 2]>, - /// at the bottom of the hierarchy are always unit `CNode`s marking a single - /// value point. - p_equiv: Option, } /// A description of bits to set in order to achieve some desired edge behavior. @@ -20,17 +24,20 @@ pub struct CNode { /// detailed to allow for more close by programs to coexist #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Program { - bits: SmallVec<[(PBack, bool); 4]>, + pub bits: SmallVec<[(PBack, bool); 4]>, } /// An edge between channels -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone)] pub struct CEdge { /// The source `CNode`, this is readonly but bidirectional `Net`s can be /// represented with two `CEdge`s going both ways source: PCNode, /// The sink `CNode` sink: PCNode, + + // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only + // respect the above and any insertion needs to check for duplicates /// Describes the required program to route a value (could be the `p_equiv` /// in a unit `CNode` or bulk routing through higher level `CNode`s) from /// the source to the sink. @@ -39,3 +46,113 @@ pub struct CEdge { // wide edges delay_weight: u64, //lagrangian_weight: u64, } + +impl PartialEq for CEdge { + fn eq(&self, other: &Self) -> bool { + self.source == other.source && self.sink == other.sink + } +} + +impl Eq for CEdge {} + +impl PartialOrd for CEdge { + fn partial_cmp(&self, other: &Self) -> Option { + match self.source.partial_cmp(&other.source) { + Some(Ordering::Equal) => {} + ord => return ord, + } + self.sink.partial_cmp(&other.sink) + } +} + +impl Ord for CEdge { + fn cmp(&self, other: &Self) -> Ordering { + match self.source.cmp(&other.source) { + Ordering::Equal => {} + ord => return ord, + } + self.sink.cmp(&other.sink) + } +} + +impl CEdge { + pub fn program(&self) -> &Program { + &self.program + } +} + +/// Management struct for channel nodes and edges +#[derive(Debug, Clone)] +pub struct Channeler { + cnodes: Arena, + cedges: OrdArena, + /// On hard dependencies where a path needs to connect to a particular + /// `PBack`, valid descencions in the `CNode` hierarchy are determined by + /// `find_with` to first get to the desired `PBack` section, then linear + /// iterating to figure out which `CNode`s contain the `PBack`. The space is + /// limited to a `n*log(n)`, there is probably some inevitable `n*log(n)` + /// cost somewhere. + backref_to_cnode: OrdArena, +} + +impl Channeler { + pub fn new() -> Self { + Self { + cnodes: Arena::new(), + cedges: OrdArena::new(), + backref_to_cnode: OrdArena::new(), + } + } + + pub fn make_cnode(&mut self, p_equiv: PBack) -> PCNode { + self.cnodes.insert(CNode { + subnodes: smallvec![], + supernodes: smallvec![], + }) + } + + pub fn make_cedge(&mut self, source: PCNode, sink: PCNode, program: Program) -> PCEdge { + let (p_new, duplicate) = self.cedges.insert( + CEdge { + source, + sink, + program, + }, + (), + ); + // there may be future circumstances where we allow this and combine + // appropriately, but disallow for now + duplicate.unwrap(); + p_new + } + + /// Starting from unit `CNode`s and `CEdge`s describing all known low level + /// progam methods, this generates a logarithmic tree of higher level + /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no + /// `CEdges` to any other (and unless the graph was disconnected there will + /// be only one top level `CNode`). + pub fn generate_hierarchy(&mut self) {} + + pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { + self.cnodes.get(p_cnode) + } + + pub fn get_cedge(&self, p_cedge: PCEdge) -> Option<&CEdge> { + self.cedges.get(p_cedge).map(|(cedge, _)| cedge) + } + + /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid + /// subnodes that still contain `PBack` + pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: PBack) -> SmallVec<[PCNode; 4]> { + if let Some(p) = self + .backref_to_cnode + .find_with(|_, (p_back1, p_cnode1), ()| p_back1.cmp(&p_back)) + { + // + let mut res = smallvec![]; + res + } else { + unreachable!() + } + } +} diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index ec8ed451..6bc1b99e 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -1,14 +1,30 @@ use crate::{ - route::{ - channel::{PCEdge, PCNode}, - CEdge, CNode, HyperPath, PHyperPath, - }, - triple_arena::{Arena, OrdArena}, + route::{HyperPath, PHyperPath}, + triple_arena::Arena, + Epoch, }; #[derive(Debug, Clone)] pub struct Router { - cnodes: Arena, - cedges: OrdArena, hyperpaths: Arena, } + +impl Router { + pub fn new() -> Self { + Self { + hyperpaths: Arena::new(), + } + } + + // TODO current plan is to have a corresponding function on the target `Epoch` + // that calls this. May want some kind of `Epoch` restating system (or use + // shared `Epoch`s?). The routing info is generated, then one or more other + // `Epoch`s that have the programs can each have their programs routed. + pub fn from_epoch(epoch: &Epoch) -> Self { + let mut res = Self::new(); + res + } + + // TODO are the target and program both on channeling graphs, what assymetries + // are there? +} From d675e38f8db715b241bda0b3f1d8f59022787804 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 14 Dec 2023 15:15:17 -0600 Subject: [PATCH 008/119] more high level details --- starlight/src/ensemble/cgraph.rs | 0 starlight/src/route/channel.rs | 124 ++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) delete mode 100644 starlight/src/ensemble/cgraph.rs diff --git a/starlight/src/ensemble/cgraph.rs b/starlight/src/ensemble/cgraph.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index adf54943..a379d30b 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use awint::awint_dag::{ smallvec::smallvec, triple_arena::{Arena, ArenaTrait, OrdArena}, + EvalError, }; use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; @@ -95,6 +96,80 @@ pub struct Channeler { backref_to_cnode: OrdArena, } +// - A `CNode` cannot have exactly one subnode and must have either zero or at +// least two subnodes +// - the immediate subnodes of a `CNode` must be in a clique with `CEdge`s + +/* +consider a loop of `CNode`s like this +0 --- 1 +| | +| | +2 --- 3 + +If higher `CNode`s and edges formed like + + 01 + / \ +02 13 + \ / + 23 + +It could cause an infinite loop, we need to guarantee logarithmic overhead +with `CEdges` being made such that e.x. 02 should connect with 13 because +02 subnodes connect with 1 and 3 which are subnodes of 13. + + 01 + / | \ +02 -- 13 + \ | / + 23 + +the next level is + +0123 + +for larger loops it will be like + +0--1--2--3--4--5--6--7--0 (wraps around to 0) + ___ ___ ___ ___ + / \ / \ / \ / \ + 01-12-23-34-45-56-67-70-01-12 + \ / \ / \ / \ / + -- -- -- -- + +// we do not want this to continue, or else we end up with n^2 space + 0123 2345 4567 6701 + 1234 3456 5670 7012 + +we notice that 12 and 23 share 0.5 of their nodes in common, what we +do is merge a "extended clique" of cliques sharing the edge between +the two nodes, specifically the 01-12-23 clique and the 12-23-34 clique + + ... + 01234-45-56-67-70-01234 + +the 01-12-23 subedges are still in the hierarchy, if the 23-34 edge is selected +for the commonality merge, 01234 is found as a supernode of 34, and the proposed +merge resulting in 12345 shares 12 and 23 with 01234 (if more than or equal to +half of the subnodes are shared with respect to one or the other (2 out of +01,12,23,34 for one or 2 out of 12,23,34,45 for the other), it should not be +made). 34-45 would also be too close. +45-56 however is successful resulting in 34567 which has the desired overlap. +70 is left without a supernode on this level, but it joins a three clique to +result in the top node + + ... +01234-34567-70-01234 + +0123457 + +8 -> 8 -> 3 -> 1 seems right, the first reduction is stalling for wider useful +cliques for the descension algorithm, and this is quickly reduced down in +the logarithmic tree we want + +*/ + impl Channeler { pub fn new() -> Self { Self { @@ -144,15 +219,60 @@ impl Channeler { /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid /// subnodes that still contain `PBack` pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: PBack) -> SmallVec<[PCNode; 4]> { - if let Some(p) = self + if let Some(p_init) = self .backref_to_cnode .find_with(|_, (p_back1, p_cnode1), ()| p_back1.cmp(&p_back)) { - // let mut res = smallvec![]; + // res } else { unreachable!() } } + + pub fn verify_integrity(&self) -> Result<(), EvalError> { + // verify all pointer validities first + for p_cnode in self.cnodes.ptrs() { + let cnode = &self.cnodes[p_cnode]; + for subnode in &cnode.subnodes { + if !self.cnodes.contains(*subnode) { + return Err(EvalError::OtherString(format!( + "{cnode:?}.subnodes[{subnode}] is invalid" + ))) + } + } + for supernode in &cnode.supernodes { + if !self.cnodes.contains(*supernode) { + return Err(EvalError::OtherString(format!( + "{cnode:?}.subnodes[{supernode}] is invalid" + ))) + } + } + } + for p_cedge in self.cedges.ptrs() { + let cedge = &self.cedges.get_key(p_cedge).unwrap(); + if !self.cnodes.contains(cedge.source) { + return Err(EvalError::OtherString(format!( + "{cedge:?}.source {} is invalid", + cedge.source + ))) + } + if !self.cnodes.contains(cedge.sink) { + return Err(EvalError::OtherString(format!( + "{cedge:?}.sink {} is invalid", + cedge.sink + ))) + } + } + for p_back_to_cnode in self.backref_to_cnode.ptrs() { + let (_, p_cnode) = self.backref_to_cnode.get_key(p_back_to_cnode).unwrap(); + if !self.cnodes.contains(*p_cnode) { + return Err(EvalError::OtherString(format!( + "{p_back_to_cnode} key {p_cnode} is invalid" + ))) + } + } + Ok(()) + } } From 8d296e4f9364ef21c457be069acd074af23c924b Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 14 Dec 2023 15:50:27 -0600 Subject: [PATCH 009/119] Update channel.rs --- starlight/src/route/channel.rs | 101 ++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index a379d30b..16729bca 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -2,22 +2,21 @@ use std::cmp::Ordering; use awint::awint_dag::{ smallvec::smallvec, - triple_arena::{Arena, ArenaTrait, OrdArena}, + triple_arena::{Arena, OrdArena}, EvalError, }; use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; -ptr_struct!(PCNode; PCEdge; PBackToCNode); +ptr_struct!(PCNode; PCEdge; PTopLevelCNode; PBackToCNode); /// A channel node #[derive(Debug, Clone)] pub struct CNode { - /// hierarchical capability - /// and descends - subnodes: SmallVec<[PCNode; 2]>, - /// The hierarchy is like a dual overlapping binary tree one - supernodes: SmallVec<[PCNode; 2]>, + /// Must be sorted. + subnodes: Vec, + /// Must be sorted. + supernodes: Vec, } /// A description of bits to set in order to achieve some desired edge behavior. @@ -87,6 +86,7 @@ impl CEdge { pub struct Channeler { cnodes: Arena, cedges: OrdArena, + top_level_cnodes: OrdArena, /// On hard dependencies where a path needs to connect to a particular /// `PBack`, valid descencions in the `CNode` hierarchy are determined by /// `find_with` to first get to the desired `PBack` section, then linear @@ -157,7 +157,7 @@ half of the subnodes are shared with respect to one or the other (2 out of made). 34-45 would also be too close. 45-56 however is successful resulting in 34567 which has the desired overlap. 70 is left without a supernode on this level, but it joins a three clique to -result in the top node +result in the final top level node ... 01234-34567-70-01234 @@ -175,15 +175,26 @@ impl Channeler { Self { cnodes: Arena::new(), cedges: OrdArena::new(), + top_level_cnodes: OrdArena::new(), backref_to_cnode: OrdArena::new(), } } pub fn make_cnode(&mut self, p_equiv: PBack) -> PCNode { - self.cnodes.insert(CNode { - subnodes: smallvec![], - supernodes: smallvec![], - }) + if self + .backref_to_cnode + .find_with(|_, (p_back, _), _| p_back.cmp(&p_equiv)) + .is_some() + { + // there shouldn't be redundant `CNode`s + panic!() + } + let res = self.cnodes.insert(CNode { + subnodes: vec![], + supernodes: vec![], + }); + self.top_level_cnodes.insert(res, ()).1.unwrap(); + res } pub fn make_cedge(&mut self, source: PCNode, sink: PCNode, program: Program) -> PCEdge { @@ -206,7 +217,9 @@ impl Channeler { /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no /// `CEdges` to any other (and unless the graph was disconnected there will /// be only one top level `CNode`). - pub fn generate_hierarchy(&mut self) {} + pub fn generate_hierarchy(&mut self) { + // + } pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { self.cnodes.get(p_cnode) @@ -232,9 +245,27 @@ impl Channeler { } pub fn verify_integrity(&self) -> Result<(), EvalError> { - // verify all pointer validities first + fn is_sorted_and_unique(x: &[PCNode]) -> bool { + for i in 1..x.len() { + if x[i - 1] >= x[i] { + return false + } + } + true + } + // verify all pointer validities and sorting invariants first for p_cnode in self.cnodes.ptrs() { let cnode = &self.cnodes[p_cnode]; + if !is_sorted_and_unique(&cnode.subnodes) { + return Err(EvalError::OtherString(format!( + "{cnode:?}.subnodes is unsorted" + ))) + } + if !is_sorted_and_unique(&cnode.supernodes) { + return Err(EvalError::OtherString(format!( + "{cnode:?}.supernodes is unsorted" + ))) + } for subnode in &cnode.subnodes { if !self.cnodes.contains(*subnode) { return Err(EvalError::OtherString(format!( @@ -265,6 +296,14 @@ impl Channeler { ))) } } + for p_top_level_cnode in self.top_level_cnodes.ptrs() { + let p_cnode = self.top_level_cnodes.get_key(p_top_level_cnode).unwrap(); + if !self.cnodes.contains(*p_cnode) { + return Err(EvalError::OtherString(format!( + "{p_top_level_cnode} key {p_cnode} is invalid" + ))) + } + } for p_back_to_cnode in self.backref_to_cnode.ptrs() { let (_, p_cnode) = self.backref_to_cnode.get_key(p_back_to_cnode).unwrap(); if !self.cnodes.contains(*p_cnode) { @@ -273,6 +312,40 @@ impl Channeler { ))) } } + // check basic tree invariants + for p_top_level_cnode in self.top_level_cnodes.ptrs() { + let p_cnode = self.top_level_cnodes.get_key(p_top_level_cnode).unwrap(); + if !self.cnodes[p_cnode].supernodes.is_empty() { + return Err(EvalError::OtherString(format!( + "{p_top_level_cnode} key {p_cnode} is not a top level `CNode`" + ))) + } + } + for p_cnode in self.cnodes.ptrs() { + let cnode = &self.cnodes[p_cnode]; + for subnode in &cnode.subnodes { + if self.cnodes[subnode] + .supernodes + .binary_search(&p_cnode) + .is_err() + { + return Err(EvalError::OtherString(format!( + "{cnode:?} subnode {subnode} does not roundtrip" + ))) + } + } + for supernode in &cnode.supernodes { + if self.cnodes[supernode] + .subnodes + .binary_search(&p_cnode) + .is_err() + { + return Err(EvalError::OtherString(format!( + "{cnode:?} supernode {supernode} does not roundtrip" + ))) + } + } + } Ok(()) } } From 0325282b4a23f614901aee6f4ce0b87148f3b714 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 16 Dec 2023 13:57:27 -0600 Subject: [PATCH 010/119] Update channel.rs --- starlight/src/route/channel.rs | 60 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 16729bca..659cd56d 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -23,10 +23,15 @@ pub struct CNode { /// For now we unconditionally specify bits, in the future it should be more /// detailed to allow for more close by programs to coexist #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Program { +pub struct SetBits { pub bits: SmallVec<[(PBack, bool); 4]>, } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Program { + SetBits(SetBits), +} + /// An edge between channels #[derive(Debug, Clone)] pub struct CEdge { @@ -232,12 +237,61 @@ impl Channeler { /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid /// subnodes that still contain `PBack` pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: PBack) -> SmallVec<[PCNode; 4]> { + let cnode = self.cnodes.get(p_cnode).unwrap(); + // TODO we need a `find_similar_with` function for `OrdArena` so we can avoid + // the loop to get to the most previous `p_cnode` in the region if let Some(p_init) = self .backref_to_cnode - .find_with(|_, (p_back1, p_cnode1), ()| p_back1.cmp(&p_back)) + .find_with(|_, (p_back1, _), ()| p_back1.cmp(&p_back)) { + let mut p = p_init; + loop { + let link = self.backref_to_cnode.get_link(p).unwrap(); + if let Some(p_prev) = link.prev() { + if self.backref_to_cnode.get_key(p_prev).unwrap().0 == p_back { + p = p_prev; + } else { + // we have reached the first + break + } + } else { + break + } + } let mut res = smallvec![]; - // + let mut i = 0; + loop { + if i >= cnode.subnodes.len() { + break + } + let (p_back1, p_cnode1) = self.backref_to_cnode.get_key(p).unwrap(); + if *p_back1 != p_back { + break + } + match cnode.subnodes[i].cmp(&p_cnode1) { + Ordering::Less => { + i += 1; + } + Ordering::Equal => { + res.push(*p_cnode1); + i += 1; + let link = self.backref_to_cnode.get_link(p).unwrap(); + if let Some(next) = link.next() { + p = next; + } else { + break + } + } + Ordering::Greater => { + let link = self.backref_to_cnode.get_link(p).unwrap(); + if let Some(next) = link.next() { + p = next; + } else { + break + } + } + } + } res } else { unreachable!() From 3fe4e28a7140adb8025568591ec6b8e95416d608 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 16 Dec 2023 20:41:13 -0600 Subject: [PATCH 011/119] Update channel.rs --- starlight/src/route/channel.rs | 52 ++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 659cd56d..575fe92a 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -19,17 +19,42 @@ pub struct CNode { supernodes: Vec, } +/// Used by higher order edges to tell what it is capable of overall +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct BulkBehavior { + /// The number of bits that can enter this channel + channel_entry_width: usize, + /// The number of bits that can exit this channel + channel_exit_width: usize, + /// For now, we just add up the number of LUT bits in the channel + lut_bits: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Behavior { + /// Routes the bit from `source` to `sink` + RouteBit, + /// Can behave as an arbitrary lookup table outputting a bit and taking the + /// input bits. + ArbitraryLut(PCNode, SmallVec<[PCNode; 4]>), + /// Bulk behavior + Bulk(BulkBehavior), +} + /// A description of bits to set in order to achieve some desired edge behavior. /// For now we unconditionally specify bits, in the future it should be more /// detailed to allow for more close by programs to coexist #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct SetBits { - pub bits: SmallVec<[(PBack, bool); 4]>, +pub struct Instruction { + pub set_bits: SmallVec<[(PBack, bool); 4]>, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Program { - SetBits(SetBits), +pub struct Programmability { + /// The behavior that can be programmed into this edge + behavior: Behavior, + /// The instruction required to get the desired behavior + instruction: Instruction, } /// An edge between channels @@ -46,7 +71,7 @@ pub struct CEdge { /// Describes the required program to route a value (could be the `p_equiv` /// in a unit `CNode` or bulk routing through higher level `CNode`s) from /// the source to the sink. - program: Program, + programmability: Programmability, // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for // wide edges delay_weight: u64, //lagrangian_weight: u64, @@ -81,8 +106,8 @@ impl Ord for CEdge { } impl CEdge { - pub fn program(&self) -> &Program { - &self.program + pub fn programmability(&self) -> &Programmability { + &self.programmability } } @@ -202,12 +227,17 @@ impl Channeler { res } - pub fn make_cedge(&mut self, source: PCNode, sink: PCNode, program: Program) -> PCEdge { + pub fn make_cedge( + &mut self, + source: PCNode, + sink: PCNode, + programmability: Programmability, + ) -> PCEdge { let (p_new, duplicate) = self.cedges.insert( CEdge { source, sink, - program, + programmability, }, (), ); @@ -222,9 +252,7 @@ impl Channeler { /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no /// `CEdges` to any other (and unless the graph was disconnected there will /// be only one top level `CNode`). - pub fn generate_hierarchy(&mut self) { - // - } + pub fn generate_hierarchy(&mut self) {} pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { self.cnodes.get(p_cnode) From 7a11ee75be394b0d4a3e9a890b90a6cb718b9715 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 16 Dec 2023 21:01:57 -0600 Subject: [PATCH 012/119] Update channel.rs --- starlight/src/route/channel.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 575fe92a..c73603d3 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -8,7 +8,7 @@ use awint::awint_dag::{ use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; -ptr_struct!(PCNode; PCEdge; PTopLevelCNode; PBackToCNode); +ptr_struct!(PCNode; PCEdge; PBackToCNode); /// A channel node #[derive(Debug, Clone)] @@ -39,6 +39,9 @@ pub enum Behavior { ArbitraryLut(PCNode, SmallVec<[PCNode; 4]>), /// Bulk behavior Bulk(BulkBehavior), + /// Nothing can happen between nodes, used for connecting top level nodes + /// that have no connection to each other + Noop, } /// A description of bits to set in order to achieve some desired edge behavior. @@ -116,7 +119,10 @@ impl CEdge { pub struct Channeler { cnodes: Arena, cedges: OrdArena, - top_level_cnodes: OrdArena, + /// The plan is that this always ends up with a single top level node, with + /// all unconnected graphs being connected with `Behavior::Noop` so that the + /// normal algorithm can allocate over them + top_level_cnodes: SmallVec<[PCNode; 1]>, /// On hard dependencies where a path needs to connect to a particular /// `PBack`, valid descencions in the `CNode` hierarchy are determined by /// `find_with` to first get to the desired `PBack` section, then linear @@ -205,7 +211,7 @@ impl Channeler { Self { cnodes: Arena::new(), cedges: OrdArena::new(), - top_level_cnodes: OrdArena::new(), + top_level_cnodes: smallvec![], backref_to_cnode: OrdArena::new(), } } @@ -223,7 +229,7 @@ impl Channeler { subnodes: vec![], supernodes: vec![], }); - self.top_level_cnodes.insert(res, ()).1.unwrap(); + self.top_level_cnodes.push(res); res } @@ -252,7 +258,9 @@ impl Channeler { /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no /// `CEdges` to any other (and unless the graph was disconnected there will /// be only one top level `CNode`). - pub fn generate_hierarchy(&mut self) {} + pub fn generate_hierarchy(&mut self) { + //let mut p = + } pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { self.cnodes.get(p_cnode) @@ -378,11 +386,10 @@ impl Channeler { ))) } } - for p_top_level_cnode in self.top_level_cnodes.ptrs() { - let p_cnode = self.top_level_cnodes.get_key(p_top_level_cnode).unwrap(); + for p_cnode in &self.top_level_cnodes { if !self.cnodes.contains(*p_cnode) { return Err(EvalError::OtherString(format!( - "{p_top_level_cnode} key {p_cnode} is invalid" + "top_level_cnodes {p_cnode} is invalid" ))) } } @@ -395,11 +402,10 @@ impl Channeler { } } // check basic tree invariants - for p_top_level_cnode in self.top_level_cnodes.ptrs() { - let p_cnode = self.top_level_cnodes.get_key(p_top_level_cnode).unwrap(); + for p_cnode in &self.top_level_cnodes { if !self.cnodes[p_cnode].supernodes.is_empty() { return Err(EvalError::OtherString(format!( - "{p_top_level_cnode} key {p_cnode} is not a top level `CNode`" + "top_level_cnodes {p_cnode} is not a top level `CNode`" ))) } } From a9dc1a1025164e5c4082ed94d07ff6996f45add7 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sun, 17 Dec 2023 22:33:49 -0600 Subject: [PATCH 013/119] add region advancer --- starlight/src/route.rs | 4 +- starlight/src/route/channel.rs | 81 ++++++++++++------------------- starlight/src/route/region_adv.rs | 72 +++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 50 deletions(-) create mode 100644 starlight/src/route/region_adv.rs diff --git a/starlight/src/route.rs b/starlight/src/route.rs index 2590280d..171c5d0a 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -1,7 +1,9 @@ mod channel; mod path; +mod region_adv; mod router; -pub use channel::{CEdge, CNode, Channeler, Program}; +pub use channel::{CEdge, CNode, Channeler, Programmability}; pub use path::{HyperPath, PHyperPath, Path}; +pub use region_adv::RegionAdvancer; pub use router::Router; diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index c73603d3..b4b1fe76 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -2,10 +2,11 @@ use std::cmp::Ordering; use awint::awint_dag::{ smallvec::smallvec, - triple_arena::{Arena, OrdArena}, + triple_arena::{Advancer, Arena, OrdArena}, EvalError, }; +use super::RegionAdvancer; use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; ptr_struct!(PCNode; PCEdge; PBackToCNode); @@ -63,11 +64,11 @@ pub struct Programmability { /// An edge between channels #[derive(Debug, Clone)] pub struct CEdge { + /// The sink `CNode` + sink: PCNode, /// The source `CNode`, this is readonly but bidirectional `Net`s can be /// represented with two `CEdge`s going both ways source: PCNode, - /// The sink `CNode` - sink: PCNode, // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only // respect the above and any insertion needs to check for duplicates @@ -258,8 +259,19 @@ impl Channeler { /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no /// `CEdges` to any other (and unless the graph was disconnected there will /// be only one top level `CNode`). + /// + /// We are currently assuming that `generate_hierarchy` is being run once on + /// a graph of unit channel nodes and edges pub fn generate_hierarchy(&mut self) { - //let mut p = + // when running out of commonality merges to make, we progress by merging based + // on the nodes with the largest fan-in + ptr_struct!(P0); + let mut fan_in_priority = OrdArena::::new(); + /*for (p_cnode, cnode) in self.cnodes { + let p_cedge = self.cedges.find_with(|_, cedge, ()| { + cedge.sink.cmp(&p_cnode) + }); + }*/ } pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { @@ -274,57 +286,28 @@ impl Channeler { /// subnodes that still contain `PBack` pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: PBack) -> SmallVec<[PCNode; 4]> { let cnode = self.cnodes.get(p_cnode).unwrap(); - // TODO we need a `find_similar_with` function for `OrdArena` so we can avoid - // the loop to get to the most previous `p_cnode` in the region - if let Some(p_init) = self - .backref_to_cnode - .find_with(|_, (p_back1, _), ()| p_back1.cmp(&p_back)) - { - let mut p = p_init; - loop { - let link = self.backref_to_cnode.get_link(p).unwrap(); - if let Some(p_prev) = link.prev() { - if self.backref_to_cnode.get_key(p_prev).unwrap().0 == p_back { - p = p_prev; - } else { - // we have reached the first - break - } - } else { - break - } - } + if let Some(mut adv) = RegionAdvancer::new(&self.backref_to_cnode, |_, (p_back1, _), ()| { + p_back1.cmp(&p_back) + }) { + // uses the fact that `subnodes` is ordered to linearly iterate over a region let mut res = smallvec![]; let mut i = 0; - loop { - if i >= cnode.subnodes.len() { - break - } - let (p_back1, p_cnode1) = self.backref_to_cnode.get_key(p).unwrap(); - if *p_back1 != p_back { - break - } - match cnode.subnodes[i].cmp(&p_cnode1) { - Ordering::Less => { - i += 1; + 'outer: while let Some(p) = adv.advance(&self.backref_to_cnode) { + let (_, p_cnode1) = self.backref_to_cnode.get_key(p).unwrap(); + loop { + if i >= cnode.subnodes.len() { + break 'outer; } - Ordering::Equal => { - res.push(*p_cnode1); - i += 1; - let link = self.backref_to_cnode.get_link(p).unwrap(); - if let Some(next) = link.next() { - p = next; - } else { - break + match cnode.subnodes[i].cmp(&p_cnode1) { + Ordering::Less => { + i += 1; } - } - Ordering::Greater => { - let link = self.backref_to_cnode.get_link(p).unwrap(); - if let Some(next) = link.next() { - p = next; - } else { + Ordering::Equal => { + res.push(*p_cnode1); + i += 1; break } + Ordering::Greater => break, } } } diff --git a/starlight/src/route/region_adv.rs b/starlight/src/route/region_adv.rs new file mode 100644 index 00000000..58ba55aa --- /dev/null +++ b/starlight/src/route/region_adv.rs @@ -0,0 +1,72 @@ +use std::{cmp::Ordering, marker::PhantomData}; + +use awint::awint_dag::triple_arena::{Advancer, OrdArena, Ptr}; + +// TODO may want to add to `triple_arena`, also add a `find_similar_with` to fix +// the issue with needing to rewind + +pub struct RegionAdvancer Ordering, P: Ptr, K, V> { + p: Option, + cmp: F, + boo: PhantomData (K, V)>, +} + +impl Ordering, P: Ptr, K, V> Advancer for RegionAdvancer { + type Collection = OrdArena; + type Item = P; + + fn advance(&mut self, collection: &Self::Collection) -> Option { + // go in the `next` direction + if let Some(current) = self.p { + let (gen, link) = collection.get_link_no_gen(current).unwrap(); + self.p = link.next(); + let p_current = P::_from_raw(current, gen); + if (self.cmp)(p_current, &link.t.0, &link.t.1) == Ordering::Equal { + Some(p_current) + } else { + // have reached the end of the region, also set to `None` to shortcut in + // case this advancer is used after `None` was first reached + self.p = None; + None + } + } else { + None + } + } +} + +impl Ordering, P: Ptr, K, V> RegionAdvancer { + /// Sometimes when advancing over an `OrdArena`, there is a contiguous + /// subset of keys that are equal with respect to some common prefix, and we + /// want to advance over all of them. This will return an advancer if it + /// finds at least one `Ordering::Equal` case from the `cmp` function, + /// otherwise it returns `None`. The advancer will then return all `Ptr`s to + /// keys from the region that compare as `Ordering::Equal` with the same + /// `cmp` function. Additionally, it returns `Ptr`s in order. + pub fn new(collection: &OrdArena, mut cmp: F) -> Option { + if let Some(p) = collection.find_with(|p, k, v| cmp(p, k, v)) { + let mut p = p.inx(); + loop { + let (gen, link) = collection.get_link_no_gen(p).unwrap(); + if cmp(Ptr::_from_raw(p, gen), &link.t.0, &link.t.1) == Ordering::Equal { + if let Some(p_prev) = link.prev() { + p = p_prev; + } else { + // region is the first + break + } + } else { + // we would exit the region + break + } + } + Some(RegionAdvancer { + p: Some(p), + cmp, + boo: PhantomData, + }) + } else { + None + } + } +} From 29bf2cf152fe1efddf6e2111b5c54be6ac33c13a Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sun, 17 Dec 2023 23:13:37 -0600 Subject: [PATCH 014/119] Update channel.rs --- starlight/src/route/channel.rs | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index b4b1fe76..3b5a50fd 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -265,13 +265,38 @@ impl Channeler { pub fn generate_hierarchy(&mut self) { // when running out of commonality merges to make, we progress by merging based // on the nodes with the largest fan-in - ptr_struct!(P0); - let mut fan_in_priority = OrdArena::::new(); - /*for (p_cnode, cnode) in self.cnodes { - let p_cedge = self.cedges.find_with(|_, cedge, ()| { - cedge.sink.cmp(&p_cnode) - }); - }*/ + ptr_struct!(P0; P1); + let mut fan_in_priority = OrdArena::::new(); + for p_cnode in self.cnodes.ptrs() { + let mut fan_in_count = 0usize; + if let Some(mut adv) = + RegionAdvancer::new(&self.cedges, |_, cedge, ()| cedge.sink.cmp(&p_cnode)) + { + while let Some(_) = adv.advance(&self.cedges) { + fan_in_count = fan_in_count.checked_add(1).unwrap(); + } + } else { + unreachable!() + } + if fan_in_count != 0 { + fan_in_priority + .insert((fan_in_count, p_cnode), ()) + .1 + .unwrap(); + } + } + let mut merge_priority = OrdArena::::new(); + loop { + if fan_in_priority.is_empty() && merge_priority.is_empty() { + break + } + while let Some(p1_max) = merge_priority.max() { + let merge = merge_priority.remove(p1_max).unwrap().0; + } + if let Some(p0_max) = fan_in_priority.max() { + let p_cnode = fan_in_priority.remove(p0_max).unwrap().0 .1; + } + } } pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { From 5789244e5b28ffe7ff15572a57914eca3f1455a5 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 18 Dec 2023 13:31:45 -0600 Subject: [PATCH 015/119] need backref system --- starlight/src/route/channel.rs | 124 ++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 3b5a50fd..c1b3f979 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -12,7 +12,7 @@ use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_st ptr_struct!(PCNode; PCEdge; PBackToCNode); /// A channel node -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct CNode { /// Must be sorted. subnodes: Vec, @@ -65,10 +65,10 @@ pub struct Programmability { #[derive(Debug, Clone)] pub struct CEdge { /// The sink `CNode` - sink: PCNode, + p_sink: PCNode, /// The source `CNode`, this is readonly but bidirectional `Net`s can be /// represented with two `CEdge`s going both ways - source: PCNode, + p_source: PCNode, // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only // respect the above and any insertion needs to check for duplicates @@ -83,7 +83,7 @@ pub struct CEdge { impl PartialEq for CEdge { fn eq(&self, other: &Self) -> bool { - self.source == other.source && self.sink == other.sink + self.p_source == other.p_source && self.p_sink == other.p_sink } } @@ -91,21 +91,21 @@ impl Eq for CEdge {} impl PartialOrd for CEdge { fn partial_cmp(&self, other: &Self) -> Option { - match self.source.partial_cmp(&other.source) { + match self.p_source.partial_cmp(&other.p_source) { Some(Ordering::Equal) => {} ord => return ord, } - self.sink.partial_cmp(&other.sink) + self.p_sink.partial_cmp(&other.p_sink) } } impl Ord for CEdge { fn cmp(&self, other: &Self) -> Ordering { - match self.source.cmp(&other.source) { + match self.p_source.cmp(&other.p_source) { Ordering::Equal => {} ord => return ord, } - self.sink.cmp(&other.sink) + self.p_sink.cmp(&other.p_sink) } } @@ -217,33 +217,38 @@ impl Channeler { } } - pub fn make_cnode(&mut self, p_equiv: PBack) -> PCNode { - if self - .backref_to_cnode - .find_with(|_, (p_back, _), _| p_back.cmp(&p_equiv)) - .is_some() - { - // there shouldn't be redundant `CNode`s - panic!() - } + /// Given the `subnodes` for a new top level `CNode`, this will manage the + /// sorting and the `supernodes` backrefs + pub fn make_top_level_cnode(&mut self, mut subnodes: Vec) -> PCNode { + subnodes.sort_unstable(); + let len = subnodes.len(); let res = self.cnodes.insert(CNode { - subnodes: vec![], + subnodes, supernodes: vec![], }); + for i in 0..len { + let subnode = self.cnodes.get(res).unwrap().subnodes[i]; + let sub_backrefs = &mut self.cnodes.get_mut(subnode).unwrap().supernodes; + // insert at the right point to keep sorted + let j = sub_backrefs.partition_point(|&p| p < res); + sub_backrefs.insert(j, res); + } self.top_level_cnodes.push(res); res } - pub fn make_cedge( + // LUTs will work by having a `CNode` with unit subnodes for each input bit, and + // an edge going to a unit output `CNode` + /*pub fn make_cedge( &mut self, - source: PCNode, - sink: PCNode, + p_source: PCNode, + p_sink: PCNode, programmability: Programmability, ) -> PCEdge { let (p_new, duplicate) = self.cedges.insert( CEdge { - source, - sink, + p_source, + p_sink, programmability, }, (), @@ -252,7 +257,7 @@ impl Channeler { // appropriately, but disallow for now duplicate.unwrap(); p_new - } + }*/ /// Starting from unit `CNode`s and `CEdge`s describing all known low level /// progam methods, this generates a logarithmic tree of higher level @@ -266,35 +271,72 @@ impl Channeler { // when running out of commonality merges to make, we progress by merging based // on the nodes with the largest fan-in ptr_struct!(P0; P1); + let mut fan_in_priority = OrdArena::::new(); - for p_cnode in self.cnodes.ptrs() { + let mut merge_priority = OrdArena::::new(); + // handles the common task of updating priorities after adding a new `CNode` to + // consideration + fn add_p_cnode( + channeler: &mut Channeler, + fan_in_priority: &mut OrdArena, + merge_priority: &mut OrdArena, + new_p_cnode: PCNode, + ) { + // add to fan in priority let mut fan_in_count = 0usize; - if let Some(mut adv) = - RegionAdvancer::new(&self.cedges, |_, cedge, ()| cedge.sink.cmp(&p_cnode)) - { - while let Some(_) = adv.advance(&self.cedges) { + if let Some(mut adv) = RegionAdvancer::new(&channeler.cedges, |_, cedge, ()| { + cedge.p_sink.cmp(&new_p_cnode) + }) { + while let Some(_) = adv.advance(&channeler.cedges) { fan_in_count = fan_in_count.checked_add(1).unwrap(); } - } else { - unreachable!() - } - if fan_in_count != 0 { fan_in_priority - .insert((fan_in_count, p_cnode), ()) + .insert((fan_in_count, new_p_cnode), ()) .1 .unwrap(); } } - let mut merge_priority = OrdArena::::new(); + let mut adv = self.cnodes.advancer(); + while let Some(p_cnode) = adv.advance(&self.cnodes) { + add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, p_cnode); + } loop { if fan_in_priority.is_empty() && merge_priority.is_empty() { break } while let Some(p1_max) = merge_priority.max() { let merge = merge_priority.remove(p1_max).unwrap().0; + // 1. } if let Some(p0_max) = fan_in_priority.max() { let p_cnode = fan_in_priority.remove(p0_max).unwrap().0 .1; + // check that it is top level and wasn't subsumed by a merge step + if self.cnodes.get(p_cnode).unwrap().supernodes.is_empty() { + // the subnodes will consist of the common sink node and its top level sources + let mut subnodes = vec![p_cnode]; + let mut adv = RegionAdvancer::new(&self.cedges, |_, cedge, ()| { + cedge.p_sink.cmp(&p_cnode) + }) + .unwrap(); + while let Some(p_edge) = adv.advance(&self.cedges) { + let edge = self.cedges.get(p_edge).unwrap().0; + let p_source = edge.p_source; + let source = self.cnodes.get(p_source).unwrap(); + if source.supernodes.is_empty() { + subnodes.push(p_source); + } + } + let new_p_cnode = self.make_top_level_cnode(subnodes); + add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, new_p_cnode); + } + } + } + + // just overwrite + self.top_level_cnodes.clear(); + for (p_cnode, cnode) in &self.cnodes { + if cnode.supernodes.is_empty() { + self.top_level_cnodes.push(p_cnode); } } } @@ -381,16 +423,16 @@ impl Channeler { } for p_cedge in self.cedges.ptrs() { let cedge = &self.cedges.get_key(p_cedge).unwrap(); - if !self.cnodes.contains(cedge.source) { + if !self.cnodes.contains(cedge.p_source) { return Err(EvalError::OtherString(format!( - "{cedge:?}.source {} is invalid", - cedge.source + "{cedge:?}.p_source {} is invalid", + cedge.p_source ))) } - if !self.cnodes.contains(cedge.sink) { + if !self.cnodes.contains(cedge.p_sink) { return Err(EvalError::OtherString(format!( - "{cedge:?}.sink {} is invalid", - cedge.sink + "{cedge:?}.p_sink {} is invalid", + cedge.p_sink ))) } } From 456b0861e83b6343848cb6a12be88be0a1f31ce1 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 18 Dec 2023 20:49:06 -0600 Subject: [PATCH 016/119] total refactor of `Channel` to use backref system --- starlight/src/route.rs | 6 +- starlight/src/route/cedge.rs | 67 +++++ starlight/src/route/channel.rs | 461 +++++++++------------------------ starlight/src/route/cnode.rs | 118 +++++++++ starlight/src/route/path.rs | 6 +- 5 files changed, 313 insertions(+), 345 deletions(-) create mode 100644 starlight/src/route/cedge.rs create mode 100644 starlight/src/route/cnode.rs diff --git a/starlight/src/route.rs b/starlight/src/route.rs index 171c5d0a..fd4f1d3a 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -1,9 +1,13 @@ +mod cedge; mod channel; +mod cnode; mod path; mod region_adv; mod router; -pub use channel::{CEdge, CNode, Channeler, Programmability}; +pub use cedge::{CEdge, PCEdge}; +pub use channel::{Channeler, PBack}; +pub use cnode::CNode; pub use path::{HyperPath, PHyperPath, Path}; pub use region_adv::RegionAdvancer; pub use router::Router; diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs new file mode 100644 index 00000000..06073b2e --- /dev/null +++ b/starlight/src/route/cedge.rs @@ -0,0 +1,67 @@ +use crate::{awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct}; + +ptr_struct!(PCEdge); + +/// Used by higher order edges to tell what it is capable of overall +#[derive(Debug, Clone)] +pub struct BulkBehavior { + /// The number of bits that can enter this channel + channel_entry_width: usize, + /// The number of bits that can exit this channel + channel_exit_width: usize, + /// For now, we just add up the number of LUT bits in the channel + lut_bits: usize, +} + +#[derive(Debug, Clone)] +pub enum Behavior { + /// Routes the bit from `source` to `sink` + RouteBit, + /// Can behave as an arbitrary lookup table outputting a bit and taking the + /// input bits. + ArbitraryLut(PBack, SmallVec<[PBack; 4]>), + /// Bulk behavior + Bulk(BulkBehavior), + /// Nothing can happen between nodes, used for connecting top level nodes + /// that have no connection to each other + Noop, +} + +/// A description of bits to set in order to achieve some desired edge behavior. +/// For now we unconditionally specify bits, in the future it should be more +/// detailed to allow for more close by programs to coexist +#[derive(Debug, Clone)] +pub struct Instruction { + pub set_bits: SmallVec<[(ensemble::PBack, bool); 4]>, +} + +#[derive(Debug, Clone)] +pub struct Programmability { + /// The behavior that can be programmed into this edge + behavior: Behavior, + /// The instruction required to get the desired behavior + instruction: Instruction, +} + +/// An edge between channels +#[derive(Debug, Clone)] +pub struct CEdge { + /// The sources and sinks + pub incidences: SmallVec<[PBack; 4]>, + + // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only + // respect the above and any insertion needs to check for duplicates + /// Describes the required program to route a value (could be the `p_equiv` + /// in a unit `CNode` or bulk routing through higher level `CNode`s) from + /// the source to the sink. + programmability: Programmability, + // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for + // wide edges delay_weight: u64, + //lagrangian_weight: u64, +} + +impl CEdge { + pub fn programmability(&self) -> &Programmability { + &self.programmability + } +} diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index c1b3f979..7cb66a92 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,137 +1,15 @@ -use std::cmp::Ordering; - use awint::awint_dag::{ smallvec::smallvec, - triple_arena::{Advancer, Arena, OrdArena}, + triple_arena::{Arena, SurjectArena}, EvalError, }; -use super::RegionAdvancer; -use crate::{awint_dag::smallvec::SmallVec, ensemble::PBack, triple_arena::ptr_struct}; - -ptr_struct!(PCNode; PCEdge; PBackToCNode); - -/// A channel node -#[derive(Debug, Clone, Default)] -pub struct CNode { - /// Must be sorted. - subnodes: Vec, - /// Must be sorted. - supernodes: Vec, -} - -/// Used by higher order edges to tell what it is capable of overall -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BulkBehavior { - /// The number of bits that can enter this channel - channel_entry_width: usize, - /// The number of bits that can exit this channel - channel_exit_width: usize, - /// For now, we just add up the number of LUT bits in the channel - lut_bits: usize, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Behavior { - /// Routes the bit from `source` to `sink` - RouteBit, - /// Can behave as an arbitrary lookup table outputting a bit and taking the - /// input bits. - ArbitraryLut(PCNode, SmallVec<[PCNode; 4]>), - /// Bulk behavior - Bulk(BulkBehavior), - /// Nothing can happen between nodes, used for connecting top level nodes - /// that have no connection to each other - Noop, -} - -/// A description of bits to set in order to achieve some desired edge behavior. -/// For now we unconditionally specify bits, in the future it should be more -/// detailed to allow for more close by programs to coexist -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Instruction { - pub set_bits: SmallVec<[(PBack, bool); 4]>, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Programmability { - /// The behavior that can be programmed into this edge - behavior: Behavior, - /// The instruction required to get the desired behavior - instruction: Instruction, -} - -/// An edge between channels -#[derive(Debug, Clone)] -pub struct CEdge { - /// The sink `CNode` - p_sink: PCNode, - /// The source `CNode`, this is readonly but bidirectional `Net`s can be - /// represented with two `CEdge`s going both ways - p_source: PCNode, - - // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only - // respect the above and any insertion needs to check for duplicates - /// Describes the required program to route a value (could be the `p_equiv` - /// in a unit `CNode` or bulk routing through higher level `CNode`s) from - /// the source to the sink. - programmability: Programmability, - // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for - // wide edges delay_weight: u64, - //lagrangian_weight: u64, -} - -impl PartialEq for CEdge { - fn eq(&self, other: &Self) -> bool { - self.p_source == other.p_source && self.p_sink == other.p_sink - } -} - -impl Eq for CEdge {} - -impl PartialOrd for CEdge { - fn partial_cmp(&self, other: &Self) -> Option { - match self.p_source.partial_cmp(&other.p_source) { - Some(Ordering::Equal) => {} - ord => return ord, - } - self.p_sink.partial_cmp(&other.p_sink) - } -} - -impl Ord for CEdge { - fn cmp(&self, other: &Self) -> Ordering { - match self.p_source.cmp(&other.p_source) { - Ordering::Equal => {} - ord => return ord, - } - self.p_sink.cmp(&other.p_sink) - } -} - -impl CEdge { - pub fn programmability(&self) -> &Programmability { - &self.programmability - } -} - -/// Management struct for channel nodes and edges -#[derive(Debug, Clone)] -pub struct Channeler { - cnodes: Arena, - cedges: OrdArena, - /// The plan is that this always ends up with a single top level node, with - /// all unconnected graphs being connected with `Behavior::Noop` so that the - /// normal algorithm can allocate over them - top_level_cnodes: SmallVec<[PCNode; 1]>, - /// On hard dependencies where a path needs to connect to a particular - /// `PBack`, valid descencions in the `CNode` hierarchy are determined by - /// `find_with` to first get to the desired `PBack` section, then linear - /// iterating to figure out which `CNode`s contain the `PBack`. The space is - /// limited to a `n*log(n)`, there is probably some inevitable `n*log(n)` - /// cost somewhere. - backref_to_cnode: OrdArena, -} +use crate::{ + awint_dag::smallvec::SmallVec, + ensemble, + route::{CEdge, CNode, PCEdge}, + triple_arena::ptr_struct, +}; // - A `CNode` cannot have exactly one subnode and must have either zero or at // least two subnodes @@ -207,151 +85,40 @@ the logarithmic tree we want */ +ptr_struct!(PBack); + +#[derive(Debug, Clone, Copy)] +pub enum Referent { + ThisCNode, + SubNode(PBack), + SuperNode(PBack), + CEdgeIncidence(PCEdge, usize), + EnsembleBackRef(ensemble::PBack), +} + +#[derive(Debug, Clone)] +pub struct Channeler { + pub cnodes: SurjectArena, + pub cedges: Arena, + /// The plan is that this always ends up with a single top level node, with + /// all unconnected graphs being connected with `Behavior::Noop` so that the + /// normal algorithm can allocate over them + pub top_level_cnodes: SmallVec<[PBack; 1]>, +} + impl Channeler { pub fn new() -> Self { Self { - cnodes: Arena::new(), - cedges: OrdArena::new(), + cnodes: SurjectArena::new(), + cedges: Arena::new(), top_level_cnodes: smallvec![], - backref_to_cnode: OrdArena::new(), } } - /// Given the `subnodes` for a new top level `CNode`, this will manage the - /// sorting and the `supernodes` backrefs - pub fn make_top_level_cnode(&mut self, mut subnodes: Vec) -> PCNode { - subnodes.sort_unstable(); - let len = subnodes.len(); - let res = self.cnodes.insert(CNode { - subnodes, - supernodes: vec![], - }); - for i in 0..len { - let subnode = self.cnodes.get(res).unwrap().subnodes[i]; - let sub_backrefs = &mut self.cnodes.get_mut(subnode).unwrap().supernodes; - // insert at the right point to keep sorted - let j = sub_backrefs.partition_point(|&p| p < res); - sub_backrefs.insert(j, res); - } - self.top_level_cnodes.push(res); - res - } - - // LUTs will work by having a `CNode` with unit subnodes for each input bit, and - // an edge going to a unit output `CNode` - /*pub fn make_cedge( - &mut self, - p_source: PCNode, - p_sink: PCNode, - programmability: Programmability, - ) -> PCEdge { - let (p_new, duplicate) = self.cedges.insert( - CEdge { - p_source, - p_sink, - programmability, - }, - (), - ); - // there may be future circumstances where we allow this and combine - // appropriately, but disallow for now - duplicate.unwrap(); - p_new - }*/ - - /// Starting from unit `CNode`s and `CEdge`s describing all known low level - /// progam methods, this generates a logarithmic tree of higher level - /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no - /// `CEdges` to any other (and unless the graph was disconnected there will - /// be only one top level `CNode`). - /// - /// We are currently assuming that `generate_hierarchy` is being run once on - /// a graph of unit channel nodes and edges - pub fn generate_hierarchy(&mut self) { - // when running out of commonality merges to make, we progress by merging based - // on the nodes with the largest fan-in - ptr_struct!(P0; P1); - - let mut fan_in_priority = OrdArena::::new(); - let mut merge_priority = OrdArena::::new(); - // handles the common task of updating priorities after adding a new `CNode` to - // consideration - fn add_p_cnode( - channeler: &mut Channeler, - fan_in_priority: &mut OrdArena, - merge_priority: &mut OrdArena, - new_p_cnode: PCNode, - ) { - // add to fan in priority - let mut fan_in_count = 0usize; - if let Some(mut adv) = RegionAdvancer::new(&channeler.cedges, |_, cedge, ()| { - cedge.p_sink.cmp(&new_p_cnode) - }) { - while let Some(_) = adv.advance(&channeler.cedges) { - fan_in_count = fan_in_count.checked_add(1).unwrap(); - } - fan_in_priority - .insert((fan_in_count, new_p_cnode), ()) - .1 - .unwrap(); - } - } - let mut adv = self.cnodes.advancer(); - while let Some(p_cnode) = adv.advance(&self.cnodes) { - add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, p_cnode); - } - loop { - if fan_in_priority.is_empty() && merge_priority.is_empty() { - break - } - while let Some(p1_max) = merge_priority.max() { - let merge = merge_priority.remove(p1_max).unwrap().0; - // 1. - } - if let Some(p0_max) = fan_in_priority.max() { - let p_cnode = fan_in_priority.remove(p0_max).unwrap().0 .1; - // check that it is top level and wasn't subsumed by a merge step - if self.cnodes.get(p_cnode).unwrap().supernodes.is_empty() { - // the subnodes will consist of the common sink node and its top level sources - let mut subnodes = vec![p_cnode]; - let mut adv = RegionAdvancer::new(&self.cedges, |_, cedge, ()| { - cedge.p_sink.cmp(&p_cnode) - }) - .unwrap(); - while let Some(p_edge) = adv.advance(&self.cedges) { - let edge = self.cedges.get(p_edge).unwrap().0; - let p_source = edge.p_source; - let source = self.cnodes.get(p_source).unwrap(); - if source.supernodes.is_empty() { - subnodes.push(p_source); - } - } - let new_p_cnode = self.make_top_level_cnode(subnodes); - add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, new_p_cnode); - } - } - } - - // just overwrite - self.top_level_cnodes.clear(); - for (p_cnode, cnode) in &self.cnodes { - if cnode.supernodes.is_empty() { - self.top_level_cnodes.push(p_cnode); - } - } - } - - pub fn get_cnode(&self, p_cnode: PCNode) -> Option<&CNode> { - self.cnodes.get(p_cnode) - } - - pub fn get_cedge(&self, p_cedge: PCEdge) -> Option<&CEdge> { - self.cedges.get(p_cedge).map(|(cedge, _)| cedge) - } - + /* /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid - /// subnodes that still contain `PBack` - pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: PBack) -> SmallVec<[PCNode; 4]> { + /// subnodes that still contain `ensemble::PBack` + pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: ensemble::PBack) -> SmallVec<[PCNode; 4]> { let cnode = self.cnodes.get(p_cnode).unwrap(); if let Some(mut adv) = RegionAdvancer::new(&self.backref_to_cnode, |_, (p_back1, _), ()| { p_back1.cmp(&p_back) @@ -382,58 +149,69 @@ impl Channeler { } else { unreachable!() } - } + }*/ pub fn verify_integrity(&self) -> Result<(), EvalError> { - fn is_sorted_and_unique(x: &[PCNode]) -> bool { - for i in 1..x.len() { - if x[i - 1] >= x[i] { - return false + // return errors in order of most likely to be root cause + + // first check that surjects self refs aren't broken by themselves + for p_back in self.cnodes.ptrs() { + let cnode = self.cnodes.get_val(p_back).unwrap(); + if let Some(Referent::ThisCNode) = self.cnodes.get_key(cnode.p_this_cnode) { + if !self.cnodes.in_same_set(p_back, cnode.p_this_cnode).unwrap() { + return Err(EvalError::OtherString(format!( + "{cnode:?}.p_this_cnode roundtrip fail" + ))) } - } - true - } - // verify all pointer validities and sorting invariants first - for p_cnode in self.cnodes.ptrs() { - let cnode = &self.cnodes[p_cnode]; - if !is_sorted_and_unique(&cnode.subnodes) { + } else { return Err(EvalError::OtherString(format!( - "{cnode:?}.subnodes is unsorted" + "{cnode:?}.p_this_cnode is invalid" ))) } - if !is_sorted_and_unique(&cnode.supernodes) { - return Err(EvalError::OtherString(format!( - "{cnode:?}.supernodes is unsorted" - ))) - } - for subnode in &cnode.subnodes { - if !self.cnodes.contains(*subnode) { + // need to roundtrip in both directions to ensure existence and uniqueness of a + // `ThisCNode` for each surject + if let Some(Referent::ThisCNode) = self.cnodes.get_key(p_back) { + if p_back != cnode.p_this_cnode { return Err(EvalError::OtherString(format!( - "{cnode:?}.subnodes[{subnode}] is invalid" + "{cnode:?}.p_this_cnode roundtrip fail" ))) } } - for supernode in &cnode.supernodes { - if !self.cnodes.contains(*supernode) { - return Err(EvalError::OtherString(format!( - "{cnode:?}.subnodes[{supernode}] is invalid" - ))) + } + // check other referent validities + for referent in self.cnodes.keys() { + let invalid = match referent { + // already checked + Referent::ThisCNode => false, + Referent::SubNode(p_subnode) => !self.cnodes.contains(*p_subnode), + Referent::SuperNode(p_supernode) => !self.cnodes.contains(*p_supernode), + Referent::CEdgeIncidence(p_cedge, i) => { + if let Some(cedges) = self.cedges.get(*p_cedge) { + if *i > cedges.incidences.len() { + return Err(EvalError::OtherString(format!( + "{referent:?} roundtrip out of bounds" + ))) + } + false + } else { + true + } } + Referent::EnsembleBackRef(_) => false, + }; + if invalid { + return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) } } + // other kinds of validity for p_cedge in self.cedges.ptrs() { - let cedge = &self.cedges.get_key(p_cedge).unwrap(); - if !self.cnodes.contains(cedge.p_source) { - return Err(EvalError::OtherString(format!( - "{cedge:?}.p_source {} is invalid", - cedge.p_source - ))) - } - if !self.cnodes.contains(cedge.p_sink) { - return Err(EvalError::OtherString(format!( - "{cedge:?}.p_sink {} is invalid", - cedge.p_sink - ))) + let cedge = &self.cedges.get(p_cedge).unwrap(); + for p_cnode in &cedge.incidences { + if !self.cnodes.contains(*p_cnode) { + return Err(EvalError::OtherString(format!( + "{cedge:?}.p_cnodes {p_cnode} is invalid", + ))) + } } } for p_cnode in &self.top_level_cnodes { @@ -443,47 +221,48 @@ impl Channeler { ))) } } - for p_back_to_cnode in self.backref_to_cnode.ptrs() { - let (_, p_cnode) = self.backref_to_cnode.get_key(p_back_to_cnode).unwrap(); - if !self.cnodes.contains(*p_cnode) { - return Err(EvalError::OtherString(format!( - "{p_back_to_cnode} key {p_cnode} is invalid" - ))) - } - } - // check basic tree invariants - for p_cnode in &self.top_level_cnodes { - if !self.cnodes[p_cnode].supernodes.is_empty() { - return Err(EvalError::OtherString(format!( - "top_level_cnodes {p_cnode} is not a top level `CNode`" - ))) - } - } - for p_cnode in self.cnodes.ptrs() { - let cnode = &self.cnodes[p_cnode]; - for subnode in &cnode.subnodes { - if self.cnodes[subnode] - .supernodes - .binary_search(&p_cnode) - .is_err() - { - return Err(EvalError::OtherString(format!( - "{cnode:?} subnode {subnode} does not roundtrip" - ))) + // Other roundtrips from `backrefs` direction to ensure bijection + for p_back in self.cnodes.ptrs() { + let referent = self.cnodes.get_key(p_back).unwrap(); + let fail = match referent { + // already checked + Referent::ThisCNode => false, + Referent::SubNode(p_subnode) => { + let subnode = self.cnodes.get_key(*p_subnode).unwrap(); + if let Referent::SuperNode(p_supernode) = subnode { + *p_supernode != p_back + } else { + true + } } - } - for supernode in &cnode.supernodes { - if self.cnodes[supernode] - .subnodes - .binary_search(&p_cnode) - .is_err() - { - return Err(EvalError::OtherString(format!( - "{cnode:?} supernode {supernode} does not roundtrip" - ))) + Referent::SuperNode(p_supernode) => { + let supernode = self.cnodes.get_key(*p_supernode).unwrap(); + if let Referent::SubNode(p_subnode) = supernode { + *p_subnode != p_back + } else { + true + } } + Referent::CEdgeIncidence(p_cedge, i) => { + let cedge = self.cedges.get(*p_cedge).unwrap(); + let p_cnode = cedge.incidences[*i]; + if let Referent::CEdgeIncidence(p_cedge1, i1) = + self.cnodes.get_key(p_cnode).unwrap() + { + (*p_cedge != *p_cedge1) || (*i != *i1) + } else { + true + } + } + Referent::EnsembleBackRef(_) => todo!(), + }; + if fail { + return Err(EvalError::OtherString(format!( + "{referent:?} roundtrip fail" + ))) } } + // tree invariants Ok(()) } } diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs new file mode 100644 index 00000000..3af0df33 --- /dev/null +++ b/starlight/src/route/cnode.rs @@ -0,0 +1,118 @@ +use awint::awint_dag::triple_arena::Ptr; + +use crate::route::{channel::Referent, Channeler, PBack}; + +/// A channel node +#[derive(Debug, Clone, Default)] +pub struct CNode { + pub p_this_cnode: PBack, +} + +impl Channeler { + /// Given the `subnodes` (which should point to unique `ThisCNode`s) for a + /// new top level `CNode`, this will manage the backrefs + pub fn make_top_level_cnode(&mut self, subnodes: Vec) -> PBack { + let p_cnode = self + .cnodes + .insert_with(|p_this_cnode| (Referent::ThisCNode, CNode { p_this_cnode })); + for subnode in subnodes { + let p_subnode = self + .cnodes + .insert_key(subnode, Referent::SuperNode(Ptr::invalid())) + .unwrap(); + let p_supernode = self + .cnodes + .insert_key(p_cnode, Referent::SubNode(p_subnode)) + .unwrap(); + // we want the referents to point exactly at each other's keys and not the + // `p_this_cnode` + *self.cnodes.get_key_mut(p_subnode).unwrap() = Referent::SuperNode(p_supernode); + } + self.top_level_cnodes.push(p_cnode); + p_cnode + } + + /* + /// Starting from unit `CNode`s and `CEdge`s describing all known low level + /// progam methods, this generates a logarithmic tree of higher level + /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no + /// `CEdges` to any other (and unless the graph was disconnected there will + /// be only one top level `CNode`). + /// + /// We are currently assuming that `generate_hierarchy` is being run once on + /// a graph of unit channel nodes and edges + pub fn generate_hierarchy(&mut self) { + // when running out of commonality merges to make, we progress by merging based + // on the nodes with the largest fan-in + ptr_struct!(P0; P1); + + let mut fan_in_priority = OrdArena::::new(); + let mut merge_priority = OrdArena::::new(); + // handles the common task of updating priorities after adding a new `CNode` to + // consideration + fn add_p_cnode( + channeler: &mut Channeler, + fan_in_priority: &mut OrdArena, + merge_priority: &mut OrdArena, + new_p_cnode: PCNode, + ) { + // add to fan in priority + let mut fan_in_count = 0usize; + if let Some(mut adv) = RegionAdvancer::new(&channeler.cedges, |_, cedge, ()| { + cedge.p_sink.cmp(&new_p_cnode) + }) { + while let Some(_) = adv.advance(&channeler.cedges) { + fan_in_count = fan_in_count.checked_add(1).unwrap(); + } + fan_in_priority + .insert((fan_in_count, new_p_cnode), ()) + .1 + .unwrap(); + } + } + let mut adv = self.cnodes.advancer(); + while let Some(p_cnode) = adv.advance(&self.cnodes) { + add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, p_cnode); + } + loop { + if fan_in_priority.is_empty() && merge_priority.is_empty() { + break + } + while let Some(p1_max) = merge_priority.max() { + let merge = merge_priority.remove(p1_max).unwrap().0; + // 1. + } + if let Some(p0_max) = fan_in_priority.max() { + let p_cnode = fan_in_priority.remove(p0_max).unwrap().0 .1; + // check that it is top level and wasn't subsumed by a merge step + if self.cnodes.get(p_cnode).unwrap().supernodes.is_empty() { + // the subnodes will consist of the common sink node and its top level sources + let mut subnodes = vec![p_cnode]; + let mut adv = RegionAdvancer::new(&self.cedges, |_, cedge, ()| { + cedge.p_sink.cmp(&p_cnode) + }) + .unwrap(); + while let Some(p_edge) = adv.advance(&self.cedges) { + let edge = self.cedges.get(p_edge).unwrap().0; + let p_source = edge.p_source; + let source = self.cnodes.get(p_source).unwrap(); + if source.supernodes.is_empty() { + subnodes.push(p_source); + } + } + let new_p_cnode = self.make_top_level_cnode(subnodes); + add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, new_p_cnode); + } + } + } + + // just overwrite + self.top_level_cnodes.clear(); + for (p_cnode, cnode) in &self.cnodes { + if cnode.supernodes.is_empty() { + self.top_level_cnodes.push(p_cnode); + } + } + } + */ +} diff --git a/starlight/src/route/path.rs b/starlight/src/route/path.rs index 34e74c76..c38438c1 100644 --- a/starlight/src/route/path.rs +++ b/starlight/src/route/path.rs @@ -1,5 +1,5 @@ use crate::{ - route::channel::{PCEdge, PCNode}, + route::{PBack, PCEdge}, triple_arena::ptr_struct, }; @@ -8,7 +8,7 @@ ptr_struct!(PHyperPath); /// A single path from a source to sink across multiple `CEdge`s #[derive(Debug, Clone)] pub struct Path { - sink: PCNode, + sink: PBack, edges: Vec, //critical_multiplier: u64, } @@ -17,6 +17,6 @@ pub struct Path { /// to one ore more `sink` nodes. Sinks can have different priorities. #[derive(Debug, Clone)] pub struct HyperPath { - source: PCNode, + source: PBack, paths: Vec, } From 7a508846d611c7e406537e0f8fd9f2b7f47ff305 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 21 Dec 2023 17:18:48 -0600 Subject: [PATCH 017/119] change `Epoch::ensemble` into function that takes a closure --- starlight/src/awi_structs/epoch.rs | 16 ++- starlight/src/ensemble/debug.rs | 23 ++-- starlight/src/lib.rs | 8 +- starlight/src/route/cedge.rs | 15 ++- starlight/src/route/channel.rs | 74 ------------- starlight/src/route/cnode.rs | 172 +++++++++++++++-------------- testcrate/tests/basic.rs | 26 ++--- testcrate/tests/fuzz_elementary.rs | 2 +- testcrate/tests/stats.rs | 18 +-- 9 files changed, 156 insertions(+), 198 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 83f563a9..f9a7dbe3 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -230,8 +230,8 @@ impl EpochShared { } /// Returns a clone of the ensemble - pub fn ensemble(&self) -> Ensemble { - self.epoch_data.borrow().ensemble.clone() + pub fn ensemble O>(&self, mut f: F) -> O { + f(&self.epoch_data.borrow().ensemble) } pub fn assertions_empty(&self) -> bool { @@ -534,6 +534,14 @@ impl Epoch { &this.shared } + pub fn ensemble O>(&self, f: F) -> O { + self.shared.ensemble(f) + } + + pub fn verify_integrity(&self) -> Result<(), EvalError> { + self.ensemble(|ensemble| ensemble.verify_integrity()) + } + /// Gets the assertions associated with this Epoch (not including assertions /// from when sub-epochs are alive or from before the this Epoch was /// created) @@ -553,10 +561,6 @@ impl Epoch { self.shared.assert_assertions(true) } - pub fn ensemble(&self) -> Ensemble { - self.shared.ensemble() - } - /// Used for testing pub fn prune_ignore_assertions(&self) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 481c1ea9..a12cf89f 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -266,7 +266,7 @@ impl Ensemble { arena } - pub fn render_to_svgs_in_dir(&mut self, out_file: PathBuf) -> Result<(), EvalError> { + pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), EvalError> { let dir = match out_file.canonicalize() { Ok(o) => { if !o.is_dir() { @@ -291,16 +291,21 @@ impl Ensemble { impl Epoch { pub fn eprint_debug_summary(&self) { - let ensemble = self.ensemble(); - let chain_arena = ensemble.backrefs_to_chain_arena(); - let debug = ensemble.to_debug(); - eprintln!( - "ensemble: {:#?}\nchain_arena: {:#?}\ndebug: {:#?}", - ensemble, chain_arena, debug - ); + self.ensemble(|ensemble| { + let chain_arena = ensemble.backrefs_to_chain_arena(); + let debug = ensemble.to_debug(); + eprintln!( + "ensemble: {:#?}\nchain_arena: {:#?}\ndebug: {:#?}", + ensemble, chain_arena, debug + ); + }); } pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), EvalError> { - self.ensemble().render_to_svgs_in_dir(out_file) + let tmp = &out_file; + self.ensemble(|ensemble| { + let out_file = tmp.to_owned(); + ensemble.render_to_svgs_in_dir(out_file) + }) } } diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 1456968f..fa2e8515 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -96,9 +96,11 @@ //! //! // Now the combinational logic is described in a DAG of lookup tables that we //! // could use for various purposes -//! for state in epoch0.ensemble().stator.states.vals() { -//! awi::assert!(state.lowered_to_lnodes); -//! } +//! epoch0.ensemble(|ensemble| { +//! for state in ensemble.stator.states.vals() { +//! awi::assert!(state.lowered_to_lnodes); +//! } +//! }); //! //! // "retroactively" assign the input with a non-opaque value //! input.retro_(&awi!(0101)).unwrap(); diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 06073b2e..ac19fbcc 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,4 +1,7 @@ -use crate::{awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct}; +use super::Channeler; +use crate::{ + awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct, Epoch, +}; ptr_struct!(PCEdge); @@ -65,3 +68,13 @@ impl CEdge { &self.programmability } } + +impl Channeler { + pub fn from_epoch(epoch: &Epoch) -> Self { + let mut res = Self::new(); + + epoch.ensemble(|ensemble| {}); + + res + } +} diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 7cb66a92..cff1acea 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -11,80 +11,6 @@ use crate::{ triple_arena::ptr_struct, }; -// - A `CNode` cannot have exactly one subnode and must have either zero or at -// least two subnodes -// - the immediate subnodes of a `CNode` must be in a clique with `CEdge`s - -/* -consider a loop of `CNode`s like this -0 --- 1 -| | -| | -2 --- 3 - -If higher `CNode`s and edges formed like - - 01 - / \ -02 13 - \ / - 23 - -It could cause an infinite loop, we need to guarantee logarithmic overhead -with `CEdges` being made such that e.x. 02 should connect with 13 because -02 subnodes connect with 1 and 3 which are subnodes of 13. - - 01 - / | \ -02 -- 13 - \ | / - 23 - -the next level is - -0123 - -for larger loops it will be like - -0--1--2--3--4--5--6--7--0 (wraps around to 0) - ___ ___ ___ ___ - / \ / \ / \ / \ - 01-12-23-34-45-56-67-70-01-12 - \ / \ / \ / \ / - -- -- -- -- - -// we do not want this to continue, or else we end up with n^2 space - 0123 2345 4567 6701 - 1234 3456 5670 7012 - -we notice that 12 and 23 share 0.5 of their nodes in common, what we -do is merge a "extended clique" of cliques sharing the edge between -the two nodes, specifically the 01-12-23 clique and the 12-23-34 clique - - ... - 01234-45-56-67-70-01234 - -the 01-12-23 subedges are still in the hierarchy, if the 23-34 edge is selected -for the commonality merge, 01234 is found as a supernode of 34, and the proposed -merge resulting in 12345 shares 12 and 23 with 01234 (if more than or equal to -half of the subnodes are shared with respect to one or the other (2 out of -01,12,23,34 for one or 2 out of 12,23,34,45 for the other), it should not be -made). 34-45 would also be too close. -45-56 however is successful resulting in 34567 which has the desired overlap. -70 is left without a supernode on this level, but it joins a three clique to -result in the final top level node - - ... -01234-34567-70-01234 - -0123457 - -8 -> 8 -> 3 -> 1 seems right, the first reduction is stalling for wider useful -cliques for the descension algorithm, and this is quickly reduced down in -the logarithmic tree we want - -*/ - ptr_struct!(PBack); #[derive(Debug, Clone, Copy)] diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index 3af0df33..aa7f0f63 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -1,4 +1,4 @@ -use awint::awint_dag::triple_arena::Ptr; +use awint::awint_dag::triple_arena::{ptr_struct, Ptr}; use crate::route::{channel::Referent, Channeler, PBack}; @@ -31,88 +31,94 @@ impl Channeler { self.top_level_cnodes.push(p_cnode); p_cnode } +} - /* - /// Starting from unit `CNode`s and `CEdge`s describing all known low level - /// progam methods, this generates a logarithmic tree of higher level - /// `CNode`s and `CEdge`s that results in top level `CNode`s that have no - /// `CEdges` to any other (and unless the graph was disconnected there will - /// be only one top level `CNode`). - /// - /// We are currently assuming that `generate_hierarchy` is being run once on - /// a graph of unit channel nodes and edges - pub fn generate_hierarchy(&mut self) { - // when running out of commonality merges to make, we progress by merging based - // on the nodes with the largest fan-in - ptr_struct!(P0; P1); - - let mut fan_in_priority = OrdArena::::new(); - let mut merge_priority = OrdArena::::new(); - // handles the common task of updating priorities after adding a new `CNode` to - // consideration - fn add_p_cnode( - channeler: &mut Channeler, - fan_in_priority: &mut OrdArena, - merge_priority: &mut OrdArena, - new_p_cnode: PCNode, - ) { - // add to fan in priority - let mut fan_in_count = 0usize; - if let Some(mut adv) = RegionAdvancer::new(&channeler.cedges, |_, cedge, ()| { - cedge.p_sink.cmp(&new_p_cnode) - }) { - while let Some(_) = adv.advance(&channeler.cedges) { - fan_in_count = fan_in_count.checked_add(1).unwrap(); - } - fan_in_priority - .insert((fan_in_count, new_p_cnode), ()) - .1 - .unwrap(); - } - } - let mut adv = self.cnodes.advancer(); - while let Some(p_cnode) = adv.advance(&self.cnodes) { - add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, p_cnode); - } - loop { - if fan_in_priority.is_empty() && merge_priority.is_empty() { - break - } - while let Some(p1_max) = merge_priority.max() { - let merge = merge_priority.remove(p1_max).unwrap().0; - // 1. - } - if let Some(p0_max) = fan_in_priority.max() { - let p_cnode = fan_in_priority.remove(p0_max).unwrap().0 .1; - // check that it is top level and wasn't subsumed by a merge step - if self.cnodes.get(p_cnode).unwrap().supernodes.is_empty() { - // the subnodes will consist of the common sink node and its top level sources - let mut subnodes = vec![p_cnode]; - let mut adv = RegionAdvancer::new(&self.cedges, |_, cedge, ()| { - cedge.p_sink.cmp(&p_cnode) - }) - .unwrap(); - while let Some(p_edge) = adv.advance(&self.cedges) { - let edge = self.cedges.get(p_edge).unwrap().0; - let p_source = edge.p_source; - let source = self.cnodes.get(p_source).unwrap(); - if source.supernodes.is_empty() { - subnodes.push(p_source); - } - } - let new_p_cnode = self.make_top_level_cnode(subnodes); - add_p_cnode(self, &mut fan_in_priority, &mut merge_priority, new_p_cnode); - } - } - } +// - A `CNode` cannot have exactly one subnode and must have either zero or at +// least two subnodes +// - the immediate subnodes of a `CNode` must be in a clique with `CEdge`s - // just overwrite - self.top_level_cnodes.clear(); - for (p_cnode, cnode) in &self.cnodes { - if cnode.supernodes.is_empty() { - self.top_level_cnodes.push(p_cnode); - } - } - } - */ +/* +consider a loop of `CNode`s like this +0 --- 1 +| | +| | +2 --- 3 + +If higher `CNode`s and edges formed like + + 01 + / \ +02 13 + \ / + 23 + +It could cause an infinite loop, we need to guarantee logarithmic overhead +with `CEdges` being made such that e.x. 02 should connect with 13 because +02 subnodes connect with 1 and 3 which are subnodes of 13. + + 01 + / | \ +02 -- 13 + \ | / + 23 + +the next level is + +0123 + +for larger loops it will be like + +0--1--2--3--4--5--6--7--0 (wraps around to 0) + ___ ___ ___ ___ + / \ / \ / \ / \ + 01-12-23-34-45-56-67-70-01-12 + \ / \ / \ / \ / + -- -- -- -- + +// we do not want this to continue, or else we end up with n^2 space + 0123 2345 4567 6701 + 1234 3456 5670 7012 + +we notice that 12 and 23 share 0.5 of their nodes in common, what we +do is merge a "extended clique" of cliques sharing the edge between +the two nodes, specifically the 01-12-23 clique and the 12-23-34 clique + + ... + 01234-45-56-67-70-01234 + +the 01-12-23 subedges are still in the hierarchy, if the 23-34 edge is selected +for the commonality merge, 01234 is found as a supernode of 34, and the proposed +merge resulting in 12345 shares 12 and 23 with 01234 (if more than or equal to +half of the subnodes are shared with respect to one or the other (2 out of +01,12,23,34 for one or 2 out of 12,23,34,45 for the other), it should not be +made). 34-45 would also be too close. +45-56 however is successful resulting in 34567 which has the desired overlap. +70 is left without a supernode on this level, but it joins a three clique to +result in the final top level node + + ... +01234-34567-70-01234 + +0123457 + +8 -> 8 -> 3 -> 1 seems right, the first reduction is stalling for wider useful +cliques for the descension algorithm, and this is quickly reduced down in +the logarithmic tree we want + +*/ + +/// Starting from unit `CNode`s and `CEdge`s describing all known low level +/// progam methods, this generates a logarithmic tree of higher level +/// `CNode`s and `CEdge`s that results in top level `CNode`s that have no +/// `CEdges` to any other (and unless the graph was disconnected there will +/// be only one top level `CNode`). +/// +/// We are currently assuming that `generate_hierarchy` is being run once on +/// a graph of unit channel nodes and edges +pub fn generate_hierarchy(channeler: &mut Channeler) { + // TODO currently we are doing a simpler strategy of merging pairs on distinct + // layers, need to methodically determine what we really want + ptr_struct!(P0); + + loop {} } diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 8c97320f..40274dfd 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -19,14 +19,14 @@ fn lazy_awi() -> Option<()> { // TODO the solution is to use the `bits` macro in these places x.retro_(&awi!(0)).unwrap(); - epoch0.ensemble().verify_integrity().unwrap(); + epoch0.verify_integrity().unwrap(); awi::assert_eq!(y.eval().unwrap(), awi!(1)); - epoch0.ensemble().verify_integrity().unwrap(); + epoch0.verify_integrity().unwrap(); x.retro_(&awi!(1)).unwrap(); awi::assert_eq!(y.eval().unwrap(), awi!(0)); - epoch0.ensemble().verify_integrity().unwrap(); + epoch0.verify_integrity().unwrap(); } // cleans up everything not still used by `LazyAwi`s, `LazyAwi`s deregister @@ -52,7 +52,7 @@ fn invert_twice() { x.retro_(&awi!(0)).unwrap(); assert_eq!(y.eval().unwrap(), awi!(0)); - epoch0.ensemble().verify_integrity().unwrap(); + epoch0.verify_integrity().unwrap(); x.retro_(&awi!(1)).unwrap(); assert_eq!(y.eval().unwrap(), awi!(1)); } @@ -145,16 +145,16 @@ fn luts() { } assert_eq!(opt_res, res); - let ensemble = epoch0.ensemble(); - - // assert that there is at most one LNode with constant inputs optimized away - let mut lnodes = ensemble.lnodes.vals(); - if let Some(lnode) = lnodes.next() { - inp_bits += lnode.inp.len(); - assert!(lnode.inp.len() <= opaque_set.count_ones()); + epoch0.ensemble(|ensemble| { + // assert that there is at most one LNode with constant inputs optimized away + let mut lnodes = ensemble.lnodes.vals(); + if let Some(lnode) = lnodes.next() { + inp_bits += lnode.inp.len(); + assert!(lnode.inp.len() <= opaque_set.count_ones()); + assert!(lnodes.next().is_none()); + } assert!(lnodes.next().is_none()); - } - assert!(lnodes.next().is_none()); + }); } } } diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 9c8fab22..225671dd 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -169,7 +169,7 @@ fn fuzz_elementary() { operation(&mut rng, &mut m) } m.finish(&epoch); - epoch.ensemble().verify_integrity().unwrap(); + epoch.verify_integrity().unwrap(); let res = m.verify_equivalence(&epoch); res.unwrap(); epoch.optimize().unwrap(); diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index f8abba32..29317895 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -14,14 +14,16 @@ fn stats_optimize_funnel() { epoch0.prune().unwrap(); epoch0.lower().unwrap(); epoch0.assert_assertions().unwrap(); - let ensemble = epoch0.ensemble(); - awi::assert_eq!(ensemble.stator.states.len(), 2436); - awi::assert_eq!(ensemble.backrefs.len_keys(), 8559); - awi::assert_eq!(ensemble.backrefs.len_vals(), 1317); + epoch0.ensemble(|ensemble| { + awi::assert_eq!(ensemble.stator.states.len(), 2436); + awi::assert_eq!(ensemble.backrefs.len_keys(), 8559); + awi::assert_eq!(ensemble.backrefs.len_vals(), 1317); + }); epoch0.optimize().unwrap(); epoch0.assert_assertions().unwrap(); - let ensemble = epoch0.ensemble(); - awi::assert_eq!(ensemble.stator.states.len(), 0); - awi::assert_eq!(ensemble.backrefs.len_keys(), 5818); - awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); + epoch0.ensemble(|ensemble| { + awi::assert_eq!(ensemble.stator.states.len(), 0); + awi::assert_eq!(ensemble.backrefs.len_keys(), 5818); + awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); + }); } From 5ced87ff7e3e68d6c592687d005b4b9b3d41115a Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 22 Dec 2023 22:23:56 -0600 Subject: [PATCH 018/119] clean up `EpochShared` --- starlight/src/awi_structs/epoch.rs | 176 +++++++++++++++-------------- starlight/src/route/cedge.rs | 20 +++- 2 files changed, 107 insertions(+), 89 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index f9a7dbe3..d663f2fa 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -16,6 +16,7 @@ use awint::{ use crate::{ensemble::Ensemble, EvalAwi}; +/// A list of single bit `EvalAwi`s for assertions #[derive(Debug, Clone)] pub struct Assertions { pub bits: Vec, @@ -35,6 +36,7 @@ impl Default for Assertions { ptr_struct!(PEpochShared); +/// Data stored in `EpochData` per each live `EpochShared` #[derive(Debug)] pub struct PerEpochShared { pub states_inserted: Vec, @@ -50,6 +52,9 @@ impl PerEpochShared { } } +/// The unit of data that gets a registered `awint_dag` `EpochKey`, and which +/// several `EpochShared`s can share +/// /// # Custom Drop /// /// This deregisters the `awint_dag::epoch::EpochKey` upon being dropped @@ -82,8 +87,11 @@ impl Drop for EpochData { } } -// `awint_dag::epoch` has a stack system which this uses, but this can have its -// own stack on top of that. +/// The raw internal management struct for `Epoch`s. Most users should be using +/// `Epoch`. +/// +/// `awint_dag::epoch` has a stack system which this uses, but this can have its +/// own stack on top of that. #[derive(Clone)] pub struct EpochShared { pub epoch_data: Rc>, @@ -91,7 +99,7 @@ pub struct EpochShared { } impl EpochShared { - /// Creates a new `Ensemble` and registers a new `EpochCallback`. + /// Creates a new `EpochData` and registers a new `EpochCallback`. pub fn new() -> Self { let mut epoch_data = EpochData { epoch_key: _callback().push_on_epoch_stack(), @@ -105,7 +113,8 @@ impl EpochShared { } } - /// Does _not_ register a new `EpochCallback`, instead + /// Does _not_ register a new `EpochCallback`, instead adds a new + /// `PerEpochShared` to the current `EpochData` of `other` pub fn shared_with(other: &Self) -> Self { let p_self = other .epoch_data @@ -118,6 +127,75 @@ impl EpochShared { } } + /// Sets `self` as the current `EpochShared` with respect to the starlight + /// stack (does not affect whatever the `awint_dag` stack is doing) + pub fn set_as_current(&self) { + CURRENT_EPOCH.with(|top| { + let mut current = top.borrow_mut(); + if let Some(current) = current.take() { + EPOCH_STACK.with(|top| { + let mut stack = top.borrow_mut(); + stack.push(current); + }) + } + *current = Some(self.clone()); + }); + } + + /// Removes `self` as the current `EpochShared` with respect to the + /// starlight stack + pub fn remove_as_current(&self) { + EPOCH_STACK.with(|top| { + let mut stack = top.borrow_mut(); + let next_current = stack.pop(); + CURRENT_EPOCH.with(|top| { + let mut current = top.borrow_mut(); + if let Some(to_drop) = current.take() { + if !Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) { + panic!( + "tried to drop an `Epoch` out of stacklike order before dropping the \ + current one" + ); + } + *current = next_current; + } else { + // there should be something current if the `Epoch` still exists + unreachable!() + } + }); + }); + } + + /// Access to the `Ensemble` + pub fn ensemble O>(&self, mut f: F) -> O { + f(&self.epoch_data.borrow().ensemble) + } + + /// Takes the `Vec` corresponding to just states added when the + /// current `EpochShared` was active. This also means that + /// `remove_associated` done immediately after this will only remove + /// assertions, responsibility should be taken over for the `PState`s + /// returned by this function + pub fn take_states_added(&mut self) -> Vec { + let mut epoch_data = self.epoch_data.borrow_mut(); + let ours = epoch_data.responsible_for.get_mut(self.p_self).unwrap(); + mem::take(&mut ours.states_inserted) + } + + /// Removes states and assertions from the `Ensemble` that were associated + /// with this particular `EpochShared` (other `EpochShared`s can still have + /// states and assertions in the `Ensemble`) + pub fn remove_associated(&self) { + let mut epoch_data = self.epoch_data.borrow_mut(); + let ours = epoch_data.responsible_for.remove(self.p_self).unwrap(); + for p_state in &ours.states_inserted { + let _ = epoch_data.ensemble.remove_state(*p_state); + } + drop(epoch_data); + // drop the `EvalAwi`s of the assertions after unlocking + drop(ours); + } + /// Returns a clone of the assertions currently associated with `self` pub fn assertions(&self) -> Assertions { let p_self = self.p_self; @@ -141,7 +219,8 @@ impl EpochShared { Assertions { bits: cloned } } - /// Using `EpochShared::assertions` creates all new `Assertions`. This + /// This evaluates all assertions (returning an error if any are false, and + /// returning an error on unevaluatable assertions if `strict`), and /// eliminates assertions that evaluate to a constant true. pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { let p_self = self.p_self; @@ -229,86 +308,6 @@ impl EpochShared { Ok(()) } - /// Returns a clone of the ensemble - pub fn ensemble O>(&self, mut f: F) -> O { - f(&self.epoch_data.borrow().ensemble) - } - - pub fn assertions_empty(&self) -> bool { - let epoch_data = self.epoch_data.borrow(); - let ours = epoch_data.responsible_for.get(self.p_self).unwrap(); - ours.assertions.bits.is_empty() - } - - pub fn take_states_added(&mut self) -> Vec { - let mut epoch_data = self.epoch_data.borrow_mut(); - let ours = epoch_data.responsible_for.get_mut(self.p_self).unwrap(); - mem::take(&mut ours.states_inserted) - } - - /// Removes associated states and assertions - pub fn remove_associated(&self) { - let mut epoch_data = self.epoch_data.borrow_mut(); - let ours = epoch_data.responsible_for.remove(self.p_self).unwrap(); - for p_state in &ours.states_inserted { - let _ = epoch_data.ensemble.remove_state(*p_state); - } - drop(epoch_data); - // drop the `EvalAwi`s of the assertions after unlocking - drop(ours); - } - - pub fn set_as_current(&self) { - CURRENT_EPOCH.with(|top| { - let mut current = top.borrow_mut(); - if let Some(current) = current.take() { - EPOCH_STACK.with(|top| { - let mut stack = top.borrow_mut(); - stack.push(current); - }) - } - *current = Some(self.clone()); - }); - } - - pub fn remove_as_current(&self) { - EPOCH_STACK.with(|top| { - let mut stack = top.borrow_mut(); - if let Some(next_current) = stack.pop() { - CURRENT_EPOCH.with(|top| { - let mut current = top.borrow_mut(); - if let Some(to_drop) = current.take() { - if !Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) { - panic!( - "tried to drop an `Epoch` out of stacklike order before dropping \ - the current one" - ); - } - *current = Some(next_current); - } else { - // there should be something current if the `Epoch` still exists - unreachable!() - } - }); - } else { - CURRENT_EPOCH.with(|top| { - let mut current = top.borrow_mut(); - if let Some(to_drop) = current.take() { - if !Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) { - panic!( - "tried to drop an `Epoch` out of stacklike order before dropping \ - the current one" - ); - } - } else { - // there should be something current if the `Epoch` still exists - unreachable!() - } - }); - } - }); - } - fn internal_drive_loops_with_lower_capability(&self) -> Result<(), EvalError> { // `Loop`s register states to lower so that the below loops can find them Ensemble::handle_requests_with_lower_capability(self)?; @@ -528,6 +527,13 @@ impl Epoch { Self { shared } } + /* + /// Returns `None` if there is a shared `Epoch` still alive + pub fn end(self) -> Option { + self.shared.remove_associated(); + self.shared.remove_as_current(); + }*/ + /// Intended primarily for developer use #[doc(hidden)] pub fn internal_epoch_shared(this: &Epoch) -> &EpochShared { diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index ac19fbcc..1c47bf11 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,4 +1,4 @@ -use super::Channeler; +use super::{channel::Referent, Channeler}; use crate::{ awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct, Epoch, }; @@ -70,11 +70,23 @@ impl CEdge { } impl Channeler { + /// Assumes that `epoch` has been optimized pub fn from_epoch(epoch: &Epoch) -> Self { - let mut res = Self::new(); + let mut channeler = Self::new(); - epoch.ensemble(|ensemble| {}); + epoch.ensemble(|ensemble| { + // for each equivalence make a `CNode` with associated `EnsembleBackref` + for equiv in ensemble.backrefs.vals() { + let p_cnode = channeler.make_top_level_cnode(vec![]); + channeler + .cnodes + .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) + .unwrap(); + } - res + // add `CEdge`s according to `LNode`s + }); + + channeler } } From 03d7f49ba783e4cbc8eb07b1f742f514b6b2d79c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 22 Dec 2023 23:03:44 -0600 Subject: [PATCH 019/119] clean up `Epoch` --- CHANGELOG.md | 4 ++ starlight/src/awi_structs/epoch.rs | 77 +++++++++++++++++------------- starlight/src/lib.rs | 4 +- testcrate/benches/bench.rs | 4 +- testcrate/tests/fuzz_elementary.rs | 2 +- testcrate/tests/fuzz_lower.rs | 4 +- testcrate/tests/stats.rs | 4 +- 7 files changed, 57 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8571e6..fcbab088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.3.0] - TODO +### Changes +- merged `Epoch::assert_assertions` and `Epoch::assert_assertions_strict` + ## [0.2.0] - 2023-12-08 ### Crate - `awint` 0.15 diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index d663f2fa..c5ca5d43 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -3,7 +3,13 @@ #![allow(clippy::new_without_default)] -use std::{cell::RefCell, mem, num::NonZeroUsize, rc::Rc, thread::panicking}; +use std::{ + cell::RefCell, + mem::{self}, + num::NonZeroUsize, + rc::Rc, + thread::panicking, +}; use awint::{ awint_dag::{ @@ -92,7 +98,11 @@ impl Drop for EpochData { /// /// `awint_dag::epoch` has a stack system which this uses, but this can have its /// own stack on top of that. -#[derive(Clone)] +/// +/// This raw version of `Epoch` has no drop code and all things need to be +/// carefully handled to avoid virtual leakage or trying to call +/// `remove_as_current` twice. +#[derive(Debug, Clone)] pub struct EpochShared { pub epoch_data: Rc>, pub p_self: PEpochShared, @@ -370,6 +380,8 @@ thread_local!( static EPOCH_STACK: RefCell> = RefCell::new(vec![]); ); +/// Returns a clone of the current `EpochShared`, or return `None` if there is +/// none #[must_use] pub fn get_current_epoch() -> Option { CURRENT_EPOCH.with(|top| { @@ -378,7 +390,7 @@ pub fn get_current_epoch() -> Option { }) } -/// Do no call recursively. +/// Allows access to the current epoch. Do no call recursively. pub fn no_recursive_current_epoch T>(mut f: F) -> T { CURRENT_EPOCH.with(|top| { let top = top.borrow(); @@ -390,7 +402,7 @@ pub fn no_recursive_current_epoch T>(mut f: F) -> T }) } -/// Do no call recursively. +/// Allows mutable access to the current epoch. Do no call recursively. pub fn no_recursive_current_epoch_mut T>(mut f: F) -> T { CURRENT_EPOCH.with(|top| { let mut top = top.borrow_mut(); @@ -497,6 +509,7 @@ pub fn _callback() -> EpochCallback { /// Using `mem::forget` or similar on a `Epoch` will leak `State`s and /// cause them to not be cleaned up, and will also likely cause panics because /// of the stack requirement. +#[derive(Debug)] pub struct Epoch { shared: EpochShared, } @@ -511,6 +524,19 @@ impl Drop for Epoch { } } +#[derive(Debug)] +pub struct SuspendedEpoch { + epoch: Epoch, +} + +impl SuspendedEpoch { + /// Resumes the `Epoch` + pub fn resume(self) -> Epoch { + self.epoch.shared.set_as_current(); + self.epoch + } +} + impl Epoch { #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -527,12 +553,11 @@ impl Epoch { Self { shared } } - /* - /// Returns `None` if there is a shared `Epoch` still alive - pub fn end(self) -> Option { - self.shared.remove_associated(); + /// Suspends the `Epoch` + pub fn suspend(self) -> SuspendedEpoch { self.shared.remove_as_current(); - }*/ + SuspendedEpoch { epoch: self } + } /// Intended primarily for developer use #[doc(hidden)] @@ -555,30 +580,15 @@ impl Epoch { self.shared.assertions() } - /// If any assertion bit evaluates to false, this returns an error. - pub fn assert_assertions(&self) -> Result<(), EvalError> { - self.shared.assert_assertions(false) - } - - /// If any assertion bit evaluates to false, this returns an error. If there - /// were no known false assertions but some are `Value::Unknown`, this - /// returns a specific error for it. - pub fn assert_assertions_strict(&self) -> Result<(), EvalError> { - self.shared.assert_assertions(true) - } - - /// Used for testing - pub fn prune_ignore_assertions(&self) -> Result<(), EvalError> { - let epoch_shared = get_current_epoch().unwrap(); - if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { - return Err(EvalError::OtherStr("epoch is not the current epoch")) - } - // do not assert assertions because that can trigger lowering - let mut lock = epoch_shared.epoch_data.borrow_mut(); - lock.ensemble.prune_states() + /// If any assertion bit evaluates to false, this returns an error. If + /// `strict` and an assertion could not be evaluated to a known value, this + /// also returns an error. Prunes assertions evaluated to a constant true. + pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { + self.shared.assert_assertions(strict) } - /// For users, this removes all states that do not lead to a live `EvalAwi` + /// Removes all states that do not lead to a live `EvalAwi`, and loosely + /// evaluates assertions. pub fn prune(&self) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { @@ -590,8 +600,9 @@ impl Epoch { lock.ensemble.prune_states() } - /// Lowers all states. This is not needed in most circumstances, `EvalAwi` - /// and optimization functions do this on demand. + /// Lowers all states internally into `LNode`s and `TNode`s. This is not + /// needed in most circumstances, `EvalAwi` and optimization functions + /// do this on demand. pub fn lower(&self) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index fa2e8515..5b8db91c 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -106,14 +106,14 @@ //! input.retro_(&awi!(0101)).unwrap(); //! // check assertions (all `dag::assert*` functions and dynamic `unwrap`s done //! // during the current `Epoch`) -//! epoch0.assert_assertions_strict().unwrap(); +//! epoch0.assert_assertions(true).unwrap(); //! // evaluate the outputs //! awi::assert_eq!(output_counter.eval().unwrap(), awi!(0011)); //! awi::assert_eq!(output_data.eval().unwrap(), awi!(0xa505_u16)); //! //! // reassign and reevaluate //! input.retro_(&awi!(1011)).unwrap(); -//! awi::assert!(epoch0.assert_assertions().is_err()); +//! awi::assert!(epoch0.assert_assertions(true).is_err()); //! awi::assert_eq!(output_data.eval().unwrap(), awi!(0x7b0b_u16)); //! } //! drop(epoch0); diff --git a/testcrate/benches/bench.rs b/testcrate/benches/bench.rs index 01a20f60..a0c33080 100644 --- a/testcrate/benches/bench.rs +++ b/testcrate/benches/bench.rs @@ -16,7 +16,7 @@ fn lower_funnel(bencher: &mut Bencher) { let _eval = EvalAwi::from(&out); epoch0.prune().unwrap(); epoch0.lower().unwrap(); - epoch0.assert_assertions().unwrap(); + epoch0.assert_assertions(true).unwrap(); }) } @@ -32,7 +32,7 @@ fn optimize_funnel(bencher: &mut Bencher) { let _eval = EvalAwi::from(&out); epoch0.prune().unwrap(); epoch0.optimize().unwrap(); - epoch0.assert_assertions().unwrap(); + epoch0.assert_assertions(true).unwrap(); }) } diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 225671dd..8f08dfd0 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -110,7 +110,7 @@ impl Mem { } // evaluate all - epoch.assert_assertions().unwrap(); + epoch.assert_assertions(true).unwrap(); for pair in self.a.vals() { assert_eq!(pair.eval.as_ref().unwrap().eval().unwrap(), pair.awi); } diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 60e7862e..a585a1e7 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -166,7 +166,7 @@ impl Mem { // lower epoch.lower().unwrap(); - epoch.assert_assertions().unwrap(); + epoch.assert_assertions(false).unwrap(); // set remaining lazy roots for (lazy, lit) in self.roots.drain(..) { @@ -174,7 +174,7 @@ impl Mem { } // evaluate all - epoch.assert_assertions_strict().unwrap(); + epoch.assert_assertions(true).unwrap(); for pair in self.a.vals() { assert_eq!(pair.eval.as_ref().unwrap().eval().unwrap(), pair.awi); } diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index 29317895..72475431 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -13,14 +13,14 @@ fn stats_optimize_funnel() { let _eval = EvalAwi::from(&out); epoch0.prune().unwrap(); epoch0.lower().unwrap(); - epoch0.assert_assertions().unwrap(); + epoch0.assert_assertions(true).unwrap(); epoch0.ensemble(|ensemble| { awi::assert_eq!(ensemble.stator.states.len(), 2436); awi::assert_eq!(ensemble.backrefs.len_keys(), 8559); awi::assert_eq!(ensemble.backrefs.len_vals(), 1317); }); epoch0.optimize().unwrap(); - epoch0.assert_assertions().unwrap(); + epoch0.assert_assertions(true).unwrap(); epoch0.ensemble(|ensemble| { awi::assert_eq!(ensemble.stator.states.len(), 0); awi::assert_eq!(ensemble.backrefs.len_keys(), 5818); From e8e6d25e42e92193f1980cdf20d6169aeb5dfd9c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 23 Dec 2023 12:56:21 -0600 Subject: [PATCH 020/119] clean up `Epoch` --- starlight/src/awi_structs/epoch.rs | 174 ++++++++++++++++++----------- starlight/src/lower/lower_state.rs | 4 +- 2 files changed, 111 insertions(+), 67 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index c5ca5d43..1a35bd2f 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -153,27 +153,33 @@ impl EpochShared { } /// Removes `self` as the current `EpochShared` with respect to the - /// starlight stack - pub fn remove_as_current(&self) { + /// starlight stack. Returns an error if there is no current `EpochShared` + /// or `self.epoch_data` did not match the current. + pub fn remove_as_current(&self) -> Result<(), &'static str> { EPOCH_STACK.with(|top| { let mut stack = top.borrow_mut(); let next_current = stack.pop(); CURRENT_EPOCH.with(|top| { let mut current = top.borrow_mut(); - if let Some(to_drop) = current.take() { - if !Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) { - panic!( - "tried to drop an `Epoch` out of stacklike order before dropping the \ - current one" - ); + if let Some(ref to_drop) = current.take() { + if Rc::ptr_eq(&to_drop.epoch_data, &self.epoch_data) { + *current = next_current; + Ok(()) + } else { + // return the error how most users will trigger it + Err( + "tried to drop or suspend an `Epoch` out of stacklike order before \ + dropping or suspending the current `Epoch`", + ) } - *current = next_current; } else { - // there should be something current if the `Epoch` still exists - unreachable!() + Err( + "`remove_as_current` encountered no current `EpochShared`, which should \ + not be possible if an `Epoch` still exists", + ) } - }); - }); + }) + }) } /// Access to the `Ensemble` @@ -195,15 +201,17 @@ impl EpochShared { /// Removes states and assertions from the `Ensemble` that were associated /// with this particular `EpochShared` (other `EpochShared`s can still have /// states and assertions in the `Ensemble`) - pub fn remove_associated(&self) { - let mut epoch_data = self.epoch_data.borrow_mut(); - let ours = epoch_data.responsible_for.remove(self.p_self).unwrap(); + #[must_use] + pub fn remove_associated(&self) -> Option<()> { + let mut lock = self.epoch_data.borrow_mut(); + let ours = lock.responsible_for.remove(self.p_self)?; for p_state in &ours.states_inserted { - let _ = epoch_data.ensemble.remove_state(*p_state); + let _ = lock.ensemble.remove_state(*p_state); } - drop(epoch_data); + drop(lock); // drop the `EvalAwi`s of the assertions after unlocking drop(ours); + Some(()) } /// Returns a clone of the assertions currently associated with `self` @@ -485,6 +493,33 @@ pub fn _callback() -> EpochCallback { } } +/// Has the actual drop code attached, preventing the need for unsafe or a +/// nonzero cost abstraction somewhere +#[derive(Debug)] +struct EpochInnerDrop { + epoch_shared: EpochShared, + is_current: bool, +} + +impl Drop for EpochInnerDrop { + // track_caller does not work for `Drop` + fn drop(&mut self) { + // prevent invoking recursive panics and a buffer overrun + if !panicking() { + let res = self.epoch_shared.remove_associated(); + if self.is_current { + if let Err(e) = self.epoch_shared.remove_as_current() { + panic!("panicked upon dropping an `Epoch`: {e}"); + } + } + // this shouldn't be possible to fail separately + if res.is_none() { + panic!("encountered unreachable case upon dropping an `Epoch`"); + } + } + } +} + /// Manages the lifetimes and assertions of `State`s created by mimicking types. /// /// During the lifetime of a `Epoch` struct, all thread local `State`s @@ -500,7 +535,7 @@ pub fn _callback() -> EpochCallback { /// other thread local restrictions once all states have been lowered. /// [Epoch::ensemble] can be called to get it. /// -/// # Panics +/// # Custom Drop /// /// The lifetimes of `Epoch` structs should be stacklike, such that a /// `Epoch` created during the lifetime of another `Epoch` should be @@ -511,62 +546,83 @@ pub fn _callback() -> EpochCallback { /// of the stack requirement. #[derive(Debug)] pub struct Epoch { - shared: EpochShared, -} - -impl Drop for Epoch { - fn drop(&mut self) { - // prevent invoking recursive panics and a buffer overrun - if !panicking() { - self.shared.remove_associated(); - self.shared.remove_as_current(); - } - } + inner: EpochInnerDrop, } +/// Represents a suspended epoch +/// +/// # Custom Drop +/// +/// This will drop an internal `Epoch` and do state removal. #[derive(Debug)] pub struct SuspendedEpoch { - epoch: Epoch, + inner: EpochInnerDrop, } impl SuspendedEpoch { - /// Resumes the `Epoch` - pub fn resume(self) -> Epoch { - self.epoch.shared.set_as_current(); - self.epoch + /// Resumes the `Epoch` as current + pub fn resume(mut self) -> Epoch { + self.inner.epoch_shared.set_as_current(); + self.inner.is_current = true; + Epoch { inner: self.inner } } } impl Epoch { + /// Creates a new `Epoch` with an independent `Ensemble` #[allow(clippy::new_without_default)] pub fn new() -> Self { let new = EpochShared::new(); new.set_as_current(); - Self { shared: new } + Self { + inner: EpochInnerDrop { + epoch_shared: new, + is_current: true, + }, + } } + /// Creates an `Epoch` that shares the `Ensemble` of `other` + /// /// The epoch from this can be dropped out of order from `other`, /// but must be dropped before others that aren't also shared pub fn shared_with(other: &Epoch) -> Self { - let shared = EpochShared::shared_with(&other.shared); + let shared = EpochShared::shared_with(&other.shared()); shared.set_as_current(); - Self { shared } + Self { + inner: EpochInnerDrop { + epoch_shared: shared, + is_current: true, + }, + } + } + + fn shared(&self) -> &EpochShared { + &self.inner.epoch_shared } - /// Suspends the `Epoch` - pub fn suspend(self) -> SuspendedEpoch { - self.shared.remove_as_current(); - SuspendedEpoch { epoch: self } + fn check_current(&self) -> Result { + let epoch_shared = get_current_epoch().unwrap(); + if Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared().epoch_data) { + Ok(epoch_shared) + } else { + Err(EvalError::OtherStr("epoch is not the current epoch")) + } } - /// Intended primarily for developer use - #[doc(hidden)] - pub fn internal_epoch_shared(this: &Epoch) -> &EpochShared { - &this.shared + /// Suspends the `Epoch` from being the current epoch temporarily. Returns + /// an error if `self` is not the current `Epoch`. + pub fn suspend(mut self) -> Result { + // TODO in the `EvalError` redo (probably needs a `starlight` side `EvalError`), + // there should be a variant that returns the `Epoch` to prevent it from being + // dropped and causing another error + self.inner.epoch_shared.remove_as_current().unwrap(); + self.inner.is_current = false; + Ok(SuspendedEpoch { inner: self.inner }) } pub fn ensemble O>(&self, f: F) -> O { - self.shared.ensemble(f) + self.shared().ensemble(f) } pub fn verify_integrity(&self) -> Result<(), EvalError> { @@ -577,23 +633,20 @@ impl Epoch { /// from when sub-epochs are alive or from before the this Epoch was /// created) pub fn assertions(&self) -> Assertions { - self.shared.assertions() + self.shared().assertions() } /// If any assertion bit evaluates to false, this returns an error. If /// `strict` and an assertion could not be evaluated to a known value, this /// also returns an error. Prunes assertions evaluated to a constant true. pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { - self.shared.assert_assertions(strict) + self.shared().assert_assertions(strict) } /// Removes all states that do not lead to a live `EvalAwi`, and loosely /// evaluates assertions. pub fn prune(&self) -> Result<(), EvalError> { - let epoch_shared = get_current_epoch().unwrap(); - if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { - return Err(EvalError::OtherStr("epoch is not the current epoch")) - } + let epoch_shared = self.check_current()?; // get rid of constant assertions let _ = epoch_shared.assert_assertions(false); let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -604,10 +657,7 @@ impl Epoch { /// needed in most circumstances, `EvalAwi` and optimization functions /// do this on demand. pub fn lower(&self) -> Result<(), EvalError> { - let epoch_shared = get_current_epoch().unwrap(); - if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { - return Err(EvalError::OtherStr("epoch is not the current epoch")) - } + let epoch_shared = self.check_current()?; Ensemble::lower_all(&epoch_shared)?; let _ = epoch_shared.assert_assertions(false); Ok(()) @@ -615,10 +665,7 @@ impl Epoch { /// Runs optimization including lowering then pruning all states. pub fn optimize(&self) -> Result<(), EvalError> { - let epoch_shared = get_current_epoch().unwrap(); - if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { - return Err(EvalError::OtherStr("epoch is not the current epoch")) - } + let epoch_shared = self.check_current()?; Ensemble::lower_all(&epoch_shared)?; let mut lock = epoch_shared.epoch_data.borrow_mut(); lock.ensemble.optimize_all(); @@ -629,10 +676,7 @@ impl Epoch { /// This evaluates all loop drivers, and then registers loopback changes pub fn drive_loops(&self) -> Result<(), EvalError> { - let epoch_shared = get_current_epoch().unwrap(); - if !Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared.epoch_data) { - return Err(EvalError::OtherStr("epoch is not the current epoch")) - } + let epoch_shared = self.check_current()?; if epoch_shared .epoch_data .borrow() diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index 8036febf..b08e75ed 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -324,7 +324,7 @@ impl Ensemble { true } Err(e) => { - temporary.remove_as_current(); + temporary.remove_as_current().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); lock.ensemble.stator.states[p_state].err = Some(e.clone()); return Err(e) @@ -335,7 +335,7 @@ impl Ensemble { // sure there are none using assertions assert!(temporary. // assertions_empty()); let states = temporary.take_states_added(); - temporary.remove_as_current(); + temporary.remove_as_current().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); for p_state in states { let state = &lock.ensemble.stator.states[p_state]; From 2d93a2ec0e776ec694f8de7e0dda43602fdb0da9 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 23 Dec 2023 13:04:37 -0600 Subject: [PATCH 021/119] Update epoch.rs --- starlight/src/awi_structs/epoch.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 1a35bd2f..27519134 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -531,12 +531,12 @@ impl Drop for EpochInnerDrop { /// associated with the top level `Epoch` alive at the time they are /// created. Use [Epoch::assertions] to acquire these. /// -/// The internal `Ensemble` can be freed from any non-`Send`, non-`Sync`, and -/// other thread local restrictions once all states have been lowered. -/// [Epoch::ensemble] can be called to get it. -/// /// # Custom Drop /// +/// Upon being dropped, this will remove states that were associated with this +/// epoch, completely removing the `Ensemble` if there are no other `Epoch`s +/// shared with this one, and deregistering this as the current `Epoch`. +/// /// The lifetimes of `Epoch` structs should be stacklike, such that a /// `Epoch` created during the lifetime of another `Epoch` should be /// dropped before the older `Epoch` is dropped, otherwise a panic occurs. @@ -553,7 +553,8 @@ pub struct Epoch { /// /// # Custom Drop /// -/// This will drop an internal `Epoch` and do state removal. +/// Upon being dropped, this will have the effect of dropping the `Epoch` this +/// was created from (except the fact of which epoch is current is not changed). #[derive(Debug)] pub struct SuspendedEpoch { inner: EpochInnerDrop, @@ -597,10 +598,13 @@ impl Epoch { } } + /// Returns the `EpochShared` of `self` fn shared(&self) -> &EpochShared { &self.inner.epoch_shared } + /// Checks if `self.shared()` is the same as the current epoch, and returns + /// the `EpochShared` if so fn check_current(&self) -> Result { let epoch_shared = get_current_epoch().unwrap(); if Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared().epoch_data) { From 913a71ba186066b160cabd4fecd544ee65af28e5 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 23 Dec 2023 16:52:39 -0600 Subject: [PATCH 022/119] many improvements, needs next awint version --- starlight/src/awi_structs/epoch.rs | 175 +++++++++++++++++++------- starlight/src/awi_structs/eval_awi.rs | 9 +- starlight/src/ensemble/rnode.rs | 15 ++- testcrate/tests/epoch.rs | 51 +++++++- 4 files changed, 198 insertions(+), 52 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 27519134..84ae1b40 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -5,6 +5,7 @@ use std::{ cell::RefCell, + fmt::Debug, mem::{self}, num::NonZeroUsize, rc::Rc, @@ -63,36 +64,38 @@ impl PerEpochShared { /// /// # Custom Drop /// -/// This deregisters the `awint_dag::epoch::EpochKey` upon being dropped -#[derive(Debug)] +/// This struct should have its `epoch_key` popped off the stack and +/// `responsible_for` emptied before being dropped normally. During a panic, the +/// order of TLS operations is unspecified, and in practice +/// `std::thread::panicking` can return false during the drop code of structs in +/// TLS even if the thread is panicking. So, the drop code for `EpochData` does +/// nothing with the `EpochKey` and `mem::forget`s the `EvalAwi` assertions. pub struct EpochData { - pub epoch_key: EpochKey, + pub epoch_key: Option, pub ensemble: Ensemble, pub responsible_for: Arena, } impl Drop for EpochData { fn drop(&mut self) { - // prevent invoking recursive panics and a buffer overrun - if !panicking() { - // if `responsible_for` is not empty, then this `EpochData` is probably being - // dropped in a special case like a panic (I have `panicking` guards on all the - // impls, but it seems that in some cases that for some reason a panic on unwrap - // can start dropping `EpochData`s before the `Epoch`s, and there are - // arbitrarily bad interactions so we always need to forget any `EvalAwi`s here) - // in which the `Epoch` is not going to be useful anyway. We need to - // `mem::forget` just the `EvalAwi`s of the assertions - for (_, mut shared) in self.responsible_for.drain() { - for eval_awi in shared.assertions.bits.drain(..) { - // avoid the `EvalAwi` drop code trying to access recursively - mem::forget(eval_awi); - } + for (_, mut shared) in self.responsible_for.drain() { + for eval_awi in shared.assertions.bits.drain(..) { + // avoid the `EvalAwi` drop code + mem::forget(eval_awi); } - self.epoch_key.pop_off_epoch_stack(); } } } +impl Debug for EpochData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EpochData") + .field("epoch_key", &self.epoch_key) + .field("responsible_for.len()", &self.responsible_for.len()) + .finish() + } +} + /// The raw internal management struct for `Epoch`s. Most users should be using /// `Epoch`. /// @@ -102,17 +105,36 @@ impl Drop for EpochData { /// This raw version of `Epoch` has no drop code and all things need to be /// carefully handled to avoid virtual leakage or trying to call /// `remove_as_current` twice. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct EpochShared { pub epoch_data: Rc>, pub p_self: PEpochShared, } +impl Debug for EpochShared { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Ok(epoch_data) = self.epoch_data.try_borrow() { + f.debug_struct("EpochShared") + .field("epoch_data", &epoch_data) + .field("p_self", &self.p_self) + .finish() + } else { + f.debug_struct("EpochShared") + .field( + "epoch_data (already borrowed, cannot display in `Debug` impl)", + &(), + ) + .field("p_self", &self.p_self) + .finish() + } + } +} + impl EpochShared { /// Creates a new `EpochData` and registers a new `EpochCallback`. pub fn new() -> Self { let mut epoch_data = EpochData { - epoch_key: _callback().push_on_epoch_stack(), + epoch_key: Some(_callback().push_on_epoch_stack()), ensemble: Ensemble::new(), responsible_for: Arena::new(), }; @@ -182,6 +204,49 @@ impl EpochShared { }) } + /// Removes states and drops assertions from the `Ensemble` that were + /// associated with this particular `EpochShared`. This also deregisters the + /// `EpochCallback` if this was the last `EpochShared` with a + /// `PerEpochShared` in the `EpochData`. + /// + /// This function should not be called more than once per `self.p_self`. + pub fn drop_associated(&self) -> Result<(), EvalError> { + let mut lock = self.epoch_data.borrow_mut(); + if let Some(ours) = lock.responsible_for.remove(self.p_self) { + for p_state in &ours.states_inserted { + let _ = lock.ensemble.remove_state(*p_state); + } + drop(lock); + // drop the `EvalAwi`s of the assertions after unlocking + drop(ours); + + let mut lock = self.epoch_data.borrow_mut(); + if lock.responsible_for.is_empty() { + // we are the last `EpochShared` + match lock.epoch_key.take().unwrap().pop_off_epoch_stack() { + Ok(()) => (), + Err((self_gen, top_gen)) => { + return Err(EvalError::OtherString(format!( + "The last `starlight::Epoch` or `starlight::SuspendedEpoch` of a \ + group of one or more shared `Epoch`s was dropped out of stacklike \ + order, such that an `awint_dag::epoch::EpochKey` with generation {} \ + was attempted to be dropped before the current key with generation \ + {}. This may be because explicit `drop`s of `Epoch`s should be used \ + in a different order.", + self_gen, top_gen + ))); + } + } + } + Ok(()) + } else { + Err(EvalError::OtherStr( + "should be unreachable: called `EpochShared::drop_associated` on the same \ + `EpochShared`", + )) + } + } + /// Access to the `Ensemble` pub fn ensemble O>(&self, mut f: F) -> O { f(&self.epoch_data.borrow().ensemble) @@ -198,22 +263,6 @@ impl EpochShared { mem::take(&mut ours.states_inserted) } - /// Removes states and assertions from the `Ensemble` that were associated - /// with this particular `EpochShared` (other `EpochShared`s can still have - /// states and assertions in the `Ensemble`) - #[must_use] - pub fn remove_associated(&self) -> Option<()> { - let mut lock = self.epoch_data.borrow_mut(); - let ours = lock.responsible_for.remove(self.p_self)?; - for p_state in &ours.states_inserted { - let _ = lock.ensemble.remove_state(*p_state); - } - drop(lock); - // drop the `EvalAwi`s of the assertions after unlocking - drop(ours); - Some(()) - } - /// Returns a clone of the assertions currently associated with `self` pub fn assertions(&self) -> Assertions { let p_self = self.p_self; @@ -312,7 +361,7 @@ impl EpochShared { if let Some(s) = s { return Err(EvalError::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ - {p_rnode} {:?}", + {p_rnode} {}", s ))) } else { @@ -506,16 +555,14 @@ impl Drop for EpochInnerDrop { fn drop(&mut self) { // prevent invoking recursive panics and a buffer overrun if !panicking() { - let res = self.epoch_shared.remove_associated(); + if let Err(e) = self.epoch_shared.drop_associated() { + panic!("{e}"); + } if self.is_current { if let Err(e) = self.epoch_shared.remove_as_current() { panic!("panicked upon dropping an `Epoch`: {e}"); } } - // this shouldn't be possible to fail separately - if res.is_none() { - panic!("encountered unreachable case upon dropping an `Epoch`"); - } } } } @@ -541,6 +588,47 @@ impl Drop for EpochInnerDrop { /// `Epoch` created during the lifetime of another `Epoch` should be /// dropped before the older `Epoch` is dropped, otherwise a panic occurs. /// +/// ``` +/// use starlight::Epoch; +/// +/// // let epoch0 = Epoch::new(); +/// // // `epoch0` is the current epoch +/// // let epoch1 = Epoch::new(); +/// // // `epoch1` is the current epoch +/// // drop(epoch0); // panics here because `epoch1` was created during `epoch0` +/// // drop(epoch1); +/// +/// // this succeeds +/// let epoch0 = Epoch::new(); +/// // `epoch0` is current +/// let epoch1 = Epoch::new(); +/// // `epoch1` is current +/// drop(epoch1); +/// // `epoch0` is current +/// let epoch2 = Epoch::new(); +/// // `epoch2` is current +/// let epoch3 = Epoch::new(); +/// // `epoch3` is current +/// drop(epoch3); +/// // `epoch2` is current +/// drop(epoch2); +/// // `epoch0` is current +/// drop(epoch0); +/// +/// // these could be dropped in any order relative to one +/// // another because they share the same `Ensemble` and +/// // `awint_dag` mimicking types callback registration, +/// let epoch0 = Epoch::new(); +/// let subepoch0 = Epoch::shared_with(&epoch0); +/// drop(epoch0); +/// // but the last one to be dropped has the restriction +/// // with respect to an independent `Epoch` +/// let epoch1 = Epoch::new(); +/// //drop(subepoch0); // would panic +/// drop(epoch1); +/// drop(subepoch0); +/// ``` +/// /// Using `mem::forget` or similar on a `Epoch` will leak `State`s and /// cause them to not be cleaned up, and will also likely cause panics because /// of the stack requirement. @@ -586,7 +674,8 @@ impl Epoch { /// Creates an `Epoch` that shares the `Ensemble` of `other` /// /// The epoch from this can be dropped out of order from `other`, - /// but must be dropped before others that aren't also shared + /// but the shared group of `Epoch`s as a whole must follow the stacklike + /// drop order described in the documentation of `Epoch`. pub fn shared_with(other: &Epoch) -> Self { let shared = EpochShared::shared_with(&other.shared()); shared.set_as_current(); diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index 4cf534a7..3289ad54 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -93,8 +93,13 @@ impl EvalAwi { from_isize isize; ); + fn try_get_nzbw(&self) -> Result { + Ensemble::get_thread_local_rnode_nzbw(self.p_rnode) + } + + #[track_caller] pub fn nzbw(&self) -> NonZeroUsize { - Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() + self.try_get_nzbw().unwrap() } pub fn bw(&self) -> usize { @@ -143,7 +148,7 @@ impl EvalAwi { } pub fn eval(&self) -> Result { - let nzbw = self.nzbw(); + let nzbw = self.try_get_nzbw()?; let mut res = awi::Awi::zero(nzbw); for bit_i in 0..res.bw() { let val = Ensemble::calculate_thread_local_rnode_value(self.p_rnode, bit_i)?; diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index d8bfe099..10ec9f7c 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -66,7 +66,10 @@ impl Ensemble { if let Some(rnode) = ensemble.rnodes.get(p_rnode) { Ok(NonZeroUsize::new(rnode.bits.len()).unwrap()) } else { - Err(EvalError::OtherStr("could not find thread local `RNode`")) + Err(EvalError::OtherStr( + "could not find thread local `RNode`, probably an `EvalAwi` or `LazyAwi` was used \ + outside of the `Epoch` it was created in", + )) } } @@ -82,7 +85,10 @@ impl Ensemble { return Err(EvalError::WrongBitwidth); } } else { - return Err(EvalError::OtherStr("could not find thread local `RNode`")) + return Err(EvalError::OtherStr( + "could not find thread local `RNode`, probably a `LazyAwi` was used outside of \ + the `Epoch` it was created in", + )) } for bit_i in 0..bits.bw() { let p_back = ensemble.rnodes[p_rnode].bits[bit_i]; @@ -116,7 +122,10 @@ impl Ensemble { )) } } else { - return Err(EvalError::OtherStr("could not find thread local `RNode`")) + return Err(EvalError::OtherStr( + "could not find thread local `RNode`, probably an `EvalAwi` was used outside of \ + the `Epoch` it was created in", + )) }; if ensemble.stator.states.is_empty() { // optimization after total pruning from `optimization` diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index 85fb0efc..ead74a6c 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -1,4 +1,4 @@ -use starlight::{dag::*, Epoch}; +use starlight::{awi, dag::*, Epoch, EvalAwi, LazyAwi}; #[test] #[should_panic] @@ -15,16 +15,46 @@ fn epoch_unregistered1() { #[test] #[should_panic] fn epoch_unregistered2() { + let epoch = Epoch::new(); + drop(epoch); + let _x: inlawi_ty!(1) = InlAwi::zero(); +} + +// generates some mimick ops and assertions +fn ex() -> (LazyAwi, EvalAwi) { + let lazy_x = LazyAwi::opaque(bw(2)); + let mut y = awi!(01); + y.shl_(lazy_x.to_usize()).unwrap(); + let eval_x = EvalAwi::from(&y); + (lazy_x, eval_x) +} + +#[test] +fn epoch_nested0() { let epoch0 = Epoch::new(); + let (lazy0, eval0) = ex(); + let epoch1 = Epoch::new(); + { + use awi::*; + awi::assert!(lazy0.retro_(&awi!(01)).is_err()); + awi::assert!(eval0.eval().is_err()); + } + drop(epoch1); drop(epoch0); - let _x: inlawi_ty!(1) = InlAwi::zero(); } #[test] -fn epoch_nested() { +fn epoch_nested1() { let epoch0 = Epoch::new(); + let (lazy0, eval0) = ex(); let epoch1 = Epoch::new(); drop(epoch1); + { + use awi::*; + lazy0.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); + epoch0.assert_assertions(true).unwrap(); + } drop(epoch0); } @@ -40,7 +70,20 @@ fn epoch_nested_fail() { #[test] fn epoch_shared() { let epoch0 = Epoch::new(); + let (lazy0, eval0) = ex(); let epoch1 = Epoch::shared_with(&epoch0); - drop(epoch1); + let (lazy1, eval1) = ex(); + { + use awi::*; + lazy0.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); + epoch0.assert_assertions(true).unwrap(); + } drop(epoch0); + { + use awi::*; + lazy0.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); + } + drop(epoch1); } From c2bd932122448f7f3e74b3eeb7ae1930224a5da5 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 23 Dec 2023 17:24:10 -0600 Subject: [PATCH 023/119] fix pruning --- CHANGELOG.md | 4 ++++ starlight/src/awi_structs/epoch.rs | 25 ++++++++++++++++--------- testcrate/tests/epoch.rs | 14 +++++++------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcbab088..25173c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [0.3.0] - TODO ### Changes - merged `Epoch::assert_assertions` and `Epoch::assert_assertions_strict` +- many fixes for `Epoch` behavior + +### Additions +- Added `Epoch::suspend` ## [0.2.0] - 2023-12-08 ### Crate diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 84ae1b40..7a207b50 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -286,9 +286,10 @@ impl EpochShared { Assertions { bits: cloned } } - /// This evaluates all assertions (returning an error if any are false, and - /// returning an error on unevaluatable assertions if `strict`), and - /// eliminates assertions that evaluate to a constant true. + /// This evaluates all associated assertions of this `EpochShared` + /// (returning an error if any are false, and returning an error on + /// unevaluatable assertions if `strict`), and eliminates assertions + /// that evaluate to a constant true. pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { let p_self = self.p_self; let epoch_data = self.epoch_data.borrow(); @@ -679,12 +680,13 @@ impl Epoch { pub fn shared_with(other: &Epoch) -> Self { let shared = EpochShared::shared_with(&other.shared()); shared.set_as_current(); - Self { + let res = Self { inner: EpochInnerDrop { epoch_shared: shared, is_current: true, }, - } + }; + res } /// Returns the `EpochShared` of `self` @@ -739,7 +741,7 @@ impl Epoch { /// Removes all states that do not lead to a live `EvalAwi`, and loosely /// evaluates assertions. pub fn prune(&self) -> Result<(), EvalError> { - let epoch_shared = self.check_current()?; + let epoch_shared = self.shared(); // get rid of constant assertions let _ = epoch_shared.assert_assertions(false); let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -748,7 +750,7 @@ impl Epoch { /// Lowers all states internally into `LNode`s and `TNode`s. This is not /// needed in most circumstances, `EvalAwi` and optimization functions - /// do this on demand. + /// do this on demand. Requires that `self` be the current `Epoch`. pub fn lower(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; Ensemble::lower_all(&epoch_shared)?; @@ -756,7 +758,8 @@ impl Epoch { Ok(()) } - /// Runs optimization including lowering then pruning all states. + /// Runs optimization including lowering then pruning all states. Requires + /// that `self` be the current `Epoch`. pub fn optimize(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; Ensemble::lower_all(&epoch_shared)?; @@ -767,7 +770,11 @@ impl Epoch { Ok(()) } - /// This evaluates all loop drivers, and then registers loopback changes + // TODO this only requires the current `Epoch` for the general case, may need a + // second function + + /// This evaluates all loop drivers, and then registers loopback changes. + /// Requires that `self` be the current `Epoch`. pub fn drive_loops(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; if epoch_shared diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index ead74a6c..26e3adaa 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -68,22 +68,22 @@ fn epoch_nested_fail() { } #[test] -fn epoch_shared() { +fn epoch_shared0() { let epoch0 = Epoch::new(); let (lazy0, eval0) = ex(); let epoch1 = Epoch::shared_with(&epoch0); - let (lazy1, eval1) = ex(); { use awi::*; lazy0.retro_(&awi!(01)).unwrap(); awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); epoch0.assert_assertions(true).unwrap(); } + drop(lazy0); + drop(eval0); drop(epoch0); - { - use awi::*; - lazy0.retro_(&awi!(01)).unwrap(); - awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); - } + // TODO unsure of what the precise semantics should be, currently unless all + // `RNode`s are removed and prune is called there is still stuff + epoch1.prune().unwrap(); + awi::assert!(epoch1.ensemble(|ensemble| ensemble.stator.states.is_empty())); drop(epoch1); } From e5e6462d875fe3a77712ae574e414b13ac04fd71 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 23 Dec 2023 17:48:49 -0600 Subject: [PATCH 024/119] suspension tests and doc --- starlight/src/awi_structs/epoch.rs | 27 +++++++++++++++++ testcrate/tests/epoch.rs | 47 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 7a207b50..3f479eb1 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -616,6 +616,33 @@ impl Drop for EpochInnerDrop { /// // `epoch0` is current /// drop(epoch0); /// +/// // suspended epochs work the same with respect to +/// // suspend and resume points +/// let epoch0 = Epoch::new(); +/// // `epoch0` is current +/// let suspended_epoch0 = epoch0.suspend().unwrap(); +/// // no epoch is current +/// let epoch1 = Epoch::new(); +/// // `epoch1` is current +/// let epoch0 = suspended_epoch0.resume(); +/// // `epoch0` is current +/// //drop(epoch1); // not here +/// let suspended_epoch0 = epoch0.suspend().unwrap(); +/// // `epoch1` is current +/// drop(epoch1); +/// // no epoch is current +/// let epoch0 = suspended_epoch0.resume(); +/// // `epoch0` is current +/// let suspended_epoch0 = epoch0.suspend().unwrap(); +/// // no epoch is current +/// let epoch1 = Epoch::new(); +/// // `epoch1` is current +/// // be aware that dropping the suspended version also counts +/// //drop(suspended_epoch0); +/// drop(epoch1); +/// // no epoch is current +/// drop(suspended_epoch0); +/// /// // these could be dropped in any order relative to one /// // another because they share the same `Ensemble` and /// // `awint_dag` mimicking types callback registration, diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index 26e3adaa..11b61ac0 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -87,3 +87,50 @@ fn epoch_shared0() { awi::assert!(epoch1.ensemble(|ensemble| ensemble.stator.states.is_empty())); drop(epoch1); } + +#[test] +fn epoch_suspension0() { + let epoch0 = Epoch::new(); + let (lazy0, eval0) = ex(); + let epoch0 = epoch0.suspend().unwrap(); + let epoch1 = Epoch::new(); + { + use awi::*; + awi::assert!(lazy0.retro_(&awi!(01)).is_err()); + awi::assert!(eval0.eval().is_err()); + } + let (lazy1, eval1) = ex(); + // TODO probably should create `RNode` and `PState` arenas with generations + // starting at random offsets to help prevent collisions + /*{ + use awi::*; + lazy1.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval1.eval().unwrap(), awi!(10)); + epoch1.assert_assertions(true).unwrap(); + }*/ + { + use awi::*; + lazy1.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval1.eval().unwrap(), awi!(10)); + epoch1.assert_assertions(true).unwrap(); + } + drop(epoch1); + let epoch0 = epoch0.resume(); + { + use awi::*; + lazy0.retro_(&awi!(01)).unwrap(); + awi::assert_eq!(eval0.eval().unwrap(), awi!(10)); + epoch0.assert_assertions(true).unwrap(); + } + drop(epoch0); +} + +#[test] +#[should_panic] +fn epoch_suspension1() { + let epoch0 = Epoch::new(); + let epoch0 = epoch0.suspend().unwrap(); + let epoch1 = Epoch::new(); + drop(epoch0); + drop(epoch1); +} From 6f8ca813526c1468de2cf93391918645b7e289fc Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 26 Dec 2023 21:42:29 -0600 Subject: [PATCH 025/119] export SuspendedEpoch --- starlight/src/awi_structs.rs | 2 +- starlight/src/lib.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/starlight/src/awi_structs.rs b/starlight/src/awi_structs.rs index ce1de563..1ebe7e6e 100644 --- a/starlight/src/awi_structs.rs +++ b/starlight/src/awi_structs.rs @@ -3,7 +3,7 @@ mod eval_awi; mod lazy_awi; mod temporal; -pub use epoch::{Assertions, Epoch}; +pub use epoch::{Assertions, Epoch, SuspendedEpoch}; pub use eval_awi::EvalAwi; pub use lazy_awi::{LazyAwi, LazyInlAwi}; pub use temporal::{Loop, Net}; diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 5b8db91c..cbc96431 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -172,7 +172,9 @@ pub mod ensemble; pub mod lower; mod misc; pub mod route; -pub use awi_structs::{epoch, Assertions, Epoch, EvalAwi, LazyAwi, LazyInlAwi, Loop, Net}; +pub use awi_structs::{ + epoch, Assertions, Epoch, EvalAwi, LazyAwi, LazyInlAwi, Loop, Net, SuspendedEpoch, +}; #[cfg(feature = "debug")] pub use awint::awint_dag::triple_arena_render; pub use awint::{self, awint_dag, awint_dag::triple_arena}; From c8f77467c75cbb4c722cb1df96f73d2384cdba56 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 26 Dec 2023 22:01:37 -0600 Subject: [PATCH 026/119] factor out a `Notary` struct --- starlight/src/awi_structs/epoch.rs | 7 ++--- starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/debug.rs | 2 +- starlight/src/ensemble/optimize.rs | 3 +- starlight/src/ensemble/rnode.rs | 48 +++++++++++++++++++++++------- starlight/src/ensemble/together.rs | 14 ++++----- starlight/src/route.rs | 2 ++ starlight/src/route/channel.rs | 9 +++++- starlight/src/route/cnode.rs | 2 +- starlight/src/route/region_adv.rs | 4 +-- starlight/src/route/router.rs | 8 +++++ 11 files changed, 72 insertions(+), 29 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 3f479eb1..eaed768d 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -705,15 +705,14 @@ impl Epoch { /// but the shared group of `Epoch`s as a whole must follow the stacklike /// drop order described in the documentation of `Epoch`. pub fn shared_with(other: &Epoch) -> Self { - let shared = EpochShared::shared_with(&other.shared()); + let shared = EpochShared::shared_with(other.shared()); shared.set_as_current(); - let res = Self { + Self { inner: EpochInnerDrop { epoch_shared: shared, is_current: true, }, - }; - res + } } /// Returns the `EpochShared` of `self` diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index e6888db0..298bff18 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -10,7 +10,7 @@ mod value; pub use lnode::{LNode, PLNode}; pub use optimize::{Optimizer, POpt}; -pub use rnode::{PRNode, RNode}; +pub use rnode::{Notary, PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; pub use together::{Ensemble, Equiv, PBack, Referent}; diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index a12cf89f..3ba51f2c 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -239,7 +239,7 @@ impl Ensemble { }) } Referent::ThisRNode(p_rnode) => { - let rnode = self.rnodes.get(*p_rnode).unwrap(); + let rnode = self.notary.rnodes.get(*p_rnode).unwrap(); let mut inx = u64::MAX; for (i, bit) in rnode.bits.iter().enumerate() { if *bit == Some(p_self) { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 577a4577..831388b4 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -425,8 +425,7 @@ impl Ensemble { tnode.p_driver = p_back_new; } Referent::ThisRNode(p_rnode) => { - // here we see a major advantage of the backref system - let rnode = self.rnodes.get_mut(p_rnode).unwrap(); + let rnode = self.notary.rnodes.get_mut(p_rnode).unwrap(); let mut found = false; for bit in &mut rnode.bits { if let Some(bit) = bit { diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 10ec9f7c..003d592b 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -1,6 +1,9 @@ -use std::num::NonZeroUsize; +use std::num::{NonZeroU128, NonZeroUsize}; -use awint::awint_dag::{triple_arena::ptr_struct, EvalError, PState}; +use awint::awint_dag::{ + triple_arena::{ptr_struct, Arena}, + EvalError, PState, +}; use crate::{ awi, @@ -10,6 +13,11 @@ use crate::{ ptr_struct!(PRNode); +ptr_struct!( + PExternal[NonZeroU128]() + doc="A UUID `Ptr` for external use that maps to an internal `PRNode`" +); + /// Reference/Register/Report node, used for external references kept alive /// after `State` pruning #[derive(Debug, Clone)] @@ -23,11 +31,25 @@ impl RNode { } } +/// Used for managing external references +#[derive(Debug, Clone)] +pub struct Notary { + pub rnodes: Arena, +} + +impl Notary { + pub fn new() -> Self { + Self { + rnodes: Arena::new(), + } + } +} + impl Ensemble { #[must_use] pub fn make_rnode_for_pstate(&mut self, p_state: PState) -> Option { self.initialize_state_bits_if_needed(p_state)?; - let p_rnode = self.rnodes.insert(RNode::new()); + let p_rnode = self.notary.rnodes.insert(RNode::new()); let len = self.stator.states[p_state].p_self_bits.len(); for i in 0..len { let p_bit = self.stator.states[p_state].p_self_bits[i]; @@ -37,16 +59,16 @@ impl Ensemble { .backrefs .insert_key(p_equiv, Referent::ThisRNode(p_rnode)) .unwrap(); - self.rnodes[p_rnode].bits.push(Some(p_back_new)); + self.notary.rnodes[p_rnode].bits.push(Some(p_back_new)); } else { - self.rnodes[p_rnode].bits.push(None); + self.notary.rnodes[p_rnode].bits.push(None); } } Some(p_rnode) } pub fn remove_rnode(&mut self, p_rnode: PRNode) -> Result<(), EvalError> { - if let Some(rnode) = self.rnodes.remove(p_rnode) { + if let Some(rnode) = self.notary.rnodes.remove(p_rnode) { for p_back in rnode.bits { if let Some(p_back) = p_back { let referent = self.backrefs.remove_key(p_back).unwrap().0; @@ -63,7 +85,7 @@ impl Ensemble { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { Ok(NonZeroUsize::new(rnode.bits.len()).unwrap()) } else { Err(EvalError::OtherStr( @@ -80,7 +102,7 @@ impl Ensemble { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { if rnode.bits.len() != bits.bw() { return Err(EvalError::WrongBitwidth); } @@ -91,7 +113,7 @@ impl Ensemble { )) } for bit_i in 0..bits.bw() { - let p_back = ensemble.rnodes[p_rnode].bits[bit_i]; + let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; if let Some(p_back) = p_back { ensemble .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) @@ -108,7 +130,7 @@ impl Ensemble { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - let p_back = if let Some(rnode) = ensemble.rnodes.get(p_rnode) { + let p_back = if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { if bit_i >= rnode.bits.len() { return Err(EvalError::OtherStr( "something went wrong with rnode bitwidth", @@ -142,3 +164,9 @@ impl Default for RNode { Self::new() } } + +impl Default for Notary { + fn default() -> Self { + Self::new() + } +} diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 8b8b499b..83fe8cd2 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -10,7 +10,7 @@ use awint::{ use crate::{ ensemble::{ - value::Evaluator, LNode, Optimizer, PLNode, PRNode, PTNode, RNode, State, Stator, TNode, + value::Evaluator, LNode, Notary, Optimizer, PLNode, PRNode, PTNode, State, Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, @@ -61,7 +61,7 @@ pub enum Referent { #[derive(Debug, Clone)] pub struct Ensemble { pub backrefs: SurjectArena, - pub rnodes: Arena, + pub notary: Notary, pub stator: Stator, pub lnodes: Arena, pub tnodes: Arena, @@ -74,7 +74,7 @@ impl Ensemble { pub fn new() -> Self { Self { backrefs: SurjectArena::new(), - rnodes: Arena::new(), + notary: Notary::new(), stator: Stator::new(), lnodes: Arena::new(), tnodes: Arena::new(), @@ -183,7 +183,7 @@ impl Ensemble { Referent::ThisStateBit(..) => false, Referent::Input(p_input) => !self.lnodes.contains(*p_input), Referent::LoopDriver(p_driver) => !self.tnodes.contains(*p_driver), - Referent::ThisRNode(p_rnode) => !self.rnodes.contains(*p_rnode), + Referent::ThisRNode(p_rnode) => !self.notary.rnodes.contains(*p_rnode), }; if invalid { return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) @@ -234,12 +234,12 @@ impl Ensemble { ))) } } - for rnode in self.rnodes.vals() { + for rnode in self.notary.rnodes.vals() { for p_back in &rnode.bits { if let Some(p_back) = p_back { if let Some(referent) = self.backrefs.get_key(*p_back) { if let Referent::ThisRNode(p_rnode) = referent { - if !self.rnodes.contains(*p_rnode) { + if !self.notary.rnodes.contains(*p_rnode) { return Err(EvalError::OtherString(format!( "{rnode:?} backref {p_rnode} is invalid" ))) @@ -294,7 +294,7 @@ impl Ensemble { tnode.p_driver != p_back } Referent::ThisRNode(p_rnode) => { - let rnode = self.rnodes.get(*p_rnode).unwrap(); + let rnode = self.notary.rnodes.get(*p_rnode).unwrap(); let mut found = false; for bit in &rnode.bits { if *bit == Some(p_back) { diff --git a/starlight/src/route.rs b/starlight/src/route.rs index fd4f1d3a..834d1235 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + mod cedge; mod channel; mod cnode; diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index cff1acea..d04ca81f 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -44,7 +44,8 @@ impl Channeler { /* /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid /// subnodes that still contain `ensemble::PBack` - pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: ensemble::PBack) -> SmallVec<[PCNode; 4]> { + pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: ensemble::PBack) + -> SmallVec<[PCNode; 4]> { let cnode = self.cnodes.get(p_cnode).unwrap(); if let Some(mut adv) = RegionAdvancer::new(&self.backref_to_cnode, |_, (p_back1, _), ()| { p_back1.cmp(&p_back) @@ -192,3 +193,9 @@ impl Channeler { Ok(()) } } + +impl Default for Channeler { + fn default() -> Self { + Self::new() + } +} diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index aa7f0f63..69cb4412 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -120,5 +120,5 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // layers, need to methodically determine what we really want ptr_struct!(P0); - loop {} + todo!() } diff --git a/starlight/src/route/region_adv.rs b/starlight/src/route/region_adv.rs index 58ba55aa..6ccc9338 100644 --- a/starlight/src/route/region_adv.rs +++ b/starlight/src/route/region_adv.rs @@ -21,7 +21,7 @@ impl Ordering, P: Ptr, K, V> Advancer for RegionAdvancer< let (gen, link) = collection.get_link_no_gen(current).unwrap(); self.p = link.next(); let p_current = P::_from_raw(current, gen); - if (self.cmp)(p_current, &link.t.0, &link.t.1) == Ordering::Equal { + if (self.cmp)(p_current, link.t.0, link.t.1) == Ordering::Equal { Some(p_current) } else { // have reached the end of the region, also set to `None` to shortcut in @@ -48,7 +48,7 @@ impl Ordering, P: Ptr, K, V> RegionAdvancer { let mut p = p.inx(); loop { let (gen, link) = collection.get_link_no_gen(p).unwrap(); - if cmp(Ptr::_from_raw(p, gen), &link.t.0, &link.t.1) == Ordering::Equal { + if cmp(Ptr::_from_raw(p, gen), link.t.0, link.t.1) == Ordering::Equal { if let Some(p_prev) = link.prev() { p = p_prev; } else { diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 6bc1b99e..f7f1ef9e 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -16,6 +16,7 @@ impl Router { } } + /* // TODO current plan is to have a corresponding function on the target `Epoch` // that calls this. May want some kind of `Epoch` restating system (or use // shared `Epoch`s?). The routing info is generated, then one or more other @@ -24,7 +25,14 @@ impl Router { let mut res = Self::new(); res } + */ // TODO are the target and program both on channeling graphs, what assymetries // are there? } + +impl Default for Router { + fn default() -> Self { + Self::new() + } +} From 824fae0343745e8b19327d8f502a29fd08a9c367 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 26 Dec 2023 22:44:39 -0600 Subject: [PATCH 027/119] use a UUID `PExternal` --- starlight/Cargo.toml | 1 + starlight/src/awi_structs/epoch.rs | 16 +++--- starlight/src/awi_structs/eval_awi.rs | 25 ++++---- starlight/src/awi_structs/lazy_awi.rs | 34 ++++++----- starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/debug.rs | 2 +- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/rnode.rs | 83 +++++++++++++++++++-------- starlight/src/ensemble/together.rs | 8 +-- 9 files changed, 110 insertions(+), 63 deletions(-) diff --git a/starlight/Cargo.toml b/starlight/Cargo.toml index c865db9f..ea5ec370 100644 --- a/starlight/Cargo.toml +++ b/starlight/Cargo.toml @@ -15,6 +15,7 @@ categories = ["algorithms"] #awint = { path = "../../awint/awint", default-features = false, features = ["rand_support", "dag"] } awint = { version = "0.15", default-features = false, features = ["rand_support", "dag"] } rand_xoshiro = { version = "0.6", default-features = false } +rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } [features] # note: "dag", "rand_support", and "std" are all turned on always diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index eaed768d..73d6fa41 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -315,28 +315,28 @@ impl EpochShared { .assertions .bits[i]; let p_state = eval_awi.state(); - let p_rnode = eval_awi.p_rnode(); + let p_external = eval_awi.p_external(); drop(epoch_data); - let val = Ensemble::calculate_thread_local_rnode_value(p_rnode, 0)?; + let val = Ensemble::calculate_thread_local_rnode_value(p_external, 0)?; if let Some(val) = val.known_value() { if !val { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { return Err(EvalError::OtherString(format!( - "an assertion bit evaluated to false, failed on {p_rnode} {:?}", + "an assertion bit evaluated to false, failed on {p_external} {:?}", s ))) } else { return Err(EvalError::OtherString(format!( - "an assertion bit evaluated to false, failed on {p_rnode} {p_state}" + "an assertion bit evaluated to false, failed on {p_external} {p_state}" ))) } } } else if unknown.is_none() { // get the earliest failure to evaluate, should be closest to the root cause. // Wait for all bits to be checked for falsity - unknown = Some((p_rnode, p_state)); + unknown = Some((p_external, p_state)); } if val.is_const() { // remove the assertion @@ -356,19 +356,19 @@ impl EpochShared { } } if strict { - if let Some((p_rnode, p_state)) = unknown { + if let Some((p_external, p_state)) = unknown { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { return Err(EvalError::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ - {p_rnode} {}", + {p_external} {}", s ))) } else { return Err(EvalError::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ - {p_rnode} {p_state}" + {p_external} {p_state}" ))) } } diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index 3289ad54..12365418 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -7,7 +7,7 @@ use awint::{ use crate::{ awi, - ensemble::{Ensemble, PRNode}, + ensemble::{Ensemble, PExternal}, epoch::get_current_epoch, }; @@ -27,7 +27,7 @@ use crate::{ /// current `Epoch`. pub struct EvalAwi { p_state: PState, - p_rnode: PRNode, + p_external: PExternal, } impl Drop for EvalAwi { @@ -36,7 +36,7 @@ impl Drop for EvalAwi { if !panicking() { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); - let res = lock.ensemble.remove_rnode(self.p_rnode); + let res = lock.ensemble.remove_rnode(self.p_external); if res.is_err() { panic!( "most likely, an `EvalAwi` created in one `Epoch` was dropped in another" @@ -93,8 +93,12 @@ impl EvalAwi { from_isize isize; ); + pub fn p_external(&self) -> PExternal { + self.p_external + } + fn try_get_nzbw(&self) -> Result { - Ensemble::get_thread_local_rnode_nzbw(self.p_rnode) + Ensemble::get_thread_local_rnode_nzbw(self.p_external) } #[track_caller] @@ -106,10 +110,6 @@ impl EvalAwi { self.nzbw().get() } - pub fn p_rnode(&self) -> PRNode { - self.p_rnode - } - /// Used internally to create `EvalAwi`s /// /// # Panics @@ -120,14 +120,17 @@ impl EvalAwi { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); match lock.ensemble.make_rnode_for_pstate(p_state) { - Some(p_rnode) => { + Some(p_external) => { lock.ensemble .stator .states .get_mut(p_state) .unwrap() .inc_extern_rc(); - Self { p_state, p_rnode } + Self { + p_state, + p_external, + } } None => { panic!( @@ -151,7 +154,7 @@ impl EvalAwi { let nzbw = self.try_get_nzbw()?; let mut res = awi::Awi::zero(nzbw); for bit_i in 0..res.bw() { - let val = Ensemble::calculate_thread_local_rnode_value(self.p_rnode, bit_i)?; + let val = Ensemble::calculate_thread_local_rnode_value(self.p_external, bit_i)?; if let Some(val) = val.known_value() { res.set(bit_i, val).unwrap(); } else { diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index c4888017..cac2e69b 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -13,7 +13,7 @@ use awint::{ use crate::{ awi, - ensemble::{Ensemble, PRNode}, + ensemble::{Ensemble, PExternal}, epoch::get_current_epoch, }; @@ -34,7 +34,7 @@ use crate::{ /// current `Epoch` pub struct LazyAwi { opaque: dag::Awi, - p_rnode: PRNode, + p_external: PExternal, } // NOTE: when changing this also remember to change `LazyInlAwi` @@ -47,7 +47,7 @@ impl Drop for LazyAwi { .epoch_data .borrow_mut() .ensemble - .remove_rnode(self.p_rnode); + .remove_rnode(self.p_external); if res.is_err() { panic!("most likely, a `LazyAwi` created in one `Epoch` was dropped in another") } @@ -68,8 +68,12 @@ impl LazyAwi { &self.opaque } + pub fn p_external(&self) -> PExternal { + self.p_external + } + pub fn nzbw(&self) -> NonZeroUsize { - Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() + Ensemble::get_thread_local_rnode_nzbw(self.p_external).unwrap() } pub fn bw(&self) -> usize { @@ -78,14 +82,14 @@ impl LazyAwi { pub fn opaque(w: NonZeroUsize) -> Self { let opaque = dag::Awi::opaque(w); - let p_rnode = get_current_epoch() + let p_external = get_current_epoch() .unwrap() .epoch_data .borrow_mut() .ensemble .make_rnode_for_pstate(opaque.state()) .unwrap(); - Self { opaque, p_rnode } + Self { opaque, p_external } } // TODO it probably does need to be an extra `Awi` in the `Opaque` variant, @@ -122,7 +126,7 @@ impl LazyAwi { /// if this is being called after the corresponding Epoch is dropped and /// states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_rnode, rhs) + Ensemble::change_thread_local_rnode_value(self.p_external, rhs) } } @@ -167,7 +171,7 @@ forward_debug_fmt!(LazyAwi); #[derive(Clone)] pub struct LazyInlAwi { opaque: dag::InlAwi, - p_rnode: PRNode, + p_external: PExternal, } #[macro_export] @@ -193,7 +197,7 @@ impl Drop for LazyInlAwi { .epoch_data .borrow_mut() .ensemble - .remove_rnode(self.p_rnode); + .remove_rnode(self.p_external); if res.is_err() { panic!( "most likely, a `LazyInlAwi` created in one `Epoch` was dropped in another" @@ -212,12 +216,16 @@ impl Lineage for LazyInlAwi { } impl LazyInlAwi { + pub fn p_external(&self) -> PExternal { + self.p_external + } + fn internal_as_ref(&self) -> &dag::InlAwi { &self.opaque } pub fn nzbw(&self) -> NonZeroUsize { - Ensemble::get_thread_local_rnode_nzbw(self.p_rnode).unwrap() + Ensemble::get_thread_local_rnode_nzbw(self.p_external).unwrap() } pub fn bw(&self) -> usize { @@ -226,21 +234,21 @@ impl LazyInlAwi { pub fn opaque() -> Self { let opaque = dag::InlAwi::opaque(); - let p_rnode = get_current_epoch() + let p_external = get_current_epoch() .unwrap() .epoch_data .borrow_mut() .ensemble .make_rnode_for_pstate(opaque.state()) .unwrap(); - Self { opaque, p_rnode } + Self { opaque, p_external } } /// Retroactively-assigns by `rhs`. Returns `None` if bitwidths mismatch or /// if this is being called after the corresponding Epoch is dropped and /// states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_rnode, rhs) + Ensemble::change_thread_local_rnode_value(self.p_external, rhs) } } diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index 298bff18..523724eb 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -10,7 +10,7 @@ mod value; pub use lnode::{LNode, PLNode}; pub use optimize::{Optimizer, POpt}; -pub use rnode::{Notary, PRNode, RNode}; +pub use rnode::{Notary, PExternal, PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; pub use together::{Ensemble, Equiv, PBack, Referent}; diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 3ba51f2c..2458059d 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -239,7 +239,7 @@ impl Ensemble { }) } Referent::ThisRNode(p_rnode) => { - let rnode = self.notary.rnodes.get(*p_rnode).unwrap(); + let rnode = self.notary.rnodes().get_val(*p_rnode).unwrap(); let mut inx = u64::MAX; for (i, bit) in rnode.bits.iter().enumerate() { if *bit == Some(p_self) { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 831388b4..eb9419cf 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -425,7 +425,7 @@ impl Ensemble { tnode.p_driver = p_back_new; } Referent::ThisRNode(p_rnode) => { - let rnode = self.notary.rnodes.get_mut(p_rnode).unwrap(); + let rnode = self.notary.get_rnode_by_p_rnode_mut(p_rnode).unwrap(); let mut found = false; for bit in &mut rnode.bits { if let Some(bit) = bit { diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 003d592b..2b965184 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -1,7 +1,8 @@ use std::num::{NonZeroU128, NonZeroUsize}; use awint::awint_dag::{ - triple_arena::{ptr_struct, Arena}, + smallvec::{smallvec, SmallVec}, + triple_arena::{ptr_struct, OrdArena, Ptr}, EvalError, PState, }; @@ -22,34 +23,66 @@ ptr_struct!( /// after `State` pruning #[derive(Debug, Clone)] pub struct RNode { - pub bits: Vec>, + pub bits: SmallVec<[Option; 1]>, } impl RNode { pub fn new() -> Self { - Self { bits: vec![] } + Self { bits: smallvec![] } } } /// Used for managing external references #[derive(Debug, Clone)] pub struct Notary { - pub rnodes: Arena, + rnodes: OrdArena, + next_external: NonZeroU128, } impl Notary { pub fn new() -> Self { Self { - rnodes: Arena::new(), + rnodes: OrdArena::new(), + next_external: rand::random(), } } + + pub fn rnodes(&self) -> &OrdArena { + &self.rnodes + } + + pub fn insert_rnode(&mut self, rnode: RNode) -> (PRNode, PExternal) { + let p_external = PExternal::_from_raw(self.next_external, ()); + let (res, replaced) = self.rnodes.insert(p_external, rnode); + // there is an astronomically small chance this fails naturally when + // `PExternal`s from other `Notary`s are involved + assert!(replaced.is_none()); + // wrapping increment except that zero is skipped + self.next_external = NonZeroU128::new(self.next_external.get().wrapping_add(1)) + .unwrap_or(NonZeroU128::new(1).unwrap()); + (res, p_external) + } + + pub fn get_rnode(&self, p_external: PExternal) -> Option<&RNode> { + let p_rnode = self.rnodes.find_key(&p_external)?; + Some(self.rnodes.get_val(p_rnode).unwrap()) + } + + pub fn get_rnode_mut(&mut self, p_external: PExternal) -> Option<&mut RNode> { + let p_rnode = self.rnodes.find_key(&p_external)?; + Some(self.rnodes.get_val_mut(p_rnode).unwrap()) + } + + pub fn get_rnode_by_p_rnode_mut(&mut self, p_rnode: PRNode) -> Option<&mut RNode> { + self.rnodes.get_val_mut(p_rnode) + } } impl Ensemble { #[must_use] - pub fn make_rnode_for_pstate(&mut self, p_state: PState) -> Option { + pub fn make_rnode_for_pstate(&mut self, p_state: PState) -> Option { self.initialize_state_bits_if_needed(p_state)?; - let p_rnode = self.notary.rnodes.insert(RNode::new()); + let (p_rnode, p_external) = self.notary.insert_rnode(RNode::new()); let len = self.stator.states[p_state].p_self_bits.len(); for i in 0..len { let p_bit = self.stator.states[p_state].p_self_bits[i]; @@ -64,11 +97,12 @@ impl Ensemble { self.notary.rnodes[p_rnode].bits.push(None); } } - Some(p_rnode) + Some(p_external) } - pub fn remove_rnode(&mut self, p_rnode: PRNode) -> Result<(), EvalError> { - if let Some(rnode) = self.notary.rnodes.remove(p_rnode) { + pub fn remove_rnode(&mut self, p_external: PExternal) -> Result<(), EvalError> { + if let Some(p_rnode) = self.notary.rnodes.find_key(&p_external) { + let rnode = self.notary.rnodes.remove(p_rnode).unwrap().1; for p_back in rnode.bits { if let Some(p_back) = p_back { let referent = self.backrefs.remove_key(p_back).unwrap().0; @@ -81,11 +115,11 @@ impl Ensemble { } } - pub fn get_thread_local_rnode_nzbw(p_rnode: PRNode) -> Result { + pub fn get_thread_local_rnode_nzbw(p_external: PExternal) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { + if let Some(rnode) = ensemble.notary.get_rnode(p_external) { Ok(NonZeroUsize::new(rnode.bits.len()).unwrap()) } else { Err(EvalError::OtherStr( @@ -96,41 +130,42 @@ impl Ensemble { } pub fn change_thread_local_rnode_value( - p_rnode: PRNode, + p_external: PExternal, bits: &awi::Bits, ) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { + if let Some(p_rnode) = ensemble.notary.rnodes.find_key(&p_external) { + let rnode = ensemble.notary.rnodes.get_val(p_rnode).unwrap(); if rnode.bits.len() != bits.bw() { return Err(EvalError::WrongBitwidth); } + for bit_i in 0..bits.bw() { + let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; + if let Some(p_back) = p_back { + ensemble + .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) + .unwrap(); + } + } } else { return Err(EvalError::OtherStr( "could not find thread local `RNode`, probably a `LazyAwi` was used outside of \ the `Epoch` it was created in", )) } - for bit_i in 0..bits.bw() { - let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; - if let Some(p_back) = p_back { - ensemble - .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) - .unwrap(); - } - } Ok(()) } pub fn calculate_thread_local_rnode_value( - p_rnode: PRNode, + p_external: PExternal, bit_i: usize, ) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - let p_back = if let Some(rnode) = ensemble.notary.rnodes.get(p_rnode) { + let p_back = if let Some(rnode) = ensemble.notary.get_rnode(p_external) { if bit_i >= rnode.bits.len() { return Err(EvalError::OtherStr( "something went wrong with rnode bitwidth", diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 83fe8cd2..f79ed5d5 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -183,7 +183,7 @@ impl Ensemble { Referent::ThisStateBit(..) => false, Referent::Input(p_input) => !self.lnodes.contains(*p_input), Referent::LoopDriver(p_driver) => !self.tnodes.contains(*p_driver), - Referent::ThisRNode(p_rnode) => !self.notary.rnodes.contains(*p_rnode), + Referent::ThisRNode(p_rnode) => !self.notary.rnodes().contains(*p_rnode), }; if invalid { return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) @@ -234,12 +234,12 @@ impl Ensemble { ))) } } - for rnode in self.notary.rnodes.vals() { + for rnode in self.notary.rnodes().vals() { for p_back in &rnode.bits { if let Some(p_back) = p_back { if let Some(referent) = self.backrefs.get_key(*p_back) { if let Referent::ThisRNode(p_rnode) = referent { - if !self.notary.rnodes.contains(*p_rnode) { + if !self.notary.rnodes().contains(*p_rnode) { return Err(EvalError::OtherString(format!( "{rnode:?} backref {p_rnode} is invalid" ))) @@ -294,7 +294,7 @@ impl Ensemble { tnode.p_driver != p_back } Referent::ThisRNode(p_rnode) => { - let rnode = self.notary.rnodes.get(*p_rnode).unwrap(); + let rnode = self.notary.rnodes().get_val(*p_rnode).unwrap(); let mut found = false; for bit in &rnode.bits { if *bit == Some(p_back) { From 515e0ba6c7e382331f3c46adefb86e08bc0e3136 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 27 Dec 2023 14:40:14 -0600 Subject: [PATCH 028/119] Add recasting --- starlight/src/awi_structs/epoch.rs | 2 +- starlight/src/ensemble/lnode.rs | 16 ++++++- starlight/src/ensemble/optimize.rs | 21 ++++++--- starlight/src/ensemble/rnode.rs | 24 +++++++++- starlight/src/ensemble/state.rs | 11 +++++ starlight/src/ensemble/tnode.rs | 12 ++++- starlight/src/ensemble/together.rs | 74 ++++++++++++++++++++++++++++++ starlight/src/ensemble/value.rs | 9 ++++ 8 files changed, 159 insertions(+), 10 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 73d6fa41..f7f98ead 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -790,7 +790,7 @@ impl Epoch { let epoch_shared = self.check_current()?; Ensemble::lower_all(&epoch_shared)?; let mut lock = epoch_shared.epoch_data.borrow_mut(); - lock.ensemble.optimize_all(); + lock.ensemble.optimize_all()?; drop(lock); let _ = epoch_shared.assert_assertions(false); Ok(()) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 28c65b48..4306f9af 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -1,7 +1,11 @@ use std::num::NonZeroUsize; use awint::{ - awint_dag::{smallvec, PState}, + awint_dag::{ + smallvec, + triple_arena::{Recast, Recaster}, + PState, + }, Awi, Bits, }; use smallvec::SmallVec; @@ -22,6 +26,16 @@ pub struct LNode { pub lowered_from: Option, } +impl Recast for LNode { + fn recast>( + &mut self, + recaster: &R, + ) -> Result<(), ::Item> { + self.p_self.recast(recaster)?; + self.inp.as_mut_slice().recast(recaster) + } +} + impl LNode { pub fn new(p_self: PBack, lowered_from: Option) -> Self { Self { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index eb9419cf..f86a35d1 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -4,7 +4,7 @@ use awint::{ awint_dag::{ smallvec::SmallVec, triple_arena::{Advancer, Ptr}, - PState, + EvalError, PState, }, Awi, InlAwi, }; @@ -63,18 +63,26 @@ pub enum Optimization { #[derive(Debug, Clone)] pub struct Optimizer { - pub gas: u64, - pub optimizations: OrdArena, + optimizations: OrdArena, } impl Optimizer { pub fn new() -> Self { Self { - gas: 0, optimizations: OrdArena::new(), } } + /// Checks that there are no remaining optimizations, then shrinks + /// allocations + pub fn check_clear(&mut self) -> Result<(), EvalError> { + if !self.optimizations.is_empty() { + return Err(EvalError::OtherStr("optimizations need to be empty")); + } + self.optimizations.clear_and_shrink(); + Ok(()) + } + pub fn insert(&mut self, optimization: Optimization) { let _ = self.optimizations.insert(optimization, ()); } @@ -311,8 +319,8 @@ impl Ensemble { self.backrefs.remove_key(tnode.p_driver).unwrap(); } - /// Also removes all states - pub fn optimize_all(&mut self) { + /// Removes all states, optimizes, and shrinks allocations + pub fn optimize_all(&mut self) -> Result<(), EvalError> { self.force_remove_all_states().unwrap(); // need to preinvestigate everything before starting a priority loop let mut adv = self.backrefs.advancer(); @@ -324,6 +332,7 @@ impl Ensemble { while let Some(p_optimization) = self.optimizer.optimizations.min() { self.optimize(p_optimization); } + self.recast_all_internal_ptrs() } pub fn optimize(&mut self, p_optimization: POpt) { diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 2b965184..eb9e5943 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -2,7 +2,7 @@ use std::num::{NonZeroU128, NonZeroUsize}; use awint::awint_dag::{ smallvec::{smallvec, SmallVec}, - triple_arena::{ptr_struct, OrdArena, Ptr}, + triple_arena::{ptr_struct, Arena, OrdArena, Ptr, Recast, Recaster}, EvalError, PState, }; @@ -26,6 +26,15 @@ pub struct RNode { pub bits: SmallVec<[Option; 1]>, } +impl Recast for RNode { + fn recast>( + &mut self, + recaster: &R, + ) -> Result<(), ::Item> { + self.bits.as_mut_slice().recast(recaster) + } +} + impl RNode { pub fn new() -> Self { Self { bits: smallvec![] } @@ -39,6 +48,15 @@ pub struct Notary { next_external: NonZeroU128, } +impl Recast for Notary { + fn recast>( + &mut self, + recaster: &R, + ) -> Result<(), ::Item> { + self.rnodes.recast(recaster) + } +} + impl Notary { pub fn new() -> Self { Self { @@ -47,6 +65,10 @@ impl Notary { } } + pub fn recast_p_rnode(&mut self) -> Arena { + self.rnodes.compress_and_shrink_recaster() + } + pub fn rnodes(&self) -> &OrdArena { &self.rnodes } diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 64e3bc07..290efc91 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -82,6 +82,17 @@ impl Stator { states_to_lower: vec![], } } + + /// Checks that there are no remaining states, then shrinks allocations + pub fn check_clear(&mut self) -> Result<(), EvalError> { + if !self.states.is_empty() { + return Err(EvalError::OtherStr("states need to be empty")); + } + self.states.clear_and_shrink(); + self.states_to_lower.clear(); + self.states_to_lower.shrink_to_fit(); + Ok(()) + } } impl Ensemble { diff --git a/starlight/src/ensemble/tnode.rs b/starlight/src/ensemble/tnode.rs index c5246685..b221bc7d 100644 --- a/starlight/src/ensemble/tnode.rs +++ b/starlight/src/ensemble/tnode.rs @@ -1,4 +1,4 @@ -use awint::awint_dag::triple_arena::ptr_struct; +use awint::awint_dag::triple_arena::{ptr_struct, Recast, Recaster}; use crate::ensemble::PBack; @@ -12,6 +12,16 @@ pub struct TNode { pub p_driver: PBack, } +impl Recast for TNode { + fn recast>( + &mut self, + recaster: &R, + ) -> Result<(), ::Item> { + self.p_self.recast(recaster)?; + self.p_driver.recast(recaster) + } +} + impl TNode { pub fn new(p_self: PBack, p_driver: PBack) -> Self { Self { p_self, p_driver } diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index f79ed5d5..f68ef19e 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -3,6 +3,7 @@ use std::num::{NonZeroU64, NonZeroUsize}; use awint::{ awint_dag::{ smallvec::{smallvec, SmallVec}, + triple_arena::{Recast, Recaster}, EvalError, Location, Op, PState, }, Awi, Bits, @@ -29,6 +30,15 @@ pub struct Equiv { pub request_visit: NonZeroU64, } +impl Recast for Equiv { + fn recast>( + &mut self, + recaster: &R, + ) -> Result<(), ::Item> { + self.p_self_equiv.recast(recaster) + } +} + impl Equiv { pub fn new(p_self_equiv: PBack, val: Value) -> Self { Self { @@ -58,6 +68,12 @@ pub enum Referent { ThisRNode(PRNode), } +impl Recast for Referent { + fn recast>(&mut self, _: &R) -> Result<(), ::Item> { + Ok(()) + } +} + #[derive(Debug, Clone)] pub struct Ensemble { pub backrefs: SurjectArena, @@ -84,6 +100,64 @@ impl Ensemble { } } + /// Compresses and shrinks all internal `Ptr`s. Returns an error if the + /// optimizer, evaluator, or stator are not empty. + pub fn recast_all_internal_ptrs(&mut self) -> Result<(), EvalError> { + self.optimizer.check_clear()?; + self.evaluator.check_clear()?; + self.stator.check_clear()?; + + let p_lnode_recaster = self.lnodes.compress_and_shrink_recaster(); + let p_tnode_recaster = self.tnodes.compress_and_shrink_recaster(); + let p_rnode_recaster = self.notary.recast_p_rnode(); + for referent in self.backrefs.keys_mut() { + match referent { + Referent::ThisEquiv => (), + Referent::ThisLNode(p_lnode) => { + if p_lnode.recast(&p_lnode_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PLNode")); + } + } + Referent::ThisTNode(p_tnode) => { + if p_tnode.recast(&p_tnode_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PTNode")); + } + } + Referent::ThisStateBit(..) => unreachable!(), + Referent::Input(p_lnode) => { + if p_lnode.recast(&p_lnode_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PLNode")); + } + } + Referent::LoopDriver(p_tnode) => { + if p_tnode.recast(&p_tnode_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PTNode")); + } + } + Referent::ThisRNode(p_rnode) => { + if p_rnode.recast(&p_rnode_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PRNode")); + } + } + } + } + + let p_back_recaster = self.backrefs.compress_and_shrink_recaster(); + if self.backrefs.recast(&p_back_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PBack")); + } + if self.notary.recast(&p_back_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PBack")); + } + if self.lnodes.recast(&p_back_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PBack")); + } + if self.tnodes.recast(&p_back_recaster).is_err() { + return Err(EvalError::OtherStr("recast error with a PBack")); + } + Ok(()) + } + pub fn verify_integrity(&self) -> Result<(), EvalError> { // return errors in order of most likely to be root cause diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 76210b35..8168602b 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -135,6 +135,15 @@ impl Evaluator { } } + /// Checks that there are no remaining evaluations, then shrinks allocations + pub fn check_clear(&mut self) -> Result<(), EvalError> { + if !self.evaluations.is_empty() { + return Err(EvalError::OtherStr("evaluations need to be empty")); + } + self.evaluations.clear_and_shrink(); + Ok(()) + } + pub fn change_visit_gen(&self) -> NonZeroU64 { self.change_visit_gen } From 34e3f3872156f0b4c1f60414a4572dec3846349d Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 27 Dec 2023 17:30:18 -0600 Subject: [PATCH 029/119] introduce `LNodeKind` --- CHANGELOG.md | 2 + starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/debug.rs | 34 +++-- starlight/src/ensemble/lnode.rs | 66 +++++++-- starlight/src/ensemble/optimize.rs | 208 +++++++++++++++-------------- starlight/src/ensemble/together.rs | 84 +++++++----- starlight/src/ensemble/value.rs | 198 +++++++++++++-------------- starlight/src/route/cedge.rs | 24 ++++ testcrate/tests/basic.rs | 13 +- 9 files changed, 374 insertions(+), 257 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25173c97..ccc12118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ ### Changes - merged `Epoch::assert_assertions` and `Epoch::assert_assertions_strict` - many fixes for `Epoch` behavior +- `LNode`s now have a `LNodeKind` ### Additions - Added `Epoch::suspend` +- Optimization now compresses allocations ## [0.2.0] - 2023-12-08 ### Crate diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index 523724eb..e4357ca3 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -8,7 +8,7 @@ mod tnode; mod together; mod value; -pub use lnode::{LNode, PLNode}; +pub use lnode::{LNode, LNodeKind, PLNode}; pub use optimize::{Optimizer, POpt}; pub use rnode::{Notary, PExternal, PRNode, RNode}; pub use state::{State, Stator}; diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 2458059d..b0a60efe 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -6,7 +6,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, Equiv, LNode, PBack, PRNode, PTNode, Referent, State}, + ensemble::{Ensemble, Equiv, LNode, LNodeKind, PBack, PRNode, PTNode, Referent, State}, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, Epoch, @@ -120,16 +120,28 @@ impl DebugNodeTrait for NodeKind { }, }, NodeKind::LNode(lnode) => DebugNode { - sources: lnode - .inp - .iter() - .enumerate() - .map(|(i, p)| (*p, format!("{i}"))) - .collect(), + sources: { + match &lnode.kind { + LNodeKind::Copy(inp) => vec![(*inp, "copy".to_owned())], + LNodeKind::Lut(inp, _) => inp + .iter() + .enumerate() + .map(|(i, p)| (*p, format!("{i}"))) + .collect(), + LNodeKind::DynamicLut(inp, table) => inp + .iter() + .enumerate() + .map(|(i, p)| (*p, format!("i{i}"))) + .chain(table.iter().enumerate().map(|(i, p)| (*p, format!("t{i}")))) + .collect(), + } + }, center: { let mut v = vec![format!("{:?}", p_this)]; - if let Some(ref lut) = lnode.lut { - v.push(format!("{:?} ", lut)); + match &lnode.kind { + LNodeKind::Copy(_) => (), + LNodeKind::Lut(_, lut) => v.push(format!("{:?} ", lut)), + LNodeKind::DynamicLut(..) => (), } if let Some(lowered_from) = lnode.lowered_from { v.push(format!("{:?}", lowered_from)); @@ -219,12 +231,12 @@ impl Ensemble { Referent::ThisLNode(p_lnode) => { let mut lnode = self.lnodes.get(*p_lnode).unwrap().clone(); // forward to the `PBack`s of LNodes - for inp in &mut lnode.inp { + lnode.inputs_mut(|inp| { if let Referent::Input(_) = self.backrefs.get_key(*inp).unwrap() { let p_input = self.backrefs.get_val(*inp).unwrap().p_self_equiv; *inp = p_input; } - } + }); NodeKind::LNode(lnode) } Referent::ThisTNode(p_tnode) => { diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 4306f9af..151a0c93 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -15,14 +15,22 @@ use crate::{ensemble::PBack, triple_arena::ptr_struct}; // We use this because our algorithms depend on generation counters ptr_struct!(PLNode); +#[derive(Debug, Clone)] +pub enum LNodeKind { + /// Copy a single input bit + Copy(PBack), + /// Static Lookup Table that outputs one bit, the `Awi` is the table and the + /// `SmallVec` is the inputs + Lut(SmallVec<[PBack; 4]>, Awi), + /// A Dynamic Lookup Table with the inputs and then the `Vec` is the table + DynamicLut(SmallVec<[PBack; 4]>, Vec), +} + /// A lookup table node #[derive(Debug, Clone)] pub struct LNode { pub p_self: PBack, - /// Inputs - pub inp: SmallVec<[PBack; 4]>, - /// Lookup Table that outputs one bit - pub lut: Option, + pub kind: LNodeKind, pub lowered_from: Option, } @@ -32,20 +40,62 @@ impl Recast for LNode { recaster: &R, ) -> Result<(), ::Item> { self.p_self.recast(recaster)?; - self.inp.as_mut_slice().recast(recaster) + self.inputs_mut(|inp| { + inp.recast(recaster).unwrap(); + }); + Ok(()) } } impl LNode { - pub fn new(p_self: PBack, lowered_from: Option) -> Self { + pub fn new(p_self: PBack, kind: LNodeKind, lowered_from: Option) -> Self { Self { p_self, - inp: SmallVec::new(), - lut: None, + kind, lowered_from, } } + /// Calls `f` for each `PBack` in the inputs of this `LNode` + pub fn inputs(&self, mut f: F) { + match &self.kind { + LNodeKind::Copy(inp) => f(*inp), + LNodeKind::Lut(inp, _) => { + for inp in inp.iter() { + f(*inp); + } + } + LNodeKind::DynamicLut(inp, table) => { + for inp in inp.iter() { + f(*inp); + } + for inp in table.iter() { + f(*inp); + } + } + } + } + + /// Calls `f` for each `&mut PBack` in the inputs of this `LNode` + pub fn inputs_mut(&mut self, mut f: F) { + match &mut self.kind { + LNodeKind::Copy(inp) => f(inp), + LNodeKind::Lut(inp, _) => { + for inp in inp.iter_mut() { + f(inp); + } + } + LNodeKind::DynamicLut(inp, table) => { + for inp in inp.iter_mut() { + f(inp); + } + for inp in table.iter_mut() { + f(inp); + } + } + } + } + /// Reduce a LUT in half by saving entries indexed by setting the `i`th /// input bit to `bit` pub fn reduce_lut(lut: &Bits, i: usize, bit: bool) -> Awi { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index f86a35d1..b07d3f00 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -10,7 +10,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, LNode, PBack, PLNode, PTNode, Referent, Value}, + ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, SmallMap, }; @@ -94,111 +94,113 @@ impl Ensemble { /// needs to be run by the caller). pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> bool { let lnode = self.lnodes.get_mut(p_lnode).unwrap(); - if let Some(original_lut) = &lnode.lut { - let mut lut = original_lut.clone(); - // acquire LUT inputs, for every constant input reduce the LUT - let len = usize::from(u8::try_from(lnode.inp.len()).unwrap()); - for i in (0..len).rev() { - let p_inp = lnode.inp[i]; - let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - // we will be removing the input, mark it to be investigated + match &mut lnode.kind { + LNodeKind::Copy(inp) => { + // wire propogation + let input_equiv = self.backrefs.get_val_mut(*inp).unwrap(); + if let Value::Const(val) = input_equiv.val { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::Const(val); self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(p_inp).unwrap(); - lnode.inp.remove(i); - - lut = LNode::reduce_lut(&lut, i, val); + .insert(Optimization::ConstifyEquiv(equiv.p_self_equiv)); + true + } else { + self.optimizer + .insert(Optimization::ForwardEquiv(lnode.p_self)); + false } } - - // check for duplicate inputs of the same source - 'outer: loop { - // we have to reset every time because the removals can mess up any range of - // indexes - let mut set = SmallMap::new(); - for i in 0..lnode.inp.len() { - let p_inp = lnode.inp[i]; + LNodeKind::Lut(inp, original_lut) => { + let mut lut = original_lut.clone(); + // acquire LUT inputs, for every constant input reduce the LUT + let len = usize::from(u8::try_from(inp.len()).unwrap()); + for i in (0..len).rev() { + let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); - match set.insert(equiv.p_self_equiv.inx(), i) { - Ok(()) => (), - Err(j) => { - let next_bw = lut.bw() / 2; - let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); - let mut to = 0; - for k in 0..lut.bw() { - let inx = InlAwi::from_usize(k); - if inx.get(i).unwrap() == inx.get(j).unwrap() { - next_lut.set(to, lut.get(k).unwrap()).unwrap(); - to += 1; + if let Value::Const(val) = equiv.val { + // we will be removing the input, mark it to be investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); + inp.remove(i); + + lut = LNode::reduce_lut(&lut, i, val); + } + } + + // check for duplicate inputs of the same source + 'outer: loop { + // we have to reset every time because the removals can mess up any range of + // indexes + let mut set = SmallMap::new(); + for i in 0..inp.len() { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + match set.insert(equiv.p_self_equiv.inx(), i) { + Ok(()) => (), + Err(j) => { + let next_bw = lut.bw() / 2; + let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); + let mut to = 0; + for k in 0..lut.bw() { + let inx = InlAwi::from_usize(k); + if inx.get(i).unwrap() == inx.get(j).unwrap() { + next_lut.set(to, lut.get(k).unwrap()).unwrap(); + to += 1; + } } + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(inp[j]).unwrap(); + inp.remove(j); + lut = next_lut; + continue 'outer } - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(lnode.inp[j]).unwrap(); - lnode.inp.remove(j); - lut = next_lut; - continue 'outer } } + break } - break - } - // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing - let len = lnode.inp.len(); - for i in (0..len).rev() { - if lut.bw() > 1 { - if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { - // independent of the `i`th bit - lut = reduced; - let p_inp = lnode.inp.remove(i); - let equiv = self.backrefs.get_val(p_inp).unwrap(); - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(p_inp).unwrap(); + // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing + let len = inp.len(); + for i in (0..len).rev() { + if lut.bw() > 1 { + if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { + // independent of the `i`th bit + lut = reduced; + let p_inp = inp.remove(i); + let equiv = self.backrefs.get_val(p_inp).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); + } } } - } - // sort inputs so that `LNode`s can be compared later - // TODO? + // sort inputs so that `LNode`s can be compared later + // TODO? - // input independence automatically reduces all zeros and all ones LUTs, so just - // need to check if the LUT is one bit for constant generation - if lut.bw() == 1 { - let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); - equiv.val = Value::Const(lut.to_bool()); - // fix the `lut` to its new state, do this even if we are doing the constant - // optimization - lnode.lut = Some(lut); - true - } else if (lut.bw() == 2) && lut.get(1).unwrap() { - // the only `lut.bw() == 2` cases that survive independence removal is identity - // and inversion. If it is identity, register this for forwarding - lnode.lut = None; - self.optimizer - .insert(Optimization::ForwardEquiv(lnode.p_self)); - false - } else { - lnode.lut = Some(lut); - false - } - } else if lnode.inp.len() == 1 { - // wire propogation - let input_equiv = self.backrefs.get_val_mut(lnode.inp[0]).unwrap(); - if let Value::Const(val) = input_equiv.val { - let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); - equiv.val = Value::Const(val); - self.optimizer - .insert(Optimization::ConstifyEquiv(equiv.p_self_equiv)); - true - } else { - self.optimizer - .insert(Optimization::ForwardEquiv(lnode.p_self)); - false + // input independence automatically reduces all zeros and all ones LUTs, so just + // need to check if the LUT is one bit for constant generation + if lut.bw() == 1 { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::Const(lut.to_bool()); + // fix the `lut` to its new state, do this even if we are doing the constant + // optimization + *original_lut = lut; + true + } else if (lut.bw() == 2) && lut.get(1).unwrap() { + // the only `lut.bw() == 2` cases that survive independence removal is identity + // and inversion. If it is identity, register this for forwarding + lnode.kind = LNodeKind::Copy(inp[0]); + self.optimizer + .insert(Optimization::ForwardEquiv(lnode.p_self)); + false + } else { + *original_lut = lut; + false + } } - } else { - false + LNodeKind::DynamicLut(..) => todo!(), } } @@ -300,12 +302,12 @@ impl Ensemble { /// `Advancer`s. pub fn remove_lnode_not_p_self(&mut self, p_lnode: PLNode) { let lnode = self.lnodes.remove(p_lnode).unwrap(); - for inp in lnode.inp { + lnode.inputs(|inp| { let p_equiv = self.backrefs.get_val(inp).unwrap().p_self_equiv; self.optimizer .insert(Optimization::InvestigateUsed(p_equiv)); self.backrefs.remove_key(inp).unwrap(); - } + }); } /// Does not perform the final step @@ -376,11 +378,14 @@ impl Ensemble { let p_source = if let Some(referent) = self.backrefs.get_key(p_ident) { if let Referent::ThisLNode(p_lnode) = referent { let lnode = &self.lnodes[p_lnode]; - assert_eq!(lnode.inp.len(), 1); - // do not use directly, use the `p_self_equiv` since this backref will be - // removed when `p_ident` is process in the loop - let p_back = lnode.inp[0]; - self.backrefs.get_val(p_back).unwrap().p_self_equiv + if let LNodeKind::Copy(inp) = lnode.kind { + // do not use directly, use the `p_self_equiv` since this backref will + // be removed when `p_ident` is process in + // the loop + self.backrefs.get_val(inp).unwrap().p_self_equiv + } else { + unreachable!() + } } else { unreachable!() } @@ -411,7 +416,7 @@ impl Ensemble { Referent::Input(p_input) => { let lnode = self.lnodes.get_mut(p_input).unwrap(); let mut found = false; - for inp in &mut lnode.inp { + lnode.inputs_mut(|inp| { if *inp == p_back { let p_back_new = self .backrefs @@ -419,9 +424,8 @@ impl Ensemble { .unwrap(); *inp = p_back_new; found = true; - break } - } + }); assert!(found); } Referent::LoopDriver(p_driver) => { diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index f68ef19e..6246995d 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -11,8 +11,8 @@ use awint::{ use crate::{ ensemble::{ - value::Evaluator, LNode, Notary, Optimizer, PLNode, PRNode, PTNode, State, Stator, TNode, - Value, + value::Evaluator, LNode, LNodeKind, Notary, Optimizer, PLNode, PRNode, PTNode, State, + Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, }; @@ -266,26 +266,28 @@ impl Ensemble { // other kinds of validity for p_lnode in self.lnodes.ptrs() { let lnode = self.lnodes.get(p_lnode).unwrap(); - for p_input in &lnode.inp { - if let Some(referent) = self.backrefs.get_key(*p_input) { + let mut res = Ok(()); + lnode.inputs(|p_input| { + if let Some(referent) = self.backrefs.get_key(p_input) { if let Referent::Input(referent) = referent { if !self.lnodes.contains(*referent) { - return Err(EvalError::OtherString(format!( + res = Err(EvalError::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} referrent {referent} is \ invalid" - ))) + ))); } } else { - return Err(EvalError::OtherString(format!( + res = Err(EvalError::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} has incorrect referrent" - ))) + ))); } } else { - return Err(EvalError::OtherString(format!( + res = Err(EvalError::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} is invalid" - ))) + ))); } - } + }); + res?; } for p_tnode in self.tnodes.ptrs() { let tnode = self.tnodes.get(p_tnode).unwrap(); @@ -355,12 +357,11 @@ impl Ensemble { Referent::Input(p_input) => { let lnode = self.lnodes.get(*p_input).unwrap(); let mut found = false; - for p_back1 in &lnode.inp { - if *p_back1 == p_back { + lnode.inputs(|p_back1| { + if p_back1 == p_back { found = true; - break } - } + }); !found } Referent::LoopDriver(p_tnode) => { @@ -387,24 +388,38 @@ impl Ensemble { } // non-pointer invariants for lnode in self.lnodes.vals() { - if let Some(ref lut) = lnode.lut { - if lnode.inp.is_empty() { - return Err(EvalError::OtherStr("no inputs for lookup table")) - } - if !lut.bw().is_power_of_two() { - return Err(EvalError::OtherStr( - "lookup table is not a power of two in bitwidth", - )) + match &lnode.kind { + LNodeKind::Copy(_) => (), + LNodeKind::Lut(inp, lut) => { + if inp.is_empty() { + return Err(EvalError::OtherStr("no inputs for lookup table")) + } + if !lut.bw().is_power_of_two() { + return Err(EvalError::OtherStr( + "lookup table is not a power of two in bitwidth", + )) + } + if (lut.bw().trailing_zeros() as usize) != inp.len() { + return Err(EvalError::OtherStr( + "number of inputs does not correspond to lookup table size", + )) + } } - if (lut.bw().trailing_zeros() as usize) != lnode.inp.len() { - return Err(EvalError::OtherStr( - "number of inputs does not correspond to lookup table size", - )) + LNodeKind::DynamicLut(inp, lut) => { + if inp.is_empty() { + return Err(EvalError::OtherStr("no inputs for lookup table")) + } + if !lut.len().is_power_of_two() { + return Err(EvalError::OtherStr( + "lookup table is not a power of two in bitwidth", + )) + } + if (lut.len().trailing_zeros() as usize) != inp.len() { + return Err(EvalError::OtherStr( + "number of inputs does not correspond to lookup table size", + )) + } } - } else if lnode.inp.len() != 1 { - return Err(EvalError::OtherStr( - "`LNode` with no lookup table has more or less than one input", - )) } } // state reference counts @@ -522,16 +537,15 @@ impl Ensemble { .backrefs .insert_key(p_equiv, Referent::ThisLNode(p_lnode)) .unwrap(); - let mut lnode = LNode::new(p_self, lowered_from); - lnode.lut = Some(Awi::from(table)); + let mut inp = smallvec![]; for p_inx in p_inxs { let p_back = self .backrefs .insert_key(p_inx.unwrap(), Referent::Input(p_lnode)) .unwrap(); - lnode.inp.push(p_back); + inp.push(p_back); } - lnode + LNode::new(p_self, LNodeKind::Lut(inp, Awi::from(table)), lowered_from) }); Some(p_equiv) } diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 8168602b..a972a3d8 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -9,7 +9,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, LNode, PBack, PLNode, PTNode, Referent}, + ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent}, epoch::EpochShared, }; @@ -177,116 +177,118 @@ impl Ensemble { // read current inputs let lnode = self.lnodes.get(p_lnode).unwrap(); let p_equiv = self.backrefs.get_val(lnode.p_self).unwrap().p_self_equiv; - if let Some(original_lut) = &lnode.lut { - let len = u8::try_from(lnode.inp.len()).unwrap(); - let len = usize::from(len); - // the nominal value of the inputs - let mut inp = Awi::zero(NonZeroUsize::new(len).unwrap()); - // corresponding bits are set if the input is either a const value or is - // already evaluated - let mut fixed = inp.clone(); - // corresponding bits are set if the input is `Value::Unknown` - let mut unknown = inp.clone(); - for i in 0..len { - let p_inp = lnode.inp[i]; - let equiv = self.backrefs.get_val(p_inp).unwrap(); + match &lnode.kind { + LNodeKind::Copy(p_inp) => { + let equiv = self.backrefs.get_val(*p_inp).unwrap(); if let Value::Const(val) = equiv.val { - fixed.set(i, true).unwrap(); - inp.set(i, val).unwrap(); + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Const(val), + })); } else if equiv.change_visit == self.evaluator.change_visit_gen() { - fixed.set(i, true).unwrap(); - if let Some(val) = equiv.val.known_value() { - inp.set(i, val).unwrap() - } else { - unknown.set(i, true).unwrap(); - } - } - } - let mut lut = original_lut.clone(); - // if fixed and unknown bits can influence the value, - // then the value of this equivalence can also be fixed - // to unknown - for i in 0..len { - if fixed.get(i).unwrap() - && unknown.get(i).unwrap() - && LNode::reduce_independent_lut(&lut, i).is_none() - { + // fixed self.evaluator.insert(Eval::Change(Change { depth, p_equiv, - value: Value::Unknown, + value: equiv.val, })); - return vec![]; - } - } - // reduce the LUT based on fixed and known bits - for i in (0..len).rev() { - if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { - lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); - } - } - // if the LUT is all ones or all zeros, we can know that any unfixed or - // unknown changes will be unable to affect the - // output - if lut.is_zero() { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Dynam(false), - })); - return vec![]; - } else if lut.is_umax() { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Dynam(true), - })); - return vec![]; - } - // TODO prioritize bits that could lead to number_a optimization - /*let mut skip = 0; - for i in 0..len { - if fixed.get(i).unwrap() && !unknown.get(i).unwrap() { - skip += 1; - } else if unknown.get(i).unwrap() { - // assume unchanging - lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); - // - } else {} - }*/ - for i in (0..len).rev() { - if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { + } else { res.push(RequestLNode { depth: depth - 1, number_a: 0, - p_back_lnode: lnode.inp[i], + p_back_lnode: *p_inp, }); } } - } else { - // LNode without LUT - let p_inp = lnode.inp[0]; - let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Const(val), - })); - } else if equiv.change_visit == self.evaluator.change_visit_gen() { - // fixed - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: equiv.val, - })); - } else { - res.push(RequestLNode { - depth: depth - 1, - number_a: 0, - p_back_lnode: lnode.inp[0], - }); + LNodeKind::Lut(inp, original_lut) => { + let len = u8::try_from(inp.len()).unwrap(); + let len = usize::from(len); + // the nominal value of the inputs + let mut inp_val = Awi::zero(NonZeroUsize::new(len).unwrap()); + // corresponding bits are set if the input is either a const value or is + // already evaluated + let mut fixed = inp_val.clone(); + // corresponding bits are set if the input is `Value::Unknown` + let mut unknown = inp_val.clone(); + for i in 0..len { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + if let Value::Const(val) = equiv.val { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap(); + } else if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + if let Some(val) = equiv.val.known_value() { + inp_val.set(i, val).unwrap() + } else { + unknown.set(i, true).unwrap(); + } + } + } + let mut lut = original_lut.clone(); + // if fixed and unknown bits can influence the value, + // then the value of this equivalence can also be fixed + // to unknown + for i in 0..len { + if fixed.get(i).unwrap() + && unknown.get(i).unwrap() + && LNode::reduce_independent_lut(&lut, i).is_none() + { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; + } + } + // reduce the LUT based on fixed and known bits + for i in (0..len).rev() { + if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { + lut = LNode::reduce_lut(&lut, i, inp_val.get(i).unwrap()); + } + } + // if the LUT is all ones or all zeros, we can know that any unfixed or + // unknown changes will be unable to affect the + // output + if lut.is_zero() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(false), + })); + return vec![]; + } else if lut.is_umax() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(true), + })); + return vec![]; + } + // TODO prioritize bits that could lead to number_a optimization + /*let mut skip = 0; + for i in 0..len { + if fixed.get(i).unwrap() && !unknown.get(i).unwrap() { + skip += 1; + } else if unknown.get(i).unwrap() { + // assume unchanging + lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); + // + } else {} + }*/ + for i in (0..len).rev() { + if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { + res.push(RequestLNode { + depth: depth - 1, + number_a: 0, + p_back_lnode: inp[i], + }); + } + } } + LNodeKind::DynamicLut(..) => todo!(), } res } diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 1c47bf11..0b25f0cd 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,3 +1,5 @@ +use awint::awint_dag::smallvec::smallvec; + use super::{channel::Referent, Channeler}; use crate::{ awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct, Epoch, @@ -70,6 +72,25 @@ impl CEdge { } impl Channeler { + /// Given the `incidences` (which should point to unique `ThisCNode`s), this + /// will manage the backrefs + pub fn make_cedge(&mut self, incidences: &[PBack], programmability: Programmability) -> PCEdge { + self.cedges.insert_with(|p_self| { + let mut actual_incidences = smallvec![]; + for (i, incidence) in incidences.iter().enumerate() { + actual_incidences.push( + self.cnodes + .insert_key(*incidence, Referent::CEdgeIncidence(p_self, i)) + .unwrap(), + ); + } + CEdge { + incidences: actual_incidences, + programmability, + } + }) + } + /// Assumes that `epoch` has been optimized pub fn from_epoch(epoch: &Epoch) -> Self { let mut channeler = Self::new(); @@ -85,6 +106,9 @@ impl Channeler { } // add `CEdge`s according to `LNode`s + for lnode in ensemble.lnodes.vals() { + //if let Some(lnode.lut + } }); channeler diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 40274dfd..d2adf531 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -1,6 +1,7 @@ use starlight::{ awi, dag::{self, *}, + ensemble::LNodeKind, Epoch, EvalAwi, LazyAwi, StarRng, }; @@ -149,8 +150,16 @@ fn luts() { // assert that there is at most one LNode with constant inputs optimized away let mut lnodes = ensemble.lnodes.vals(); if let Some(lnode) = lnodes.next() { - inp_bits += lnode.inp.len(); - assert!(lnode.inp.len() <= opaque_set.count_ones()); + match &lnode.kind { + LNodeKind::Copy(_) => { + inp_bits += 1; + } + LNodeKind::Lut(inp, _) => { + inp_bits += inp.len(); + assert!(inp.len() <= opaque_set.count_ones()); + } + LNodeKind::DynamicLut(..) => unreachable!(), + } assert!(lnodes.next().is_none()); } assert!(lnodes.next().is_none()); From 5466a097460ddf846785ef42d9c44c3848dfc5fe Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 28 Dec 2023 12:23:24 -0600 Subject: [PATCH 030/119] working on channeling --- starlight/src/awi_structs/epoch.rs | 9 ++++++ starlight/src/route.rs | 2 +- starlight/src/route/cedge.rs | 47 ++++++++++++++++++++---------- starlight/src/route/channel.rs | 25 +++++++++++++--- starlight/src/route/router.rs | 44 ++++++++++++++++++++++------ 5 files changed, 97 insertions(+), 30 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index f7f98ead..08b6fd65 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -683,6 +683,15 @@ impl SuspendedEpoch { self.inner.is_current = true; Epoch { inner: self.inner } } + + /// Returns the `EpochShared` of `self` + fn shared(&self) -> &EpochShared { + &self.inner.epoch_shared + } + + pub fn ensemble O>(&self, f: F) -> O { + self.shared().ensemble(f) + } } impl Epoch { diff --git a/starlight/src/route.rs b/starlight/src/route.rs index 834d1235..952727da 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -7,7 +7,7 @@ mod path; mod region_adv; mod router; -pub use cedge::{CEdge, PCEdge}; +pub use cedge::{Behavior, BulkBehavior, CEdge, Instruction, PCEdge, Programmability}; pub use channel::{Channeler, PBack}; pub use cnode::CNode; pub use path::{HyperPath, PHyperPath, Path}; diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 0b25f0cd..44f414a4 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,8 +1,15 @@ -use awint::awint_dag::smallvec::smallvec; +use awint::{ + awint_dag::{smallvec::smallvec, EvalError, EvalResult}, + Awi, +}; -use super::{channel::Referent, Channeler}; use crate::{ - awint_dag::smallvec::SmallVec, ensemble, route::PBack, triple_arena::ptr_struct, Epoch, + awint_dag::smallvec::SmallVec, + ensemble, + ensemble::LNodeKind, + route::{channel::Referent, Channeler, PBack}, + triple_arena::ptr_struct, + Epoch, }; ptr_struct!(PCEdge); @@ -11,25 +18,26 @@ ptr_struct!(PCEdge); #[derive(Debug, Clone)] pub struct BulkBehavior { /// The number of bits that can enter this channel - channel_entry_width: usize, + pub channel_entry_width: usize, /// The number of bits that can exit this channel - channel_exit_width: usize, + pub channel_exit_width: usize, /// For now, we just add up the number of LUT bits in the channel - lut_bits: usize, + pub lut_bits: usize, } #[derive(Debug, Clone)] pub enum Behavior { + /// Nothing can happen between nodes, used for connecting top level nodes + /// that have no connection to each other + Noop, /// Routes the bit from `source` to `sink` RouteBit, + StaticLut(Awi), /// Can behave as an arbitrary lookup table outputting a bit and taking the /// input bits. - ArbitraryLut(PBack, SmallVec<[PBack; 4]>), + ArbitraryLut(usize), /// Bulk behavior Bulk(BulkBehavior), - /// Nothing can happen between nodes, used for connecting top level nodes - /// that have no connection to each other - Noop, } /// A description of bits to set in order to achieve some desired edge behavior. @@ -43,9 +51,9 @@ pub struct Instruction { #[derive(Debug, Clone)] pub struct Programmability { /// The behavior that can be programmed into this edge - behavior: Behavior, + pub behavior: Behavior, /// The instruction required to get the desired behavior - instruction: Instruction, + pub instruction: Instruction, } /// An edge between channels @@ -92,7 +100,7 @@ impl Channeler { } /// Assumes that `epoch` has been optimized - pub fn from_epoch(epoch: &Epoch) -> Self { + pub fn from_epoch(epoch: &Epoch) -> Result { let mut channeler = Self::new(); epoch.ensemble(|ensemble| { @@ -107,10 +115,17 @@ impl Channeler { // add `CEdge`s according to `LNode`s for lnode in ensemble.lnodes.vals() { - //if let Some(lnode.lut + match &lnode.kind { + LNodeKind::Copy(_) => { + return Err(EvalError::OtherStr("the epoch was not optimized")) + } + LNodeKind::Lut(inp, awi) => {} + LNodeKind::DynamicLut(..) => todo!(), + } } - }); + Ok(()) + })?; - channeler + Ok(channeler) } } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index d04ca81f..98946743 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -7,7 +7,7 @@ use awint::awint_dag::{ use crate::{ awint_dag::smallvec::SmallVec, ensemble, - route::{CEdge, CNode, PCEdge}, + route::{Behavior, CEdge, CNode, PCEdge}, triple_arena::ptr_struct, }; @@ -130,9 +130,8 @@ impl Channeler { return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) } } - // other kinds of validity for p_cedge in self.cedges.ptrs() { - let cedge = &self.cedges.get(p_cedge).unwrap(); + let cedge = self.cedges.get(p_cedge).unwrap(); for p_cnode in &cedge.incidences { if !self.cnodes.contains(*p_cnode) { return Err(EvalError::OtherString(format!( @@ -189,7 +188,25 @@ impl Channeler { ))) } } - // tree invariants + // non `Ptr` validities + for p_cedge in self.cedges.ptrs() { + let cedge = self.cedges.get(p_cedge).unwrap(); + let ok = match &cedge.programmability().behavior { + Behavior::Noop | Behavior::RouteBit | Behavior::Bulk(_) => { + cedge.incidences.len() == 2 + } + Behavior::StaticLut(lut) => { + lut.bw().is_power_of_two() + && ((lut.bw().trailing_zeros() as usize + 1) == cedge.incidences.len()) + } + Behavior::ArbitraryLut(input_len) => *input_len == cedge.incidences.len(), + }; + if !ok { + return Err(EvalError::OtherString(format!( + "{cedge:?} an invariant is broken" + ))) + } + } Ok(()) } } diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index f7f1ef9e..bc732494 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -1,17 +1,35 @@ +use awint::awint_dag::EvalError; + use crate::{ - route::{HyperPath, PHyperPath}, + ensemble::{self, Ensemble}, + route::{Channeler, HyperPath, PHyperPath}, triple_arena::Arena, - Epoch, + Epoch, EvalAwi, LazyAwi, SuspendedEpoch, }; #[derive(Debug, Clone)] pub struct Router { + target_ensemble: Ensemble, + target_channeler: Channeler, + program_ensemble: Ensemble, + program_channeler: Channeler, hyperpaths: Arena, } impl Router { - pub fn new() -> Self { + pub fn new( + target_epoch: &SuspendedEpoch, + target_channeler: Channeler, + program_epoch: &SuspendedEpoch, + program_channeler: Channeler, + ) -> Self { + // TODO may want the primary user function to take ownership of epoch, or maybe + // always for memory reasons Self { + target_ensemble: target_epoch.ensemble(|ensemble| ensemble.clone()), + target_channeler, + program_ensemble: program_epoch.ensemble(|ensemble| ensemble.clone()), + program_channeler, hyperpaths: Arena::new(), } } @@ -27,12 +45,20 @@ impl Router { } */ - // TODO are the target and program both on channeling graphs, what assymetries - // are there? -} + /// Tell the router what bits it can use for programming the target + pub fn map_config(&mut self, config: &LazyAwi) -> Result<(), EvalError> { + Ok(()) + } + + /// Tell the router what program input bits we want to map to what target + /// input bits + pub fn map_lazy(&mut self, target: &LazyAwi, program: &LazyAwi) -> Result<(), EvalError> { + Ok(()) + } -impl Default for Router { - fn default() -> Self { - Self::new() + /// Tell the router what program output bits we want to map to what target + /// output bits + pub fn map_eval(&mut self, target: &EvalAwi, program: &EvalAwi) -> Result<(), EvalError> { + Ok(()) } } From f16846cee535081c3b9a9813cb6ed45bf171ed6c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 28 Dec 2023 16:22:38 -0600 Subject: [PATCH 031/119] introducing `DynamicValue` --- starlight/src/awi_structs/lazy_awi.rs | 28 +++++--- starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/debug.rs | 21 ++++-- starlight/src/ensemble/lnode.rs | 50 +++++++++++--- starlight/src/ensemble/optimize.rs | 93 ++++++++++++++++++++++++++- starlight/src/ensemble/rnode.rs | 13 +++- starlight/src/ensemble/state.rs | 15 ++--- starlight/src/ensemble/together.rs | 6 +- starlight/src/ensemble/value.rs | 37 +++++++++-- starlight/src/route/cedge.rs | 83 +++++++++++++++++------- starlight/src/route/channel.rs | 7 +- 11 files changed, 286 insertions(+), 69 deletions(-) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index cac2e69b..bad2e8c2 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -122,11 +122,17 @@ impl LazyAwi { Self::from_bits(&awi::Awi::uone(w)) }*/ - /// Retroactively-assigns by `rhs`. Returns `None` if bitwidths mismatch or - /// if this is being called after the corresponding Epoch is dropped and - /// states have been pruned. + /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch + /// or if this is being called after the corresponding Epoch is dropped + /// and states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs) + Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + } + + /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it + /// adds the guarantee that the value will never be changed again + pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + Ensemble::change_thread_local_rnode_value(self.p_external, rhs, true) } } @@ -244,11 +250,17 @@ impl LazyInlAwi { Self { opaque, p_external } } - /// Retroactively-assigns by `rhs`. Returns `None` if bitwidths mismatch or - /// if this is being called after the corresponding Epoch is dropped and - /// states have been pruned. + /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch + /// or if this is being called after the corresponding Epoch is dropped + /// and states have been pruned. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs) + Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + } + + /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it + /// adds the guarantee that the value will never be changed again + pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + Ensemble::change_thread_local_rnode_value(self.p_external, rhs, true) } } diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index e4357ca3..00bf63bb 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -14,4 +14,4 @@ pub use rnode::{Notary, PExternal, PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; pub use together::{Ensemble, Equiv, PBack, Referent}; -pub use value::{Evaluator, Value}; +pub use value::{DynamicValue, Evaluator, Value}; diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index b0a60efe..88794d30 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -5,6 +5,7 @@ use awint::{ awint_macro_internals::triple_arena::Arena, }; +use super::DynamicValue; use crate::{ ensemble::{Ensemble, Equiv, LNode, LNodeKind, PBack, PRNode, PTNode, Referent, State}, triple_arena::{Advancer, ChainArena}, @@ -128,12 +129,18 @@ impl DebugNodeTrait for NodeKind { .enumerate() .map(|(i, p)| (*p, format!("{i}"))) .collect(), - LNodeKind::DynamicLut(inp, table) => inp - .iter() - .enumerate() - .map(|(i, p)| (*p, format!("i{i}"))) - .chain(table.iter().enumerate().map(|(i, p)| (*p, format!("t{i}")))) - .collect(), + LNodeKind::DynamicLut(inp, lut) => { + let mut v = vec![]; + for (i, p) in inp.iter().enumerate() { + v.push((*p, format!("i{i}"))); + } + for (i, p) in lut.iter().enumerate() { + if let DynamicValue::Dynam(p_back) = p { + v.push((*p_back, format!("l{i}"))); + } + } + v + } } }, center: { @@ -141,7 +148,7 @@ impl DebugNodeTrait for NodeKind { match &lnode.kind { LNodeKind::Copy(_) => (), LNodeKind::Lut(_, lut) => v.push(format!("{:?} ", lut)), - LNodeKind::DynamicLut(..) => (), + LNodeKind::DynamicLut(..) => v.push("dyn".to_owned()), } if let Some(lowered_from) = lnode.lowered_from { v.push(format!("{:?}", lowered_from)); diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 151a0c93..4aab82e1 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -10,7 +10,10 @@ use awint::{ }; use smallvec::SmallVec; -use crate::{ensemble::PBack, triple_arena::ptr_struct}; +use crate::{ + ensemble::{DynamicValue, PBack}, + triple_arena::ptr_struct, +}; // We use this because our algorithms depend on generation counters ptr_struct!(PLNode); @@ -23,7 +26,7 @@ pub enum LNodeKind { /// `SmallVec` is the inputs Lut(SmallVec<[PBack; 4]>, Awi), /// A Dynamic Lookup Table with the inputs and then the `Vec` is the table - DynamicLut(SmallVec<[PBack; 4]>, Vec), + DynamicLut(SmallVec<[PBack; 4]>, Vec), } /// A lookup table node @@ -65,12 +68,14 @@ impl LNode { f(*inp); } } - LNodeKind::DynamicLut(inp, table) => { + LNodeKind::DynamicLut(inp, lut) => { for inp in inp.iter() { f(*inp); } - for inp in table.iter() { - f(*inp); + for inp in lut.iter() { + if let DynamicValue::Dynam(inp) = inp { + f(*inp); + } } } } @@ -85,12 +90,14 @@ impl LNode { f(inp); } } - LNodeKind::DynamicLut(inp, table) => { + LNodeKind::DynamicLut(inp, lut) => { for inp in inp.iter_mut() { f(inp); } - for inp in table.iter_mut() { - f(inp); + for inp in lut.iter_mut() { + if let DynamicValue::Dynam(inp) = inp { + f(inp); + } } } } @@ -115,6 +122,33 @@ impl LNode { next_lut } + /// The same as `reduce_lut`, except for a dynamic table, and it returns + /// removed `PBack`s + pub fn reduce_dynamic_lut( + lut: &[DynamicValue], + i: usize, + bit: bool, + ) -> (Vec, Vec) { + assert!(lut.len().is_power_of_two()); + let next_bw = lut.len() / 2; + let mut next_lut = vec![DynamicValue::Unknown; next_bw]; + let mut removed = Vec::with_capacity(next_bw); + let w = 1 << i; + let mut from = 0; + let mut to = 0; + while to < next_bw { + for j in 0..w { + next_lut[to + j] = lut[if bit { from + j } else { from }]; + if let DynamicValue::Dynam(p_back) = lut[if !bit { from + j } else { from }] { + removed.push(p_back); + } + } + from += 2 * w; + to += w; + } + (next_lut, removed) + } + /// Returns an equivalent reduced LUT (with the `i`th index removed) if the /// LUT output is independent with respect to the `i`th bit #[must_use] diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index b07d3f00..f55db848 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -10,7 +10,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, + ensemble::{DynamicValue, Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, SmallMap, }; @@ -200,7 +200,96 @@ impl Ensemble { false } } - LNodeKind::DynamicLut(..) => todo!(), + LNodeKind::DynamicLut(inp, lut) => { + // FIXME + /* + // acquire LUT inputs, for every constant input reduce the LUT + let len = usize::from(u8::try_from(inp.len()).unwrap()); + for i in (0..len).rev() { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + if let Value::Const(val) = equiv.val { + // we will be removing the input, mark it to be investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); + inp.remove(i); + + let (tmp, removed) = LNode::reduce_dynamic_lut(&lut, i, val); + *lut = tmp; + for remove in removed { + let equiv = self.backrefs.get_val(remove).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(remove).unwrap(); + } + } + } + + // check for duplicate inputs of the same source + 'outer: loop { + // we have to reset every time because the removals can mess up any range of + // indexes + let mut set = SmallMap::new(); + for i in 0..inp.len() { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + match set.insert(equiv.p_self_equiv.inx(), i) { + Ok(()) => (), + Err(j) => { + let next_bw = lut.len() / 2; + let mut next_lut = vec![DynamicValue::Unknown; next_bw]; + let mut removed = Vec::with_capacity(next_bw); + let mut to = 0; + for k in 0..lut.len() { + let inx = InlAwi::from_usize(k); + if inx.get(i).unwrap() == inx.get(j).unwrap() { + next_lut[to] = lut[k]; + to += 1; + } else if let DynamicValue::Dynam(p_back) = lut[k] { + removed.push(p_back); + } + } + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(inp[j]).unwrap(); + inp.remove(j); + *lut = next_lut; + for p_back in removed { + let equiv = self.backrefs.get_val(p_back).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_back).unwrap(); + } + continue 'outer + } + } + } + break + } + + // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing + let len = inp.len(); + for i in (0..len).rev() { + if lut.bw() > 1 { + if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { + // independent of the `i`th bit + lut = reduced; + let p_inp = inp.remove(i); + let equiv = self.backrefs.get_val(p_inp).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); + } + } + } + */ + // sort inputs so that `LNode`s can be compared later + // TODO? + + //false + todo!() + } } } diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index eb9e5943..e6cc3588 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -154,6 +154,7 @@ impl Ensemble { pub fn change_thread_local_rnode_value( p_external: PExternal, bits: &awi::Bits, + make_const: bool, ) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -166,9 +167,15 @@ impl Ensemble { for bit_i in 0..bits.bw() { let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; if let Some(p_back) = p_back { - ensemble - .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) - .unwrap(); + if make_const { + ensemble + .change_value(p_back, Value::Const(bits.get(bit_i).unwrap())) + .unwrap(); + } else { + ensemble + .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) + .unwrap(); + } } } } else { diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 290efc91..661f34ea 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -415,8 +415,8 @@ fn lower_elementary_to_lnodes_intermediate( from += 1; } } - StaticLut(ref concat, ref table) => { - let table = table.clone(); + StaticLut(ref concat, ref lut) => { + let lut = lut.clone(); let concat_len = concat.len(); let mut inx_bits: SmallVec<[Option; 8]> = smallvec![]; for c_i in 0..concat_len { @@ -433,21 +433,20 @@ fn lower_elementary_to_lnodes_intermediate( let out_bw = this.stator.states[p_state].p_self_bits.len(); let num_entries = 1usize.checked_shl(u32::try_from(inx_len).unwrap()).unwrap(); // this must be handled upstream - assert_eq!(out_bw * num_entries, table.bw()); + assert_eq!(out_bw * num_entries, lut.bw()); // convert from multiple out to single out bit lut for bit_i in 0..out_bw { - let single_bit_table = if out_bw == 1 { - table.clone() + let single_bit_lut = if out_bw == 1 { + lut.clone() } else { let mut val = awi::Awi::zero(NonZeroUsize::new(num_entries).unwrap()); for i in 0..num_entries { - val.set(i, table.get((i * out_bw) + bit_i).unwrap()) - .unwrap(); + val.set(i, lut.get((i * out_bw) + bit_i).unwrap()).unwrap(); } val }; let p_equiv0 = this - .make_lut(&inx_bits, &single_bit_table, Some(p_state)) + .make_lut(&inx_bits, &single_bit_lut, Some(p_state)) .unwrap(); let p_equiv1 = this.stator.states[p_state].p_self_bits[bit_i].unwrap(); this.union_equiv(p_equiv0, p_equiv1).unwrap(); diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 6246995d..0fd2d207 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -512,11 +512,11 @@ impl Ensemble { pub fn make_lut( &mut self, p_inxs: &[Option], - table: &Bits, + lut: &Bits, lowered_from: Option, ) -> Option { let num_entries = 1 << p_inxs.len(); - if table.bw() != num_entries { + if lut.bw() != num_entries { return None } for p_inx in p_inxs { @@ -545,7 +545,7 @@ impl Ensemble { .unwrap(); inp.push(p_back); } - LNode::new(p_self, LNodeKind::Lut(inp, Awi::from(table)), lowered_from) + LNode::new(p_self, LNodeKind::Lut(inp, Awi::from(lut)), lowered_from) }); Some(p_equiv) } diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index a972a3d8..e1f81a53 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -13,10 +13,15 @@ use crate::{ epoch::EpochShared, }; +/// The value of a multistate boolean #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { + /// The value is simply unknown, or a circuit is undriven Unknown, + /// The value is a known constant that is guaranteed to not change under any + /// condition Const(bool), + /// The value is known, but may be dynamically changed Dynam(bool), } @@ -53,6 +58,26 @@ impl Value { } } +/// Used for dealing with mixed values and dynamics +#[derive(Debug, Clone, Copy)] +pub enum DynamicValue { + /// Corresponds with `Value::Unknown` + Unknown, + /// Corresponds with `Value::Const` + Const(bool), + Dynam(PBack), +} + +impl DynamicValue { + pub fn is_known(&self) -> bool { + match self { + DynamicValue::Unknown => false, + DynamicValue::Const(_) => true, + DynamicValue::Dynam(_) => true, + } + } +} + /* Consider a request front where we want to know if the output of a LUT is unable to change and thus that part of the front can be eliminated @@ -325,11 +350,13 @@ impl Ensemble { } } - pub fn change_value(&mut self, p_back: PBack, value: Value) -> Option<()> { + pub fn change_value(&mut self, p_back: PBack, value: Value) -> Result<(), EvalError> { if let Some(equiv) = self.backrefs.get_val_mut(p_back) { if equiv.val.is_const() && (equiv.val != value) { - // not allowed - panic!(); + return Err(EvalError::OtherStr( + "tried to change a constant (probably, `retro_const_` was used followed by a \ + contradicting `retro_*`", + )) } // switch to change phase if not already if self.evaluator.phase != EvalPhase::Change { @@ -338,9 +365,9 @@ impl Ensemble { } equiv.val = value; equiv.change_visit = self.evaluator.change_visit_gen(); - Some(()) + Ok(()) } else { - None + Err(EvalError::InvalidPtr) } } diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 44f414a4..d9f11369 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -6,7 +6,7 @@ use awint::{ use crate::{ awint_dag::smallvec::SmallVec, ensemble, - ensemble::LNodeKind, + ensemble::{Ensemble, LNodeKind}, route::{channel::Referent, Channeler, PBack}, triple_arena::ptr_struct, Epoch, @@ -43,11 +43,18 @@ pub enum Behavior { /// A description of bits to set in order to achieve some desired edge behavior. /// For now we unconditionally specify bits, in the future it should be more /// detailed to allow for more close by programs to coexist -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Instruction { pub set_bits: SmallVec<[(ensemble::PBack, bool); 4]>, } +impl Instruction { + /// A new instruction that requires nothing + pub fn new() -> Self { + Self::default() + } +} + #[derive(Debug, Clone)] pub struct Programmability { /// The behavior that can be programmed into this edge @@ -99,32 +106,64 @@ impl Channeler { }) } - /// Assumes that `epoch` has been optimized - pub fn from_epoch(epoch: &Epoch) -> Result { + /// Assumes that the ensemble has been optimized + pub fn from_ensemble(ensemble: &Ensemble) -> Result { let mut channeler = Self::new(); - epoch.ensemble(|ensemble| { - // for each equivalence make a `CNode` with associated `EnsembleBackref` - for equiv in ensemble.backrefs.vals() { - let p_cnode = channeler.make_top_level_cnode(vec![]); - channeler - .cnodes - .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) - .unwrap(); - } + // for each equivalence make a `CNode` with associated `EnsembleBackref` + for equiv in ensemble.backrefs.vals() { + let p_cnode = channeler.make_top_level_cnode(vec![]); + let channeler_backref = channeler + .cnodes + .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) + .unwrap(); + channeler + .ensemble_backref_to_channeler_backref + .insert(equiv.p_self_equiv, channeler_backref); + } + + // translate from any ensemble backref to the equivalence backref to the + // channeler backref + fn translate( + ensemble: &Ensemble, + channeler: &Channeler, + ensemble_backref: ensemble::PBack, + ) -> PBack { + let p_equiv = ensemble + .backrefs + .get_val(ensemble_backref) + .unwrap() + .p_self_equiv; + let p0 = channeler + .ensemble_backref_to_channeler_backref + .find_key(&p_equiv) + .unwrap(); + *channeler + .ensemble_backref_to_channeler_backref + .get_val(p0) + .unwrap() + } - // add `CEdge`s according to `LNode`s - for lnode in ensemble.lnodes.vals() { - match &lnode.kind { - LNodeKind::Copy(_) => { - return Err(EvalError::OtherStr("the epoch was not optimized")) + // add `CEdge`s according to `LNode`s + for lnode in ensemble.lnodes.vals() { + match &lnode.kind { + LNodeKind::Copy(_) => { + return Err(EvalError::OtherStr("the epoch was not optimized")) + } + LNodeKind::Lut(inp, awi) => { + let mut v: SmallVec<[PBack; 8]> = + smallvec![translate(ensemble, &channeler, lnode.p_self)]; + for input in inp { + v.push(translate(ensemble, &channeler, *input)); } - LNodeKind::Lut(inp, awi) => {} - LNodeKind::DynamicLut(..) => todo!(), + channeler.make_cedge(&v, Programmability { + behavior: Behavior::StaticLut(awi.clone()), + instruction: Instruction::new(), + }); } + LNodeKind::DynamicLut(inp, lut) => todo!(), } - Ok(()) - })?; + } Ok(channeler) } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 98946743..c9363809 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,6 +1,6 @@ use awint::awint_dag::{ smallvec::smallvec, - triple_arena::{Arena, SurjectArena}, + triple_arena::{Arena, OrdArena, SurjectArena}, EvalError, }; @@ -11,7 +11,7 @@ use crate::{ triple_arena::ptr_struct, }; -ptr_struct!(PBack); +ptr_struct!(P0; PBack); #[derive(Debug, Clone, Copy)] pub enum Referent { @@ -30,6 +30,8 @@ pub struct Channeler { /// all unconnected graphs being connected with `Behavior::Noop` so that the /// normal algorithm can allocate over them pub top_level_cnodes: SmallVec<[PBack; 1]>, + // needed for the unit edges to find incidences + pub ensemble_backref_to_channeler_backref: OrdArena, } impl Channeler { @@ -38,6 +40,7 @@ impl Channeler { cnodes: SurjectArena::new(), cedges: Arena::new(), top_level_cnodes: smallvec![], + ensemble_backref_to_channeler_backref: OrdArena::new(), } } From 5b01da6b000bd95660f9b9cd6838251fffbf993f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 28 Dec 2023 16:57:32 -0600 Subject: [PATCH 032/119] minor fixes --- README.md | 2 +- starlight/src/ensemble/optimize.rs | 1 - starlight/src/lib.rs | 22 +++--- testcrate/benches/bench.rs | 24 +++---- testcrate/tests/basic.rs | 112 +++-------------------------- testcrate/tests/loop.rs | 50 ++++++------- testcrate/tests/luts.rs | 91 +++++++++++++++++++++++ testcrate/tests/stats.rs | 16 ++--- 8 files changed, 159 insertions(+), 159 deletions(-) create mode 100644 testcrate/tests/luts.rs diff --git a/README.md b/README.md index 8b7838e7..7818873a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ // First, create an epoch, this will live until this struct is dropped. The // epoch needs to live until all mimicking operations are done and states are // lowered. Manually drop it with the `drop` function to avoid mistakes. - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let mut m = StateMachine::new(bw(4)); diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index f55db848..45c84be5 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -123,7 +123,6 @@ impl Ensemble { .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); self.backrefs.remove_key(p_inp).unwrap(); inp.remove(i); - lut = LNode::reduce_lut(&lut, i, val); } } diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index cbc96431..072a21fe 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -58,7 +58,7 @@ //! // First, create an epoch, this will live until this struct is dropped. The //! // epoch needs to live until all mimicking operations are done and states are //! // lowered. Manually drop it with the `drop` function to avoid mistakes. -//! let epoch0 = Epoch::new(); +//! let epoch = Epoch::new(); //! //! let mut m = StateMachine::new(bw(4)); //! @@ -83,20 +83,20 @@ //! use awi::*; //! //! // discard all unused mimicking states so the render is cleaner -//! epoch0.prune().unwrap(); +//! epoch.prune().unwrap(); //! //! // See the mimicking state DAG before it is lowered -//! epoch0 +//! epoch //! .render_to_svgs_in_dir(std::path::PathBuf::from("./".to_owned())) //! .unwrap(); //! //! // lower into purely static bit movements and lookup tables. -//! epoch0.lower().unwrap(); -//! epoch0.optimize().unwrap(); +//! epoch.lower().unwrap(); +//! epoch.optimize().unwrap(); //! //! // Now the combinational logic is described in a DAG of lookup tables that we //! // could use for various purposes -//! epoch0.ensemble(|ensemble| { +//! epoch.ensemble(|ensemble| { //! for state in ensemble.stator.states.vals() { //! awi::assert!(state.lowered_to_lnodes); //! } @@ -106,24 +106,24 @@ //! input.retro_(&awi!(0101)).unwrap(); //! // check assertions (all `dag::assert*` functions and dynamic `unwrap`s done //! // during the current `Epoch`) -//! epoch0.assert_assertions(true).unwrap(); +//! epoch.assert_assertions(true).unwrap(); //! // evaluate the outputs //! awi::assert_eq!(output_counter.eval().unwrap(), awi!(0011)); //! awi::assert_eq!(output_data.eval().unwrap(), awi!(0xa505_u16)); //! //! // reassign and reevaluate //! input.retro_(&awi!(1011)).unwrap(); -//! awi::assert!(epoch0.assert_assertions(true).is_err()); +//! awi::assert!(epoch.assert_assertions(true).is_err()); //! awi::assert_eq!(output_data.eval().unwrap(), awi!(0x7b0b_u16)); //! } -//! drop(epoch0); +//! drop(epoch); //! ``` //! //! ``` //! use starlight::{dag, awi, Epoch, EvalAwi}; //! use dag::*; //! -//! let epoch0 = Epoch::new(); +//! let epoch = Epoch::new(); //! //! let mut lhs = inlawi!(zero: ..8); //! let rhs = inlawi!(umax: ..8); @@ -159,7 +159,7 @@ //! use awi::*; //! awi::assert_eq!(output_eval.eval().unwrap(), awi!(01010101)); //! } -//! drop(epoch0); +//! drop(epoch); //! ``` #![allow(clippy::needless_range_loop)] diff --git a/testcrate/benches/bench.rs b/testcrate/benches/bench.rs index a0c33080..0dece4e1 100644 --- a/testcrate/benches/bench.rs +++ b/testcrate/benches/bench.rs @@ -7,38 +7,38 @@ use test::Bencher; #[bench] fn lower_funnel(bencher: &mut Bencher) { bencher.iter(|| { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let rhs = LazyAwi::opaque(bw(64)); let s = LazyAwi::opaque(bw(5)); let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch0.prune().unwrap(); - epoch0.lower().unwrap(); - epoch0.assert_assertions(true).unwrap(); + epoch.prune().unwrap(); + epoch.lower().unwrap(); + epoch.assert_assertions(true).unwrap(); }) } #[bench] fn optimize_funnel(bencher: &mut Bencher) { bencher.iter(|| { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let rhs = LazyAwi::opaque(bw(64)); let s = LazyAwi::opaque(bw(5)); let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch0.prune().unwrap(); - epoch0.optimize().unwrap(); - epoch0.assert_assertions(true).unwrap(); + epoch.prune().unwrap(); + epoch.optimize().unwrap(); + epoch.assert_assertions(true).unwrap(); }) } #[bench] fn loop_net(bencher: &mut Bencher) { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let num_ports = 16; let mut net = Net::zero(bw(5)); @@ -54,19 +54,19 @@ fn loop_net(bencher: &mut Bencher) { let eval_res = EvalAwi::from_bool(res.is_none()); { use awi::*; - epoch0.optimize().unwrap(); + epoch.optimize().unwrap(); bencher.iter(|| { for i in 0..(1 << w.get()) { let mut inx = Awi::zero(w); inx.usize_(i); lazy.retro_(&inx).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); awi::assert_eq!(eval_res.eval().unwrap().to_bool(), i >= num_ports); if i < num_ports { awi::assert_eq!(eval_net.eval().unwrap().to_usize(), i); } } }); - drop(epoch0); + drop(epoch); } } diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index d2adf531..6762aef5 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -7,7 +7,7 @@ use starlight::{ #[test] fn lazy_awi() -> Option<()> { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let x = LazyAwi::opaque(bw(1)); let mut a = awi!(x); @@ -20,26 +20,26 @@ fn lazy_awi() -> Option<()> { // TODO the solution is to use the `bits` macro in these places x.retro_(&awi!(0)).unwrap(); - epoch0.verify_integrity().unwrap(); + epoch.verify_integrity().unwrap(); awi::assert_eq!(y.eval().unwrap(), awi!(1)); - epoch0.verify_integrity().unwrap(); + epoch.verify_integrity().unwrap(); x.retro_(&awi!(1)).unwrap(); awi::assert_eq!(y.eval().unwrap(), awi!(0)); - epoch0.verify_integrity().unwrap(); + epoch.verify_integrity().unwrap(); } // cleans up everything not still used by `LazyAwi`s, `LazyAwi`s deregister // rnodes when dropped - drop(epoch0); + drop(epoch); Some(()) } #[test] fn invert_twice() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let x = LazyAwi::opaque(bw(1)); let mut a = awi!(x); a.not_(); @@ -53,16 +53,16 @@ fn invert_twice() { x.retro_(&awi!(0)).unwrap(); assert_eq!(y.eval().unwrap(), awi!(0)); - epoch0.verify_integrity().unwrap(); + epoch.verify_integrity().unwrap(); x.retro_(&awi!(1)).unwrap(); assert_eq!(y.eval().unwrap(), awi!(1)); } - drop(epoch0); + drop(epoch); } #[test] fn multiplier() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let input_a = LazyAwi::opaque(bw(16)); let input_b = LazyAwi::opaque(bw(16)); let mut output = inlawi!(zero: ..32); @@ -76,100 +76,10 @@ fn multiplier() { input_b.retro_(&awi!(77u16)).unwrap(); std::assert_eq!(output.eval().unwrap(), awi!(9471u32)); - epoch0.optimize().unwrap(); + epoch.optimize().unwrap(); input_a.retro_(&awi!(10u16)).unwrap(); std::assert_eq!(output.eval().unwrap(), awi!(770u32)); } - drop(epoch0); -} - -// test LUT simplifications -#[test] -fn luts() { - let mut rng = StarRng::new(0); - let mut inp_bits = 0; - for input_w in 1usize..=8 { - let lut_w = 1 << input_w; - for _ in 0..100 { - let epoch0 = Epoch::new(); - let mut test_input = awi::Awi::zero(bw(input_w)); - rng.next_bits(&mut test_input); - let original_input = test_input.clone(); - let input = LazyAwi::opaque(bw(input_w)); - let mut lut_input = dag::Awi::from(input.as_ref()); - let mut opaque_set = awi::Awi::umax(bw(input_w)); - for i in 0..input_w { - // randomly set some bits to a constant and leave some as opaque - if rng.next_bool() { - lut_input.set(i, test_input.get(i).unwrap()).unwrap(); - opaque_set.set(i, false).unwrap(); - } - } - for _ in 0..input_w { - if (rng.next_u8() % 8) == 0 { - let inx0 = (rng.next_u8() % (input_w as awi::u8)) as awi::usize; - let inx1 = (rng.next_u8() % (input_w as awi::u8)) as awi::usize; - if opaque_set.get(inx0).unwrap() && opaque_set.get(inx1).unwrap() { - // randomly make some inputs duplicates from the same source - let tmp = lut_input.get(inx0).unwrap(); - lut_input.set(inx1, tmp).unwrap(); - let tmp = test_input.get(inx0).unwrap(); - test_input.set(inx1, tmp).unwrap(); - } - } - } - let mut lut = awi::Awi::zero(bw(lut_w)); - rng.next_bits(&mut lut); - let mut x = awi!(0); - x.lut_(&Awi::from(&lut), &lut_input).unwrap(); - - { - use awi::{assert, assert_eq, *}; - - let opt_res = EvalAwi::from(&x); - - epoch0.optimize().unwrap(); - - input.retro_(&original_input).unwrap(); - - // check that the value is correct - let opt_res = opt_res.eval().unwrap(); - let res = lut.get(test_input.to_usize()).unwrap(); - let res = Awi::from_bool(res); - if opt_res != res { - /* - println!("{:0b}", &opaque_set); - println!("{:0b}", &test_input); - println!("{:0b}", &lut); - */ - } - assert_eq!(opt_res, res); - - epoch0.ensemble(|ensemble| { - // assert that there is at most one LNode with constant inputs optimized away - let mut lnodes = ensemble.lnodes.vals(); - if let Some(lnode) = lnodes.next() { - match &lnode.kind { - LNodeKind::Copy(_) => { - inp_bits += 1; - } - LNodeKind::Lut(inp, _) => { - inp_bits += inp.len(); - assert!(inp.len() <= opaque_set.count_ones()); - } - LNodeKind::DynamicLut(..) => unreachable!(), - } - assert!(lnodes.next().is_none()); - } - assert!(lnodes.next().is_none()); - }); - } - } - } - { - use awi::assert_eq; - // this should only decrease from future optimizations - assert_eq!(inp_bits, 1386); - } + drop(epoch); } diff --git a/testcrate/tests/loop.rs b/testcrate/tests/loop.rs index 3a1f2155..305ced2d 100644 --- a/testcrate/tests/loop.rs +++ b/testcrate/tests/loop.rs @@ -4,7 +4,7 @@ use starlight::{awi, dag::*, Epoch, EvalAwi, LazyAwi, Loop}; #[test] fn loop_invert() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let looper = Loop::zero(bw(1)); let mut x = awi!(looper); let x_copy = x.clone(); @@ -19,18 +19,18 @@ fn loop_invert() { let eval_x = EvalAwi::from(&x); assert_eq!(eval_x.eval().unwrap(), awi!(1)); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(eval_x.eval().unwrap(), awi!(0)); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(eval_x.eval().unwrap(), awi!(1)); } - drop(epoch0); + drop(epoch); } // tests an incrementing counter #[test] fn loop_incrementer() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let looper = Loop::zero(bw(4)); let val = EvalAwi::from(&looper); let mut tmp = awi!(looper); @@ -40,15 +40,15 @@ fn loop_incrementer() { { for i in 0..16 { awi::assert_eq!(i, val.eval().unwrap().to_usize()); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); } } - drop(epoch0); + drop(epoch); } #[test] fn loop_net4() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let mut net = Net::zero(bw(4)); net.push(&awi!(0xa_u4)).unwrap(); net.push(&awi!(0xb_u4)).unwrap(); @@ -61,25 +61,25 @@ fn loop_net4() { { use awi::{assert_eq, *}; inx.retro_(&awi!(0_u2)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(val.eval().unwrap(), awi!(0xa_u4)); inx.retro_(&awi!(2_u2)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(val.eval().unwrap(), awi!(0xc_u4)); inx.retro_(&awi!(1_u2)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(val.eval().unwrap(), awi!(0xb_u4)); inx.retro_(&awi!(3_u2)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(val.eval().unwrap(), awi!(0xd_u4)); } - drop(epoch0); + drop(epoch); } -fn exhaustive_net_test(epoch0: &Epoch, num_ports: awi::usize, diff: awi::isize) { +fn exhaustive_net_test(epoch: &Epoch, num_ports: awi::usize, diff: awi::isize) { let mut net = Net::zero(bw(5)); for i in 0..num_ports { let mut port = awi!(0u5); @@ -94,12 +94,12 @@ fn exhaustive_net_test(epoch0: &Epoch, num_ports: awi::usize, diff: awi::isize) let eval_res = EvalAwi::from_bool(res.is_none()); { use awi::*; - epoch0.optimize().unwrap(); + epoch.optimize().unwrap(); for i in 0..(1 << w.get()) { let mut inx = Awi::zero(w); inx.usize_(i); lazy.retro_(&inx).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); awi::assert_eq!(eval_res.eval().unwrap().to_bool(), i >= num_ports); if i < num_ports { awi::assert_eq!(eval_net.eval().unwrap().to_usize(), i); @@ -110,7 +110,7 @@ fn exhaustive_net_test(epoch0: &Epoch, num_ports: awi::usize, diff: awi::isize) #[test] fn loop_net_no_ports() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); // done separately because it results in an undriven `Loop` { let net = Net::zero(bw(5)); @@ -121,12 +121,12 @@ fn loop_net_no_ports() { assert!(res.is_none_at_runtime()); } } - drop(epoch0); + drop(epoch); } #[test] fn loop_net() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); // one port { let mut net = Net::zero(bw(5)); @@ -138,21 +138,21 @@ fn loop_net() { { use awi::{assert_eq, *}; lazy.retro_(&awi!(0)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(eval_res.eval().unwrap(), awi!(0)); assert_eq!(eval_net.eval().unwrap(), awi!(0xa_u5)); // any nonzero index always returns a `None` from the function lazy.retro_(&awi!(1)).unwrap(); - epoch0.drive_loops().unwrap(); + epoch.drive_loops().unwrap(); assert_eq!(eval_res.eval().unwrap(), awi!(1)); } } for num_ports in 3..17 { // test with index size one less than needed to index all ports - exhaustive_net_test(&epoch0, num_ports, -1); - exhaustive_net_test(&epoch0, num_ports, 0); - exhaustive_net_test(&epoch0, num_ports, 1); + exhaustive_net_test(&epoch, num_ports, -1); + exhaustive_net_test(&epoch, num_ports, 0); + exhaustive_net_test(&epoch, num_ports, 1); } - drop(epoch0); + drop(epoch); } diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs new file mode 100644 index 00000000..9865995d --- /dev/null +++ b/testcrate/tests/luts.rs @@ -0,0 +1,91 @@ +use starlight::{awi, dag::*, ensemble::LNodeKind, Epoch, EvalAwi, LazyAwi, StarRng}; + +// Test static LUT simplifications +#[test] +fn luts_states() { + let mut rng = StarRng::new(0); + let mut inp_bits = 0; + for input_w in 1usize..=8 { + let lut_w = 1 << input_w; + for _ in 0..100 { + let epoch = Epoch::new(); + let mut test_input = awi::Awi::zero(bw(input_w)); + rng.next_bits(&mut test_input); + let original_input = test_input.clone(); + let input = LazyAwi::opaque(bw(input_w)); + let mut lut_input = Awi::from(input.as_ref()); + let mut opaque_set = awi::Awi::umax(bw(input_w)); + for i in 0..input_w { + // randomly set some bits to a constant and leave some as opaque + if rng.next_bool() { + lut_input.set(i, test_input.get(i).unwrap()).unwrap(); + opaque_set.set(i, false).unwrap(); + } + } + for _ in 0..input_w { + if (rng.next_u8() % 8) == 0 { + let inx0 = (rng.next_u8() % (input_w as awi::u8)) as awi::usize; + let inx1 = (rng.next_u8() % (input_w as awi::u8)) as awi::usize; + if opaque_set.get(inx0).unwrap() && opaque_set.get(inx1).unwrap() { + // randomly make some inputs duplicates from the same source + let tmp = lut_input.get(inx0).unwrap(); + lut_input.set(inx1, tmp).unwrap(); + let tmp = test_input.get(inx0).unwrap(); + test_input.set(inx1, tmp).unwrap(); + } + } + } + let mut lut = awi::Awi::zero(bw(lut_w)); + rng.next_bits(&mut lut); + let mut x = awi!(0); + x.lut_(&Awi::from(&lut), &lut_input).unwrap(); + + { + use awi::{assert, assert_eq, *}; + + let opt_res = EvalAwi::from(&x); + + epoch.optimize().unwrap(); + + input.retro_(&original_input).unwrap(); + + // check that the value is correct + let opt_res = opt_res.eval().unwrap(); + let res = lut.get(test_input.to_usize()).unwrap(); + let res = Awi::from_bool(res); + if opt_res != res { + /* + println!("{:0b}", &opaque_set); + println!("{:0b}", &test_input); + println!("{:0b}", &lut); + */ + } + assert_eq!(opt_res, res); + + epoch.ensemble(|ensemble| { + // assert that there is at most one LNode with constant inputs optimized away + let mut lnodes = ensemble.lnodes.vals(); + if let Some(lnode) = lnodes.next() { + match &lnode.kind { + LNodeKind::Copy(_) => { + inp_bits += 1; + } + LNodeKind::Lut(inp, _) => { + inp_bits += inp.len(); + assert!(inp.len() <= opaque_set.count_ones()); + } + LNodeKind::DynamicLut(..) => unreachable!(), + } + assert!(lnodes.next().is_none()); + } + assert!(lnodes.next().is_none()); + }); + } + } + } + { + use awi::assert_eq; + // this should only decrease from future optimizations + assert_eq!(inp_bits, 1386); + } +} diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index 72475431..f280ad07 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -4,24 +4,24 @@ use starlight::{awi, dag::*, Epoch, EvalAwi, LazyAwi}; // expensive #[test] fn stats_optimize_funnel() { - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let rhs = LazyAwi::opaque(bw(64)); let s = LazyAwi::opaque(bw(5)); let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch0.prune().unwrap(); - epoch0.lower().unwrap(); - epoch0.assert_assertions(true).unwrap(); - epoch0.ensemble(|ensemble| { + epoch.prune().unwrap(); + epoch.lower().unwrap(); + epoch.assert_assertions(true).unwrap(); + epoch.ensemble(|ensemble| { awi::assert_eq!(ensemble.stator.states.len(), 2436); awi::assert_eq!(ensemble.backrefs.len_keys(), 8559); awi::assert_eq!(ensemble.backrefs.len_vals(), 1317); }); - epoch0.optimize().unwrap(); - epoch0.assert_assertions(true).unwrap(); - epoch0.ensemble(|ensemble| { + epoch.optimize().unwrap(); + epoch.assert_assertions(true).unwrap(); + epoch.ensemble(|ensemble| { awi::assert_eq!(ensemble.stator.states.len(), 0); awi::assert_eq!(ensemble.backrefs.len_keys(), 5818); awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); From 412e512187248ecde7b4bcd917b2389413651bd1 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 17:20:17 -0600 Subject: [PATCH 033/119] fix the lazyness of `RNode` lowering --- README.md | 44 +++++---- starlight/src/awi_structs/epoch.rs | 29 ++++-- starlight/src/awi_structs/eval_awi.rs | 12 +-- starlight/src/awi_structs/lazy_awi.rs | 5 +- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/rnode.rs | 130 +++++++++++++++++--------- starlight/src/ensemble/state.rs | 32 +++++-- starlight/src/lib.rs | 7 +- testcrate/benches/bench.rs | 2 - testcrate/tests/epoch.rs | 4 +- testcrate/tests/luts.rs | 2 + testcrate/tests/stats.rs | 2 +- 12 files changed, 167 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 7818873a..bb57cf3c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # Starlight - This is a RTL (Register Transfer Level) description library. Instead of the - typical DSL (Domain Specific Language) approach, this allows RTL + This is a DSL (Domain Specific Language) that can describe combinational + logic and temporal logic. This allows RTL (Register Transfer Level) descriptions in ordinary Rust code with all the features that Rust provides. - This crate is still a WIP, but it currently can describe most combinational - logic. The temporal structs (`Loop` and `Net`) need more development before - they will work properly. Many optimizations are planned in the near future. + This crate still has a considerable amount of WIP stuff needed to evolve + into a proper HDL (Hardware Description Language). See the documentation of `awint`/`awint_dag` which is used as the backend - for this. + for this. `awint` is the base library that operations are modeled off of. + `awint_dag` allows for recording a DAG of arbitrary bitwidth integer + operations. `starlight` lowers high level operations down into a DAG of + simple lookup tables, and also adds on temporal structs like `Loop`s. It can + optimize, evaluate, and retroactively change values in the `DAG` for various + purposes. ``` use std::num::NonZeroUsize; @@ -81,45 +85,46 @@ use awi::*; // discard all unused mimicking states so the render is cleaner - epoch0.prune().unwrap(); + epoch.prune_unused_states().unwrap(); // See the mimicking state DAG before it is lowered - epoch0 + epoch .render_to_svgs_in_dir(std::path::PathBuf::from("./".to_owned())) .unwrap(); - // lower into purely static bit movements and lookup tables. - epoch0.lower().unwrap(); - epoch0.optimize().unwrap(); + // lower into purely static bit movements and lookup tables and optimize + epoch.optimize().unwrap(); // Now the combinational logic is described in a DAG of lookup tables that we // could use for various purposes - for state in epoch0.ensemble().stator.states.vals() { - awi::assert!(state.lowered_to_lnodes); - } + epoch.ensemble(|ensemble| { + for state in ensemble.stator.states.vals() { + awi::assert!(state.lowered_to_lnodes); + } + }); // "retroactively" assign the input with a non-opaque value input.retro_(&awi!(0101)).unwrap(); // check assertions (all `dag::assert*` functions and dynamic `unwrap`s done // during the current `Epoch`) - epoch0.assert_assertions_strict().unwrap(); + epoch.assert_assertions(true).unwrap(); // evaluate the outputs awi::assert_eq!(output_counter.eval().unwrap(), awi!(0011)); awi::assert_eq!(output_data.eval().unwrap(), awi!(0xa505_u16)); // reassign and reevaluate input.retro_(&awi!(1011)).unwrap(); - awi::assert!(epoch0.assert_assertions().is_err()); + awi::assert!(epoch.assert_assertions(true).is_err()); awi::assert_eq!(output_data.eval().unwrap(), awi!(0x7b0b_u16)); } - drop(epoch0); + drop(epoch); ``` ``` use starlight::{dag, awi, Epoch, EvalAwi}; use dag::*; - let epoch0 = Epoch::new(); + let epoch = Epoch::new(); let mut lhs = inlawi!(zero: ..8); let rhs = inlawi!(umax: ..8); @@ -155,4 +160,5 @@ use awi::*; awi::assert_eq!(output_eval.eval().unwrap(), awi!(01010101)); } - drop(epoch0); + drop(epoch); + ``` \ No newline at end of file diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 08b6fd65..eb7a8016 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -775,31 +775,44 @@ impl Epoch { /// Removes all states that do not lead to a live `EvalAwi`, and loosely /// evaluates assertions. - pub fn prune(&self) -> Result<(), EvalError> { + pub fn prune_unused_states(&self) -> Result<(), EvalError> { let epoch_shared = self.shared(); // get rid of constant assertions let _ = epoch_shared.assert_assertions(false); let mut lock = epoch_shared.epoch_data.borrow_mut(); - lock.ensemble.prune_states() + lock.ensemble.prune_unused_states() } - /// Lowers all states internally into `LNode`s and `TNode`s. This is not - /// needed in most circumstances, `EvalAwi` and optimization functions - /// do this on demand. Requires that `self` be the current `Epoch`. + /// Lowers states internally into `LNode`s and `TNode`s, for trees of + /// `RNode`s that need it. This is not needed in most circumstances, + /// `EvalAwi` and optimization functions do this on demand. Requires + /// that `self` be the current `Epoch`. pub fn lower(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; - Ensemble::lower_all(&epoch_shared)?; + Ensemble::lower_for_rnodes(&epoch_shared)?; let _ = epoch_shared.assert_assertions(false); Ok(()) } + /// Aggressively prunes all states, lowering `RNode`s for `EvalAwi`s and + /// `LazyAwi`s if necessary and evaluating assertions. Requires that `self` + /// be the current `Epoch`. + pub fn prune(&self) -> Result<(), EvalError> { + let epoch_shared = self.check_current()?; + Ensemble::lower_for_rnodes(&epoch_shared)?; + // get rid of constant assertions + let _ = epoch_shared.assert_assertions(false); + let mut lock = epoch_shared.epoch_data.borrow_mut(); + lock.ensemble.force_remove_all_states() + } + /// Runs optimization including lowering then pruning all states. Requires /// that `self` be the current `Epoch`. pub fn optimize(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; - Ensemble::lower_all(&epoch_shared)?; + Ensemble::lower_for_rnodes(&epoch_shared).unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); - lock.ensemble.optimize_all()?; + lock.ensemble.optimize_all().unwrap(); drop(lock); let _ = epoch_shared.assert_assertions(false); Ok(()) diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index 12365418..e637a7d2 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -119,7 +119,7 @@ impl EvalAwi { pub fn from_state(p_state: PState) -> Self { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); - match lock.ensemble.make_rnode_for_pstate(p_state) { + match lock.ensemble.make_rnode_for_pstate(p_state, true) { Some(p_external) => { lock.ensemble .stator @@ -159,14 +159,8 @@ impl EvalAwi { res.set(bit_i, val).unwrap(); } else { return Err(EvalError::OtherString(format!( - "could not eval bit {bit_i} to known value, the state is {}", - get_current_epoch() - .unwrap() - .epoch_data - .borrow() - .ensemble - .get_state_debug(self.p_state) - .unwrap() + "could not eval bit {bit_i} to known value, the node is {}", + self.p_external() ))) } } diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index bad2e8c2..c2b3e5ed 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -87,7 +87,7 @@ impl LazyAwi { .epoch_data .borrow_mut() .ensemble - .make_rnode_for_pstate(opaque.state()) + .make_rnode_for_pstate(opaque.state(), false) .unwrap(); Self { opaque, p_external } } @@ -238,6 +238,7 @@ impl LazyInlAwi { self.nzbw().get() } + #[track_caller] pub fn opaque() -> Self { let opaque = dag::InlAwi::opaque(); let p_external = get_current_epoch() @@ -245,7 +246,7 @@ impl LazyInlAwi { .epoch_data .borrow_mut() .ensemble - .make_rnode_for_pstate(opaque.state()) + .make_rnode_for_pstate(opaque.state(), false) .unwrap(); Self { opaque, p_external } } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 45c84be5..86c9aa60 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -10,7 +10,7 @@ use awint::{ }; use crate::{ - ensemble::{DynamicValue, Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, + ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, SmallMap, }; diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index e6cc3588..b28679c8 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -23,7 +23,10 @@ ptr_struct!( /// after `State` pruning #[derive(Debug, Clone)] pub struct RNode { + pub nzbw: NonZeroUsize, pub bits: SmallVec<[Option; 1]>, + pub associated_state: Option, + pub lower_before_pruning: bool, } impl Recast for RNode { @@ -36,8 +39,17 @@ impl Recast for RNode { } impl RNode { - pub fn new() -> Self { - Self { bits: smallvec![] } + pub fn new( + nzbw: NonZeroUsize, + associated_state: Option, + lower_before_pruning: bool, + ) -> Self { + Self { + nzbw, + bits: smallvec![], + associated_state, + lower_before_pruning, + } } } @@ -85,14 +97,14 @@ impl Notary { (res, p_external) } - pub fn get_rnode(&self, p_external: PExternal) -> Option<&RNode> { + pub fn get_rnode(&self, p_external: PExternal) -> Option<(PRNode, &RNode)> { let p_rnode = self.rnodes.find_key(&p_external)?; - Some(self.rnodes.get_val(p_rnode).unwrap()) + Some((p_rnode, self.rnodes.get_val(p_rnode).unwrap())) } - pub fn get_rnode_mut(&mut self, p_external: PExternal) -> Option<&mut RNode> { + pub fn get_rnode_mut(&mut self, p_external: PExternal) -> Option<(PRNode, &mut RNode)> { let p_rnode = self.rnodes.find_key(&p_external)?; - Some(self.rnodes.get_val_mut(p_rnode).unwrap()) + Some((p_rnode, self.rnodes.get_val_mut(p_rnode).unwrap())) } pub fn get_rnode_by_p_rnode_mut(&mut self, p_rnode: PRNode) -> Option<&mut RNode> { @@ -102,24 +114,50 @@ impl Notary { impl Ensemble { #[must_use] - pub fn make_rnode_for_pstate(&mut self, p_state: PState) -> Option { - self.initialize_state_bits_if_needed(p_state)?; - let (p_rnode, p_external) = self.notary.insert_rnode(RNode::new()); - let len = self.stator.states[p_state].p_self_bits.len(); - for i in 0..len { - let p_bit = self.stator.states[p_state].p_self_bits[i]; - if let Some(p_bit) = p_bit { - let p_equiv = self.backrefs.get_val(p_bit)?.p_self_equiv; - let p_back_new = self - .backrefs - .insert_key(p_equiv, Referent::ThisRNode(p_rnode)) - .unwrap(); - self.notary.rnodes[p_rnode].bits.push(Some(p_back_new)); - } else { - self.notary.rnodes[p_rnode].bits.push(None); + pub fn make_rnode_for_pstate( + &mut self, + p_state: PState, + lower_before_pruning: bool, + ) -> Option { + let nzbw = self.stator.states[p_state].nzbw; + let (_, p_external) = + self.notary + .insert_rnode(RNode::new(nzbw, Some(p_state), lower_before_pruning)); + Some(p_external) + } + + /// If the state is allowed to be pruned, `allow_pruned` can be set. + pub fn initialize_rnode_if_needed( + &mut self, + p_rnode: PRNode, + allow_pruned: bool, + ) -> Result<(), EvalError> { + let rnode = &self.notary.rnodes()[p_rnode]; + if rnode.bits.is_empty() { + if let Some(p_state) = rnode.associated_state { + if self.initialize_state_bits_if_needed(p_state).is_some() { + let len = self.stator.states[p_state].p_self_bits.len(); + for i in 0..len { + let p_bit = self.stator.states[p_state].p_self_bits[i]; + if let Some(p_bit) = p_bit { + let p_equiv = self.backrefs.get_val(p_bit).unwrap().p_self_equiv; + let p_back_new = self + .backrefs + .insert_key(p_equiv, Referent::ThisRNode(p_rnode)) + .unwrap(); + self.notary.rnodes[p_rnode].bits.push(Some(p_back_new)); + } else { + self.notary.rnodes[p_rnode].bits.push(None); + } + } + return Ok(()) + } + } + if !allow_pruned { + return Err(EvalError::OtherStr("failed to initialize `RNode`")) } } - Some(p_external) + Ok(()) } pub fn remove_rnode(&mut self, p_external: PExternal) -> Result<(), EvalError> { @@ -141,8 +179,8 @@ impl Ensemble { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - if let Some(rnode) = ensemble.notary.get_rnode(p_external) { - Ok(NonZeroUsize::new(rnode.bits.len()).unwrap()) + if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { + Ok(rnode.nzbw) } else { Err(EvalError::OtherStr( "could not find thread local `RNode`, probably an `EvalAwi` or `LazyAwi` was used \ @@ -160,24 +198,27 @@ impl Ensemble { let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; if let Some(p_rnode) = ensemble.notary.rnodes.find_key(&p_external) { - let rnode = ensemble.notary.rnodes.get_val(p_rnode).unwrap(); - if rnode.bits.len() != bits.bw() { - return Err(EvalError::WrongBitwidth); - } - for bit_i in 0..bits.bw() { - let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; - if let Some(p_back) = p_back { - if make_const { - ensemble - .change_value(p_back, Value::Const(bits.get(bit_i).unwrap())) - .unwrap(); - } else { - ensemble - .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) - .unwrap(); + ensemble.initialize_rnode_if_needed(p_rnode, true)?; + if !ensemble.notary.rnodes[p_rnode].bits.is_empty() { + if ensemble.notary.rnodes[p_rnode].bits.len() != bits.bw() { + return Err(EvalError::WrongBitwidth); + } + for bit_i in 0..bits.bw() { + let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; + if let Some(p_back) = p_back { + if make_const { + ensemble + .change_value(p_back, Value::Const(bits.get(bit_i).unwrap())) + .unwrap(); + } else { + ensemble + .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) + .unwrap(); + } } } } + // else the state was pruned } else { return Err(EvalError::OtherStr( "could not find thread local `RNode`, probably a `LazyAwi` was used outside of \ @@ -194,7 +235,10 @@ impl Ensemble { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; - let p_back = if let Some(rnode) = ensemble.notary.get_rnode(p_external) { + if let Some((p_rnode, _)) = ensemble.notary.get_rnode(p_external) { + ensemble.initialize_rnode_if_needed(p_rnode, false)?; + } + let p_back = if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { if bit_i >= rnode.bits.len() { return Err(EvalError::OtherStr( "something went wrong with rnode bitwidth", @@ -223,12 +267,6 @@ impl Ensemble { } } -impl Default for RNode { - fn default() -> Self { - Self::new() - } -} - impl Default for Notary { fn default() -> Self { Self::new() diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 661f34ea..50b59634 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -46,7 +46,8 @@ pub struct State { } impl State { - /// Returns if pruning this state is allowed + /// Returns if pruning this state is allowed. Internal or external + /// references prevent pruning. Custom `Opaque`s prevent pruning. pub fn pruning_allowed(&self) -> bool { (self.rc == 0) && (self.extern_rc == 0) && !matches!(self.op, Opaque(_, Some(_))) } @@ -120,7 +121,7 @@ impl Ensemble { } /// Prunes all states with `pruning_allowed()` - pub fn prune_states(&mut self) -> Result<(), EvalError> { + pub fn prune_unused_states(&mut self) -> Result<(), EvalError> { let mut adv = self.stator.states.advancer(); while let Some(p_state) = adv.advance(&self.stator.states) { let state = &self.stator.states[p_state]; @@ -298,21 +299,32 @@ impl Ensemble { Ok(()) } - pub fn lower_all(epoch_shared: &EpochShared) -> Result<(), EvalError> { + /// Lowers `RNode`s with the `lower_before_pruning` flag + pub fn lower_for_rnodes(epoch_shared: &EpochShared) -> Result<(), EvalError> { let lock = epoch_shared.epoch_data.borrow(); - let mut adv = lock.ensemble.stator.states.advancer(); + let mut adv = lock.ensemble.notary.rnodes().advancer(); drop(lock); loop { let lock = epoch_shared.epoch_data.borrow(); - if let Some(p_state) = adv.advance(&lock.ensemble.stator.states) { - // only do this to roots - let state = &lock.ensemble.stator.states[p_state]; - if state.rc == 0 { - drop(lock); - Ensemble::dfs_lower(epoch_shared, p_state)?; + if let Some(p_rnode) = adv.advance(&lock.ensemble.notary.rnodes()) { + // only lower state trees attached to rnodes that need lowering + let rnode = &lock.ensemble.notary.rnodes()[p_rnode]; + if rnode.lower_before_pruning { + let p_state = rnode.associated_state.unwrap(); + if lock.ensemble.stator.states.contains(p_state) { + drop(lock); + Ensemble::dfs_lower(epoch_shared, p_state)?; + } else { + drop(lock); + } } else { drop(lock); } + // tricky: need to be initialized + let mut lock = epoch_shared.epoch_data.borrow_mut(); + lock.ensemble + .initialize_rnode_if_needed(p_rnode, true) + .unwrap(); } else { break } diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 072a21fe..4fb03bdb 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -1,3 +1,5 @@ +// NOTE: remember to update the README when updating this + //! This is a DSL (Domain Specific Language) that can describe combinational //! logic and temporal logic. This allows RTL (Register Transfer Level) //! descriptions in ordinary Rust code with all the features that Rust provides. @@ -83,15 +85,14 @@ //! use awi::*; //! //! // discard all unused mimicking states so the render is cleaner -//! epoch.prune().unwrap(); +//! epoch.prune_unused_states().unwrap(); //! //! // See the mimicking state DAG before it is lowered //! epoch //! .render_to_svgs_in_dir(std::path::PathBuf::from("./".to_owned())) //! .unwrap(); //! -//! // lower into purely static bit movements and lookup tables. -//! epoch.lower().unwrap(); +//! // lower into purely static bit movements and lookup tables and optimize //! epoch.optimize().unwrap(); //! //! // Now the combinational logic is described in a DAG of lookup tables that we diff --git a/testcrate/benches/bench.rs b/testcrate/benches/bench.rs index 0dece4e1..ab0191d2 100644 --- a/testcrate/benches/bench.rs +++ b/testcrate/benches/bench.rs @@ -15,7 +15,6 @@ fn lower_funnel(bencher: &mut Bencher) { out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); epoch.prune().unwrap(); - epoch.lower().unwrap(); epoch.assert_assertions(true).unwrap(); }) } @@ -30,7 +29,6 @@ fn optimize_funnel(bencher: &mut Bencher) { let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch.prune().unwrap(); epoch.optimize().unwrap(); epoch.assert_assertions(true).unwrap(); }) diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index 11b61ac0..c06f4cfd 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -81,9 +81,7 @@ fn epoch_shared0() { drop(lazy0); drop(eval0); drop(epoch0); - // TODO unsure of what the precise semantics should be, currently unless all - // `RNode`s are removed and prune is called there is still stuff - epoch1.prune().unwrap(); + epoch1.prune_unused_states().unwrap(); awi::assert!(epoch1.ensemble(|ensemble| ensemble.stator.states.is_empty())); drop(epoch1); } diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 9865995d..3301f489 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -45,6 +45,8 @@ fn luts_states() { let opt_res = EvalAwi::from(&x); + epoch.lower().unwrap(); + epoch.optimize().unwrap(); input.retro_(&original_input).unwrap(); diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index f280ad07..cddcd281 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -11,7 +11,7 @@ fn stats_optimize_funnel() { let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch.prune().unwrap(); + epoch.prune_unused_states().unwrap(); epoch.lower().unwrap(); epoch.assert_assertions(true).unwrap(); epoch.ensemble(|ensemble| { From 4c79d8429d4c82da6f41662b2d98d15b2a2394d7 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 17:22:52 -0600 Subject: [PATCH 034/119] rename `prune` to `lower_and_prune` --- starlight/src/awi_structs/epoch.rs | 2 +- testcrate/benches/bench.rs | 2 +- testcrate/tests/fuzz_elementary.rs | 2 +- testcrate/tests/fuzz_lower.rs | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index eb7a8016..164237dc 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -797,7 +797,7 @@ impl Epoch { /// Aggressively prunes all states, lowering `RNode`s for `EvalAwi`s and /// `LazyAwi`s if necessary and evaluating assertions. Requires that `self` /// be the current `Epoch`. - pub fn prune(&self) -> Result<(), EvalError> { + pub fn lower_and_prune(&self) -> Result<(), EvalError> { let epoch_shared = self.check_current()?; Ensemble::lower_for_rnodes(&epoch_shared)?; // get rid of constant assertions diff --git a/testcrate/benches/bench.rs b/testcrate/benches/bench.rs index ab0191d2..92108dd2 100644 --- a/testcrate/benches/bench.rs +++ b/testcrate/benches/bench.rs @@ -14,7 +14,7 @@ fn lower_funnel(bencher: &mut Bencher) { let mut out = inlawi!(0u32); out.funnel_(&rhs, &s).unwrap(); let _eval = EvalAwi::from(&out); - epoch.prune().unwrap(); + epoch.lower_and_prune().unwrap(); epoch.assert_assertions(true).unwrap(); }) } diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 8f08dfd0..5ae07cd6 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -100,7 +100,7 @@ impl Mem { pair.eval = Some(EvalAwi::from(&pair.dag)) } // then pruning can be done safely - epoch.prune().unwrap(); + epoch.lower_and_prune().unwrap(); } pub fn verify_equivalence(&mut self, epoch: &Epoch) -> Result<(), EvalError> { diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index a585a1e7..ddec29cc 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -152,7 +152,7 @@ impl Mem { for pair in self.a.vals_mut() { pair.eval = Some(EvalAwi::from(&pair.dag)) } - epoch.prune().unwrap(); + epoch.lower_and_prune().unwrap(); } pub fn eval_and_verify_equal(&mut self, epoch: &Epoch) -> Result<(), EvalError> { @@ -164,8 +164,7 @@ impl Mem { lazy.retro_(&lit).unwrap(); } - // lower - epoch.lower().unwrap(); + // lowering was done by the finishing step epoch.assert_assertions(false).unwrap(); // set remaining lazy roots From 5ebd507e29573496267f2f6ddfea1497780ca263 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 17:24:48 -0600 Subject: [PATCH 035/119] fix warnings --- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/state.rs | 2 +- testcrate/tests/basic.rs | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 86c9aa60..e838c702 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -199,7 +199,7 @@ impl Ensemble { false } } - LNodeKind::DynamicLut(inp, lut) => { + LNodeKind::DynamicLut(_inp, _lut) => { // FIXME /* // acquire LUT inputs, for every constant input reduce the LUT diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 50b59634..a80e9b87 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -306,7 +306,7 @@ impl Ensemble { drop(lock); loop { let lock = epoch_shared.epoch_data.borrow(); - if let Some(p_rnode) = adv.advance(&lock.ensemble.notary.rnodes()) { + if let Some(p_rnode) = adv.advance(lock.ensemble.notary.rnodes()) { // only lower state trees attached to rnodes that need lowering let rnode = &lock.ensemble.notary.rnodes()[p_rnode]; if rnode.lower_before_pruning { diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 6762aef5..2359e61a 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -1,9 +1,4 @@ -use starlight::{ - awi, - dag::{self, *}, - ensemble::LNodeKind, - Epoch, EvalAwi, LazyAwi, StarRng, -}; +use starlight::{awi, dag::*, Epoch, EvalAwi, LazyAwi}; #[test] fn lazy_awi() -> Option<()> { From 20a8cf0c013a59eeea80066c5f819b9faa65d794 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 17:34:11 -0600 Subject: [PATCH 036/119] Update stats.rs --- testcrate/tests/stats.rs | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index cddcd281..bef4cbeb 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -27,3 +27,66 @@ fn stats_optimize_funnel() { awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); }); } + +// checks that states are being lowered and pruned at the right times and in the +// expected amounts, and also that some optimizations are working +#[test] +fn stats_different_prunings() { + let epoch = Epoch::new(); + + let num_ports = 2; + let w = bw(1); + let mut net = Net::zero(w); + for i in 0..num_ports { + let mut port = awi!(0u1); + port.usize_(i); + net.push(&port).unwrap(); + } + let lazy = LazyAwi::opaque(w); + let eval_net = EvalAwi::from(&net); + let res = net.drive(&lazy); + let eval_res = EvalAwi::from_bool(res.is_none()); + { + use awi::{assert_eq, *}; + + epoch.ensemble(|ensemble| { + assert_eq!(ensemble.notary.rnodes().len(), 3); + assert_eq!(ensemble.stator.states.len(), 22); + assert_eq!(ensemble.backrefs.len_keys(), 0); + assert_eq!(ensemble.backrefs.len_vals(), 0); + }); + epoch.lower().unwrap(); + epoch.ensemble(|ensemble| { + assert_eq!(ensemble.notary.rnodes().len(), 3); + assert_eq!(ensemble.stator.states.len(), 19); + assert_eq!(ensemble.backrefs.len_keys(), 31); + assert_eq!(ensemble.backrefs.len_vals(), 8); + }); + epoch.lower_and_prune().unwrap(); + epoch.ensemble(|ensemble| { + assert_eq!(ensemble.notary.rnodes().len(), 3); + assert_eq!(ensemble.stator.states.len(), 0); + assert_eq!(ensemble.backrefs.len_keys(), 20); + assert_eq!(ensemble.backrefs.len_vals(), 8); + }); + epoch.optimize().unwrap(); + epoch.ensemble(|ensemble| { + assert_eq!(ensemble.notary.rnodes().len(), 3); + assert_eq!(ensemble.stator.states.len(), 0); + assert_eq!(ensemble.backrefs.len_keys(), 8); + assert_eq!(ensemble.backrefs.len_vals(), 3); + }); + + for i in 0..(1 << w.get()) { + let mut inx = Awi::zero(w); + inx.usize_(i); + lazy.retro_(&inx).unwrap(); + epoch.drive_loops().unwrap(); + awi::assert_eq!(eval_res.eval().unwrap().to_bool(), i >= num_ports); + if i < num_ports { + awi::assert_eq!(eval_net.eval().unwrap().to_usize(), i); + } + } + drop(epoch); + } +} From d17a71c0fbd356f08112c8c477917a84b1a78b41 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 17:35:39 -0600 Subject: [PATCH 037/119] Update stats.rs --- testcrate/tests/stats.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index bef4cbeb..15f32fbf 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -55,7 +55,9 @@ fn stats_different_prunings() { assert_eq!(ensemble.backrefs.len_keys(), 0); assert_eq!(ensemble.backrefs.len_vals(), 0); }); + epoch.verify_integrity().unwrap(); epoch.lower().unwrap(); + epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); assert_eq!(ensemble.stator.states.len(), 19); @@ -63,6 +65,7 @@ fn stats_different_prunings() { assert_eq!(ensemble.backrefs.len_vals(), 8); }); epoch.lower_and_prune().unwrap(); + epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); assert_eq!(ensemble.stator.states.len(), 0); @@ -70,6 +73,7 @@ fn stats_different_prunings() { assert_eq!(ensemble.backrefs.len_vals(), 8); }); epoch.optimize().unwrap(); + epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); assert_eq!(ensemble.stator.states.len(), 0); From 06b1dc96a6c4ec478afa079b59aa6ffe080a9ddd Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 18:34:49 -0600 Subject: [PATCH 038/119] optimize LUTs in the state phase --- starlight/src/lower/meta.rs | 101 +++++++++++++++++++++++++++++------- testcrate/tests/stats.rs | 18 +++---- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 211e90b2..24dcf48c 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -5,7 +5,7 @@ use std::{cmp::min, mem, num::NonZeroUsize}; use awint::{ awint_dag::{ smallvec::{smallvec, SmallVec}, - ConcatFieldsType, + ConcatFieldsType, PState, }, bw, }; @@ -14,6 +14,7 @@ use crate::{ awi, awint_dag::{ConcatType, Lineage, Op}, dag::{awi, inlawi, inlawi_ty, Awi, Bits, InlAwi}, + ensemble::LNode, }; const USIZE_BITS: usize = usize::BITS as usize; @@ -28,20 +29,74 @@ const USIZE_BITS: usize = usize::BITS as usize; // TODO In the future if we want something more, we should have some kind of // caching for known optimization results. -// note that the $inx arguments are in order from least to most significant +// even though we have later stages that would optimize LUTs, we find it good to +// optimize as early as possible for this common case. +fn create_static_lut( + mut inxs: SmallVec<[PState; 4]>, + mut lut: awi::Awi, +) -> Result, PState> { + // acquire LUT inputs, for every constant input reduce the LUT + let len = usize::from(u8::try_from(inxs.len()).unwrap()); + for i in (0..len).rev() { + let p_state = inxs[i]; + if let Some(bit) = p_state.try_get_as_awi() { + assert_eq!(bit.bw(), 1); + inxs.remove(i); + lut = crate::ensemble::LNode::reduce_lut(&lut, i, bit.to_bool()); + } + } + + // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing + let len = inxs.len(); + for i in (0..len).rev() { + if lut.bw() > 1 { + if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { + // independent of the `i`th bit + lut = reduced; + inxs.remove(i); + } + } + } + + // input independence automatically reduces all zeros and all ones LUTs, so just + // need to check if the LUT is one bit for constant generation + if lut.bw() == 1 { + if lut.is_zero() { + Ok(Op::Literal(awi::Awi::zero(bw(1)))) + } else { + Ok(Op::Literal(awi::Awi::umax(bw(1)))) + } + } else if (lut.bw() == 2) && lut.get(1).unwrap() { + Err(inxs[0]) + } else { + Ok(Op::StaticLut( + ConcatType::from_iter(inxs.iter().cloned()), + lut, + )) + } +} + +// note that the $inx arguments are in order from least to most significant, and +// this assumes the LUT has a single output bit macro_rules! static_lut { ($lhs:ident; $lut:expr; $($inx:expr),*) => {{ - let nzbw = $lhs.state_nzbw(); - let op = Op::StaticLut( - ConcatType::from_iter([$( + //let nzbw = $lhs.state_nzbw(); + match create_static_lut( + smallvec![$( $inx.state(), - )*]), + )*], {use awi::*; awi!($lut)} - ); - $lhs.update_state( - nzbw, - op, - ).unwrap_at_runtime() + ) { + Ok(op) => { + $lhs.update_state( + bw(1), + op, + ).unwrap_at_runtime(); + } + Err(copy) => { + $lhs.set_state(copy); + } + } }}; } @@ -637,14 +692,20 @@ pub fn cin_sum(cin: &Bits, lhs: &Bits, rhs: &Bits) -> (Awi, inlawi_ty!(1), inlaw let mut out = SmallVec::with_capacity(nzbw.get()); let mut carry = InlAwi::from(cin.to_bool()); for i in 0..w { - let mut carry_sum = inlawi!(00); - static_lut!(carry_sum; 1110_1001_1001_0100; + let mut sum = inlawi!(0); + let mut next_carry = inlawi!(0); + static_lut!(sum; 1001_0110; + carry, + lhs.get(i).unwrap(), + rhs.get(i).unwrap() + ); + static_lut!(next_carry; 1110_1000; carry, lhs.get(i).unwrap(), rhs.get(i).unwrap() ); - out.push(carry_sum.get(0).unwrap().state()); - carry.bool_(carry_sum.get(1).unwrap()); + out.push(sum.state()); + carry = next_carry; } let mut signed_overflow = inlawi!(0); let a = lhs.get(w - 1).unwrap().state(); @@ -672,11 +733,13 @@ pub fn negator(x: &Bits, neg: &Bits) -> Awi { let mut out = SmallVec::with_capacity(nzbw.get()); let mut carry = InlAwi::from(neg.to_bool()); for i in 0..x.bw() { - let mut carry_sum = inlawi!(00); + let mut sum = inlawi!(0); + let mut next_carry = inlawi!(0); // half adder with input inversion control - static_lut!(carry_sum; 0100_1001_1001_0100; carry, x.get(i).unwrap(), neg); - out.push(carry_sum.get(0).unwrap().state()); - carry.bool_(carry_sum.get(1).unwrap()); + static_lut!(sum; 1001_0110; carry, x.get(i).unwrap(), neg); + static_lut!(next_carry; 0010_1000; carry, x.get(i).unwrap(), neg); + out.push(sum.state()); + carry = next_carry; } Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) } diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index 15f32fbf..d4a61aa9 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -15,9 +15,9 @@ fn stats_optimize_funnel() { epoch.lower().unwrap(); epoch.assert_assertions(true).unwrap(); epoch.ensemble(|ensemble| { - awi::assert_eq!(ensemble.stator.states.len(), 2436); - awi::assert_eq!(ensemble.backrefs.len_keys(), 8559); - awi::assert_eq!(ensemble.backrefs.len_vals(), 1317); + awi::assert_eq!(ensemble.stator.states.len(), 2356); + awi::assert_eq!(ensemble.backrefs.len_keys(), 8303); + awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); }); epoch.optimize().unwrap(); epoch.assert_assertions(true).unwrap(); @@ -51,7 +51,7 @@ fn stats_different_prunings() { epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); - assert_eq!(ensemble.stator.states.len(), 22); + assert_eq!(ensemble.stator.states.len(), 19); assert_eq!(ensemble.backrefs.len_keys(), 0); assert_eq!(ensemble.backrefs.len_vals(), 0); }); @@ -60,17 +60,17 @@ fn stats_different_prunings() { epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); - assert_eq!(ensemble.stator.states.len(), 19); - assert_eq!(ensemble.backrefs.len_keys(), 31); - assert_eq!(ensemble.backrefs.len_vals(), 8); + assert_eq!(ensemble.stator.states.len(), 18); + assert_eq!(ensemble.backrefs.len_keys(), 23); + assert_eq!(ensemble.backrefs.len_vals(), 6); }); epoch.lower_and_prune().unwrap(); epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); assert_eq!(ensemble.stator.states.len(), 0); - assert_eq!(ensemble.backrefs.len_keys(), 20); - assert_eq!(ensemble.backrefs.len_vals(), 8); + assert_eq!(ensemble.backrefs.len_keys(), 15); + assert_eq!(ensemble.backrefs.len_vals(), 6); }); epoch.optimize().unwrap(); epoch.verify_integrity().unwrap(); From 2c72d1ec4e5f63de5d8ba9c5e15b735afe480eb0 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 19:39:51 -0600 Subject: [PATCH 039/119] more optimization --- starlight/src/lower/meta.rs | 81 ++++++++++++++++--------------------- testcrate/tests/stats.rs | 4 +- 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 24dcf48c..2f04f43d 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -100,13 +100,30 @@ macro_rules! static_lut { }}; } +fn concat(nzbw: NonZeroUsize, vec: SmallVec<[PState; 4]>) -> Awi { + if vec.len() == 1 { + Awi::from_state(vec[0]) + } else { + Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(vec))) + } +} + +fn concat_update(bits: &mut Bits, nzbw: NonZeroUsize, vec: SmallVec<[PState; 4]>) { + if vec.len() == 1 { + bits.set_state(vec[0]); + } else { + bits.update_state(nzbw, Op::Concat(ConcatType::from_smallvec(vec))) + .unwrap_at_runtime(); + } +} + pub fn reverse(x: &Bits) -> Awi { let nzbw = x.nzbw(); let mut out = SmallVec::with_capacity(nzbw.get()); for i in 0..x.bw() { out.push(x.get(x.bw() - 1 - i).unwrap().state()) } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } /// Given `inx.bw()` bits, this returns `2^inx.bw()` signals for every possible @@ -163,7 +180,7 @@ pub fn selector_awi(inx: &Bits, cap: Option) -> Awi { } signals.push(signal.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(signals))) + concat(nzbw, signals) } /// Trailing smear, given the value of `inx` it will set all bits in the vector @@ -237,7 +254,7 @@ pub fn tsmear_awi(inx: &Bits, num_signals: usize) -> Awi { } signals.push(signal.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(signals))) + concat(nzbw, signals) } pub fn mux_(x0: &Bits, x1: &Bits, inx: &Bits) -> Awi { @@ -250,7 +267,7 @@ pub fn mux_(x0: &Bits, x1: &Bits, inx: &Bits) -> Awi { static_lut!(tmp; 1100_1010; x0.get(i).unwrap(), x1.get(i).unwrap(), inx); signals.push(tmp.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(signals))) + concat(nzbw, signals) } /* @@ -285,8 +302,7 @@ pub fn dynamic_to_static_lut(out: &mut Bits, table: &Bits, inx: &Bits) { } tmp_output.push(column.state()); } - out.update_state(nzbw, Op::Concat(ConcatType::from_smallvec(tmp_output))) - .unwrap_at_runtime(); + concat_update(out, nzbw, tmp_output) } pub fn dynamic_to_static_get(bits: &Bits, inx: &Bits) -> inlawi_ty!(1) { @@ -314,7 +330,7 @@ pub fn dynamic_to_static_set(bits: &Bits, inx: &Bits, bit: &Bits) -> Awi { static_lut!(tmp; 1101_1000; signal, bit, bits.get(i).unwrap()); out.push(tmp.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } pub fn resize(x: &Bits, w: NonZeroUsize, signed: bool) -> Awi { @@ -330,22 +346,10 @@ pub fn resize(x: &Bits, w: NonZeroUsize, signed: bool) -> Awi { NonZeroUsize::new(w.get() - x.bw()).unwrap(), Op::Repeat([x.msb().state()]), ); - Awi::new( - w, - Op::Concat(ConcatType::from_smallvec(smallvec![ - x.state(), - extension.state() - ])), - ) + concat(w, smallvec![x.state(), extension.state()]) } else { let zero = Awi::zero(NonZeroUsize::new(w.get() - x.bw()).unwrap()); - Awi::new( - w, - Op::Concat(ConcatType::from_smallvec(smallvec![ - x.state(), - zero.state() - ])), - ) + concat(w, smallvec![x.state(), zero.state()]) } } @@ -363,13 +367,7 @@ pub fn resize_cond(x: &Bits, w: NonZeroUsize, signed: &Bits) -> Awi { NonZeroUsize::new(w.get() - x.bw()).unwrap(), Op::Repeat([signed.state()]), ); - Awi::new( - w, - Op::Concat(ConcatType::from_smallvec(smallvec![ - x.state(), - extension.state() - ])), - ) + concat(w, smallvec![x.state(), extension.state()]) } } @@ -434,7 +432,7 @@ pub fn field_width(lhs: &Bits, rhs: &Bits, width: &Bits) -> Awi { static_lut!(tmp; 1100_1010; lhs.get(i).unwrap(), rhs.get(i).unwrap(), signal); mux_part.push(tmp.state()); } - let mux_part = Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(mux_part))); + let mux_part = concat(nzbw, mux_part); if let Some(lhs_rem_hi) = NonZeroUsize::new(lhs.bw() - nzbw.get()) { Awi::new( lhs.nzbw(), @@ -479,9 +477,7 @@ pub fn crossbar( } tmp_output.push(out_bar.state()); } - output - .update_state(nzbw, Op::Concat(ConcatType::from_smallvec(tmp_output))) - .unwrap_at_runtime(); + concat_update(output, nzbw, tmp_output) } pub fn funnel_(x: &Bits, s: &Bits) -> Awi { @@ -613,7 +609,7 @@ pub fn bitwise_not(x: &Bits) -> Awi { static_lut!(tmp; 01; x.get(i).unwrap()); out.push(tmp.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } pub fn bitwise(lhs: &Bits, rhs: &Bits, lut: awi::Awi) -> Awi { @@ -633,7 +629,7 @@ pub fn bitwise(lhs: &Bits, rhs: &Bits, lut: awi::Awi) -> Awi { .unwrap_at_runtime(); out.push(tmp.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } pub fn incrementer(x: &Bits, cin: &Bits, dec: bool) -> (Awi, inlawi_ty!(1)) { @@ -660,10 +656,7 @@ pub fn incrementer(x: &Bits, cin: &Bits, dec: bool) -> (Awi, inlawi_ty!(1)) { static_lut!(carry; 1000; carry, b); } } - ( - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))), - carry, - ) + (concat(nzbw, out), carry) } // TODO select carry adder @@ -720,11 +713,7 @@ pub fn cin_sum(cin: &Bits, lhs: &Bits, rhs: &Bits) -> (Awi, inlawi_ty!(1), inlaw }), ) .unwrap_at_runtime(); - ( - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))), - carry, - signed_overflow, - ) + (concat(nzbw, out), carry, signed_overflow) } pub fn negator(x: &Bits, neg: &Bits) -> Awi { @@ -741,7 +730,7 @@ pub fn negator(x: &Bits, neg: &Bits) -> Awi { out.push(sum.state()); carry = next_carry; } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } /// Setting `width` to 0 guarantees that nothing happens even with other @@ -791,7 +780,7 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { ); out.push(lut_out.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } else { let lut = inlawi!(rhs[0], lhs[0]).unwrap(); let mut out = awi!(0); @@ -859,7 +848,7 @@ pub fn field(lhs: &Bits, to: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Aw ); out.push(lut_out.state()); } - Awi::new(nzbw, Op::Concat(ConcatType::from_smallvec(out))) + concat(nzbw, out) } else { // `lhs.bw() == 1`, `rhs.bw() == 1`, `width` is the only thing that matters let lut = inlawi!(rhs[0], lhs[0]).unwrap(); diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index d4a61aa9..8609c529 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -60,8 +60,8 @@ fn stats_different_prunings() { epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); - assert_eq!(ensemble.stator.states.len(), 18); - assert_eq!(ensemble.backrefs.len_keys(), 23); + assert_eq!(ensemble.stator.states.len(), 17); + assert_eq!(ensemble.backrefs.len_keys(), 22); assert_eq!(ensemble.backrefs.len_vals(), 6); }); epoch.lower_and_prune().unwrap(); From de131cffe9e6bb26ff45558c3025b06b7bb3114e Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 19:48:21 -0600 Subject: [PATCH 040/119] use `debug_` in most places --- starlight/src/ensemble/lnode.rs | 6 ++-- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/rnode.rs | 2 +- starlight/src/ensemble/state.rs | 22 ++++++------ starlight/src/lower/lower_op.rs | 2 +- starlight/src/lower/meta.rs | 58 +++++++++++++++--------------- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 4aab82e1..e044c396 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -106,7 +106,7 @@ impl LNode { /// Reduce a LUT in half by saving entries indexed by setting the `i`th /// input bit to `bit` pub fn reduce_lut(lut: &Bits, i: usize, bit: bool) -> Awi { - assert!(lut.bw().is_power_of_two()); + debug_assert!(lut.bw().is_power_of_two()); let next_bw = lut.bw() / 2; let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); let w = 1 << i; @@ -129,7 +129,7 @@ impl LNode { i: usize, bit: bool, ) -> (Vec, Vec) { - assert!(lut.len().is_power_of_two()); + debug_assert!(lut.len().is_power_of_two()); let next_bw = lut.len() / 2; let mut next_lut = vec![DynamicValue::Unknown; next_bw]; let mut removed = Vec::with_capacity(next_bw); @@ -154,7 +154,7 @@ impl LNode { #[must_use] pub fn reduce_independent_lut(lut: &Bits, i: usize) -> Option { let nzbw = lut.nzbw(); - assert!(nzbw.get().is_power_of_two()); + debug_assert!(nzbw.get().is_power_of_two()); let next_bw = nzbw.get() / 2; let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); let mut tmp0 = Awi::zero(next_nzbw); diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index e838c702..5f886a89 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -518,7 +518,7 @@ impl Ensemble { } Referent::LoopDriver(p_driver) => { let tnode = self.tnodes.get_mut(p_driver).unwrap(); - assert_eq!(tnode.p_driver, p_back); + debug_assert_eq!(tnode.p_driver, p_back); let p_back_new = self .backrefs .insert_key(p_source, Referent::LoopDriver(p_driver)) diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index b28679c8..deab405c 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -166,7 +166,7 @@ impl Ensemble { for p_back in rnode.bits { if let Some(p_back) = p_back { let referent = self.backrefs.remove_key(p_back).unwrap().0; - assert!(matches!(referent, Referent::ThisRNode(_))); + debug_assert!(matches!(referent, Referent::ThisRNode(_))); } } Ok(()) diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index a80e9b87..8864506e 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -157,7 +157,7 @@ impl Ensemble { // lost if this was done after initializing `p_self_bits` let state = &mut self.stator.states[p_state]; if !state.p_self_bits.is_empty() { - assert_eq!(state.p_self_bits.len(), x.bw()); + debug_assert_eq!(state.p_self_bits.len(), x.bw()); for i in 0..x.bw() { if let Some(p_bit) = state.p_self_bits[i] { let p_equiv = self.backrefs.get_val(p_bit).unwrap().p_self_equiv; @@ -191,7 +191,7 @@ impl Ensemble { // this can be done because `Assert` is a sink that should not be used by // anything let state = self.stator.states.get_mut(p_state).unwrap(); - assert_eq!(state.rc, 0); + debug_assert_eq!(state.rc, 0); self.remove_state(p_state).unwrap(); Ok(()) } else { @@ -238,7 +238,7 @@ impl Ensemble { // reached a root match self.stator.states[p_state].op { Literal(ref lit) => { - assert_eq!(lit.nzbw(), nzbw); + debug_assert_eq!(lit.nzbw(), nzbw); self.initialize_state_bits_if_needed(p_state).unwrap(); } Opaque(_, name) => { @@ -344,7 +344,7 @@ fn lower_elementary_to_lnodes_intermediate( // this is the only foolproof way of doing this, at least without more // branches let len = this.stator.states[p_state].p_self_bits.len(); - assert_eq!(len, this.stator.states[x].p_self_bits.len()); + debug_assert_eq!(len, this.stator.states[x].p_self_bits.len()); for i in 0..len { let p_equiv0 = this.stator.states[p_state].p_self_bits[i].unwrap(); let p_equiv1 = this.stator.states[x].p_self_bits[i].unwrap(); @@ -355,7 +355,7 @@ fn lower_elementary_to_lnodes_intermediate( // this is the only foolproof way of doing this, at least without more // branches let len = this.stator.states[p_state].p_self_bits.len(); - assert_eq!(len, this.stator.states[x].p_self_bits.len()); + debug_assert_eq!(len, this.stator.states[x].p_self_bits.len()); for i in 0..len { let p_equiv0 = this.stator.states[p_state].p_self_bits[i].unwrap(); let p_equiv1 = this.stator.states[x].p_self_bits[i].unwrap(); @@ -364,9 +364,9 @@ fn lower_elementary_to_lnodes_intermediate( } StaticGet([bits], inx) => { let len = this.stator.states[bits].p_self_bits.len(); - assert!(inx < len); + debug_assert!(inx < len); let p_self_bits = &this.stator.states[p_state].p_self_bits; - assert_eq!(p_self_bits.len(), 1); + debug_assert_eq!(p_self_bits.len(), 1); let p_equiv0 = p_self_bits[0].unwrap(); let p_equiv1 = this.stator.states[bits].p_self_bits[inx].unwrap(); this.union_equiv(p_equiv0, p_equiv1).unwrap(); @@ -389,7 +389,7 @@ fn lower_elementary_to_lnodes_intermediate( } to += len; } - assert_eq!(total_len, to); + debug_assert_eq!(total_len, to); } ConcatFields(ref concat) => { let concat_len = concat.len(); @@ -410,12 +410,12 @@ fn lower_elementary_to_lnodes_intermediate( } to += len; } - assert_eq!(total_len, to); + debug_assert_eq!(total_len, to); } Repeat([x]) => { let len = this.stator.states[p_state].p_self_bits.len(); let x_w = this.stator.states[x].p_self_bits.len(); - assert!((len % x_w) == 0); + debug_assert!((len % x_w) == 0); let mut from = 0; for to in 0..len { if from >= x_w { @@ -445,7 +445,7 @@ fn lower_elementary_to_lnodes_intermediate( let out_bw = this.stator.states[p_state].p_self_bits.len(); let num_entries = 1usize.checked_shl(u32::try_from(inx_len).unwrap()).unwrap(); // this must be handled upstream - assert_eq!(out_bw * num_entries, lut.bw()); + debug_assert_eq!(out_bw * num_entries, lut.bw()); // convert from multiple out to single out bit lut for bit_i in 0..out_bw { let single_bit_lut = if out_bw == 1 { diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index bcc9e095..80465b0d 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -384,7 +384,7 @@ pub fn lower_op( Neg([x, neg]) => { let x = Awi::opaque(m.get_nzbw(x)); let neg = Awi::opaque(m.get_nzbw(neg)); - assert_eq!(neg.bw(), 1); + debug_assert_eq!(neg.bw(), 1); let out = negator(&x, &neg); m.graft(&[out.state(), x.state(), neg.state()]); } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 2f04f43d..5a018bb3 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -40,7 +40,7 @@ fn create_static_lut( for i in (0..len).rev() { let p_state = inxs[i]; if let Some(bit) = p_state.try_get_as_awi() { - assert_eq!(bit.bw(), 1); + debug_assert_eq!(bit.bw(), 1); inxs.remove(i); lut = crate::ensemble::LNode::reduce_lut(&lut, i, bit.to_bool()); } @@ -258,8 +258,8 @@ pub fn tsmear_awi(inx: &Bits, num_signals: usize) -> Awi { } pub fn mux_(x0: &Bits, x1: &Bits, inx: &Bits) -> Awi { - assert_eq!(x0.bw(), x1.bw()); - assert_eq!(inx.bw(), 1); + debug_assert_eq!(x0.bw(), x1.bw()); + debug_assert_eq!(inx.bw(), 1); let nzbw = x0.nzbw(); let mut signals = SmallVec::with_capacity(nzbw.get()); for i in 0..x0.bw() { @@ -291,7 +291,7 @@ y_1 = (s_0 && x_1_0) || (s_1 && x_1_1) || ... */ pub fn dynamic_to_static_lut(out: &mut Bits, table: &Bits, inx: &Bits) { // if this is broken it breaks a lot of stuff - assert!(table.bw() == (out.bw().checked_mul(1 << inx.bw()).unwrap())); + debug_assert!(table.bw() == (out.bw().checked_mul(1 << inx.bw()).unwrap())); let signals = selector(inx, None); let nzbw = out.nzbw(); let mut tmp_output = SmallVec::with_capacity(nzbw.get()); @@ -354,7 +354,7 @@ pub fn resize(x: &Bits, w: NonZeroUsize, signed: bool) -> Awi { } pub fn resize_cond(x: &Bits, w: NonZeroUsize, signed: &Bits) -> Awi { - assert_eq!(signed.bw(), 1); + debug_assert_eq!(signed.bw(), 1); if w == x.nzbw() { Awi::from_bits(x) } else if w < x.nzbw() { @@ -457,8 +457,8 @@ pub fn crossbar( signals: &[inlawi_ty!(1)], signal_range: (usize, usize), ) { - assert!(signal_range.0 < signal_range.1); - assert_eq!(signal_range.1 - signal_range.0, signals.len()); + debug_assert!(signal_range.0 < signal_range.1); + debug_assert_eq!(signal_range.1 - signal_range.0, signals.len()); let nzbw = output.nzbw(); let mut tmp_output = SmallVec::with_capacity(nzbw.get()); @@ -481,8 +481,8 @@ pub fn crossbar( } pub fn funnel_(x: &Bits, s: &Bits) -> Awi { - assert_eq!(x.bw() & 1, 0); - assert_eq!(x.bw() / 2, 1 << s.bw()); + debug_assert_eq!(x.bw() & 1, 0); + debug_assert_eq!(x.bw() / 2, 1 << s.bw()); let mut out = Awi::zero(NonZeroUsize::new(x.bw() / 2).unwrap()); let signals = selector(s, None); // select zero should connect the zeroeth crossbars, so the offset is `out.bw() @@ -495,8 +495,8 @@ pub fn funnel_(x: &Bits, s: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { - assert_eq!(from.bw(), USIZE_BITS); - assert_eq!(width.bw(), USIZE_BITS); + debug_assert_eq!(from.bw(), USIZE_BITS); + debug_assert_eq!(width.bw(), USIZE_BITS); let mut out = Awi::from_bits(lhs); // the `width == 0` case will result in a no-op from the later `field_width` // part, so we need to be able to handle just `rhs.bw()` possible shifts for @@ -512,7 +512,7 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { } pub fn shl(x: &Bits, s: &Bits) -> Awi { - assert_eq!(s.bw(), USIZE_BITS); + debug_assert_eq!(s.bw(), USIZE_BITS); let mut signals = selector(s, Some(x.bw())); signals.reverse(); let mut out = Awi::zero(x.nzbw()); @@ -521,7 +521,7 @@ pub fn shl(x: &Bits, s: &Bits) -> Awi { } pub fn lshr(x: &Bits, s: &Bits) -> Awi { - assert_eq!(s.bw(), USIZE_BITS); + debug_assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); let mut out = Awi::zero(x.nzbw()); crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); @@ -529,7 +529,7 @@ pub fn lshr(x: &Bits, s: &Bits) -> Awi { } pub fn ashr(x: &Bits, s: &Bits) -> Awi { - assert_eq!(s.bw(), USIZE_BITS); + debug_assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); let mut out = Awi::zero(x.nzbw()); crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); @@ -569,7 +569,7 @@ pub fn ashr(x: &Bits, s: &Bits) -> Awi { } pub fn rotl(x: &Bits, s: &Bits) -> Awi { - assert_eq!(s.bw(), USIZE_BITS); + debug_assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); // we will use the whole cross bar, with every signal controlling two diagonals // for the wraparound except for the `x.bw() - 1` one @@ -586,7 +586,7 @@ pub fn rotl(x: &Bits, s: &Bits) -> Awi { } pub fn rotr(x: &Bits, s: &Bits) -> Awi { - assert_eq!(s.bw(), USIZE_BITS); + debug_assert_eq!(s.bw(), USIZE_BITS); let signals = selector(s, Some(x.bw())); // we will use the whole cross bar, with every signal controlling two diagonals // for the wraparound except for the `x.bw() - 1` one @@ -613,8 +613,8 @@ pub fn bitwise_not(x: &Bits) -> Awi { } pub fn bitwise(lhs: &Bits, rhs: &Bits, lut: awi::Awi) -> Awi { - assert_eq!(lhs.bw(), rhs.bw()); - assert_eq!(lut.bw(), 4); + debug_assert_eq!(lhs.bw(), rhs.bw()); + debug_assert_eq!(lut.bw(), 4); let nzbw = lhs.nzbw(); let mut out = SmallVec::with_capacity(nzbw.get()); for i in 0..lhs.bw() { @@ -633,7 +633,7 @@ pub fn bitwise(lhs: &Bits, rhs: &Bits, lut: awi::Awi) -> Awi { } pub fn incrementer(x: &Bits, cin: &Bits, dec: bool) -> (Awi, inlawi_ty!(1)) { - assert_eq!(cin.bw(), 1); + debug_assert_eq!(cin.bw(), 1); let nzbw = x.nzbw(); let mut out = SmallVec::with_capacity(nzbw.get()); let mut carry = InlAwi::from(cin.to_bool()); @@ -678,8 +678,8 @@ for i in 0..lb { } */ pub fn cin_sum(cin: &Bits, lhs: &Bits, rhs: &Bits) -> (Awi, inlawi_ty!(1), inlawi_ty!(1)) { - assert_eq!(cin.bw(), 1); - assert_eq!(lhs.bw(), rhs.bw()); + debug_assert_eq!(cin.bw(), 1); + debug_assert_eq!(lhs.bw(), rhs.bw()); let w = lhs.bw(); let nzbw = lhs.nzbw(); let mut out = SmallVec::with_capacity(nzbw.get()); @@ -717,7 +717,7 @@ pub fn cin_sum(cin: &Bits, lhs: &Bits, rhs: &Bits) -> (Awi, inlawi_ty!(1), inlaw } pub fn negator(x: &Bits, neg: &Bits) -> Awi { - assert_eq!(neg.bw(), 1); + debug_assert_eq!(neg.bw(), 1); let nzbw = x.nzbw(); let mut out = SmallVec::with_capacity(nzbw.get()); let mut carry = InlAwi::from(neg.to_bool()); @@ -736,8 +736,8 @@ pub fn negator(x: &Bits, neg: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { - assert_eq!(to.bw(), USIZE_BITS); - assert_eq!(width.bw(), USIZE_BITS); + debug_assert_eq!(to.bw(), USIZE_BITS); + debug_assert_eq!(width.bw(), USIZE_BITS); // simplified version of `field` below @@ -792,9 +792,9 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field(lhs: &Bits, to: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { - assert_eq!(to.bw(), USIZE_BITS); - assert_eq!(from.bw(), USIZE_BITS); - assert_eq!(width.bw(), USIZE_BITS); + debug_assert_eq!(to.bw(), USIZE_BITS); + debug_assert_eq!(from.bw(), USIZE_BITS); + debug_assert_eq!(width.bw(), USIZE_BITS); // we use some summation to get the fielding done with a single crossbar @@ -981,7 +981,7 @@ pub fn significant_bits(x: &Bits) -> Awi { pub fn lut_set(table: &Bits, entry: &Bits, inx: &Bits) -> Awi { let num_entries = 1 << inx.bw(); - assert_eq!(table.bw(), entry.bw() * num_entries); + debug_assert_eq!(table.bw(), entry.bw() * num_entries); let signals = selector(inx, Some(num_entries)); let mut out = Awi::from_bits(table); for (j, signal) in signals.into_iter().enumerate() { @@ -1089,7 +1089,7 @@ pub fn mul_add(out_w: NonZeroUsize, add: Option<&Bits>, lhs: &Bits, rhs: &Bits) /// multiplication. TODO try out other algorithms in the `specialized-div-rem` /// crate for this implementation. pub fn division(duo: &Bits, div: &Bits) -> (Awi, Awi) { - assert_eq!(duo.bw(), div.bw()); + debug_assert_eq!(duo.bw(), div.bw()); // this uses the nonrestoring SWAR algorithm, with `duo` and `div` extended by // one bit so we don't need one of the edge case handlers. TODO can we From fb991b0b6ba902bef8c4368ae680a194cf35e771 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 20:41:10 -0600 Subject: [PATCH 041/119] optimize lut reduction --- starlight/src/ensemble/lnode.rs | 62 +++++++++++++++++++++++------- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/value.rs | 2 +- starlight/src/lower/meta.rs | 2 +- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index e044c396..976c7e96 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -50,6 +50,47 @@ impl Recast for LNode { } } +fn general_reduce_lut(lut: &Awi, i: usize, bit: bool) -> Awi { + let next_bw = lut.bw() / 2; + let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); + let w = 1 << i; + let mut from = 0; + let mut to = 0; + while to < next_bw { + next_lut + .field(to, lut, if bit { from + w } else { from }, w) + .unwrap(); + from += 2 * w; + to += w; + } + next_lut +} + +const M: [u64; 6] = [ + 0x5555_5555_5555_5555, + 0x3333_3333_3333_3333, + 0x0f0f_0f0f_0f0f_0f0f, + 0x00ff_00ff_00ff_00ff, + 0x0000_ffff_0000_ffff, + 0x0000_0000_ffff_ffff, +]; +const A: [u64; 5] = [ + 0x1111_1111_1111_1111, + 0x0303_0303_0303_0303, + 0x000f_000f_000f_000f, + 0x0000_00ff_0000_00ff, + 0x0000_0000_0000_ffff, +]; +// This can quickly reduce LUTs with bitwidths less than 64 +fn reduce64(mut lut: u64, i: usize, bit: bool) -> u64 { + lut >>= (bit as usize) << i; + lut &= M[i]; + for i in i..5 { + lut = (lut & A[i]) | ((lut & !A[i]) >> (1 << i)); + } + lut +} + impl LNode { pub fn new(p_self: PBack, kind: LNodeKind, lowered_from: Option) -> Self { Self { @@ -105,21 +146,16 @@ impl LNode { /// Reduce a LUT in half by saving entries indexed by setting the `i`th /// input bit to `bit` - pub fn reduce_lut(lut: &Bits, i: usize, bit: bool) -> Awi { + pub fn reduce_lut(lut: &mut Awi, i: usize, bit: bool) { debug_assert!(lut.bw().is_power_of_two()); - let next_bw = lut.bw() / 2; - let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); - let w = 1 << i; - let mut from = 0; - let mut to = 0; - while to < next_bw { - next_lut - .field(to, lut, if bit { from + w } else { from }, w) - .unwrap(); - from += 2 * w; - to += w; + let half = NonZeroUsize::new(lut.bw() / 2).unwrap(); + if lut.bw() > 64 { + *lut = general_reduce_lut(lut, i, bit); + } else { + let halved = reduce64(lut.to_u64(), i, bit); + lut.zero_resize(half); + lut.u64_(halved); } - next_lut } /// The same as `reduce_lut`, except for a dynamic table, and it returns diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 5f886a89..a8400b30 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -123,7 +123,7 @@ impl Ensemble { .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); self.backrefs.remove_key(p_inp).unwrap(); inp.remove(i); - lut = LNode::reduce_lut(&lut, i, val); + LNode::reduce_lut(&mut lut, i, val); } } diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index e1f81a53..5ddef751 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -271,7 +271,7 @@ impl Ensemble { // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { - lut = LNode::reduce_lut(&lut, i, inp_val.get(i).unwrap()); + LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); } } // if the LUT is all ones or all zeros, we can know that any unfixed or diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 5a018bb3..d58835c3 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -42,7 +42,7 @@ fn create_static_lut( if let Some(bit) = p_state.try_get_as_awi() { debug_assert_eq!(bit.bw(), 1); inxs.remove(i); - lut = crate::ensemble::LNode::reduce_lut(&lut, i, bit.to_bool()); + crate::ensemble::LNode::reduce_lut(&mut lut, i, bit.to_bool()); } } From 30fc16299f20dd3eedcca683dc319c4907b9c42f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 20:45:18 -0600 Subject: [PATCH 042/119] improve signatures --- starlight/src/ensemble/lnode.rs | 9 +++++---- starlight/src/ensemble/optimize.rs | 17 +++++++---------- starlight/src/ensemble/value.rs | 2 +- starlight/src/lower/meta.rs | 9 +++------ 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 976c7e96..e63cf9f2 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -6,7 +6,7 @@ use awint::{ triple_arena::{Recast, Recaster}, PState, }, - Awi, Bits, + Awi, }; use smallvec::SmallVec; @@ -188,7 +188,7 @@ impl LNode { /// Returns an equivalent reduced LUT (with the `i`th index removed) if the /// LUT output is independent with respect to the `i`th bit #[must_use] - pub fn reduce_independent_lut(lut: &Bits, i: usize) -> Option { + pub fn reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { let nzbw = lut.nzbw(); debug_assert!(nzbw.get().is_power_of_two()); let next_bw = nzbw.get() / 2; @@ -213,9 +213,10 @@ impl LNode { to += w; } if tmp0 == tmp1 { - Some(tmp0) + *lut = tmp0; + true } else { - None + false } } } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index a8400b30..06987565 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -163,16 +163,13 @@ impl Ensemble { // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing let len = inp.len(); for i in (0..len).rev() { - if lut.bw() > 1 { - if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { - // independent of the `i`th bit - lut = reduced; - let p_inp = inp.remove(i); - let equiv = self.backrefs.get_val(p_inp).unwrap(); - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(p_inp).unwrap(); - } + if (lut.bw() > 1) && LNode::reduce_independent_lut(&mut lut, i) { + // independent of the `i`th bit + let p_inp = inp.remove(i); + let equiv = self.backrefs.get_val(p_inp).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); } } // sort inputs so that `LNode`s can be compared later diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 5ddef751..2b6a8ead 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -258,7 +258,7 @@ impl Ensemble { for i in 0..len { if fixed.get(i).unwrap() && unknown.get(i).unwrap() - && LNode::reduce_independent_lut(&lut, i).is_none() + && (!LNode::reduce_independent_lut(&mut lut.clone(), i)) { self.evaluator.insert(Eval::Change(Change { depth, diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index d58835c3..9cb68775 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -49,12 +49,9 @@ fn create_static_lut( // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing let len = inxs.len(); for i in (0..len).rev() { - if lut.bw() > 1 { - if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { - // independent of the `i`th bit - lut = reduced; - inxs.remove(i); - } + if (lut.bw() > 1) && LNode::reduce_independent_lut(&mut lut, i) { + // independent of the `i`th bit + inxs.remove(i); } } From 036aa43ad8c052024d0110f791f99dd9a30540f5 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 29 Dec 2023 20:54:20 -0600 Subject: [PATCH 043/119] optimize independence detection --- starlight/src/ensemble/lnode.rs | 77 ++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index e63cf9f2..08e4d54a 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -66,6 +66,38 @@ fn general_reduce_lut(lut: &Awi, i: usize, bit: bool) -> Awi { next_lut } +fn general_reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { + let nzbw = lut.nzbw(); + debug_assert!(nzbw.get().is_power_of_two()); + let next_bw = nzbw.get() / 2; + let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); + let mut tmp0 = Awi::zero(next_nzbw); + let mut tmp1 = Awi::zero(next_nzbw); + let w = 1 << i; + // LUT if the `i`th bit were 0 + let mut from = 0; + let mut to = 0; + while to < next_bw { + tmp0.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + // LUT if the `i`th bit were 1 + from = w; + to = 0; + while to < next_bw { + tmp1.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + if tmp0 == tmp1 { + *lut = tmp0; + true + } else { + false + } +} + const M: [u64; 6] = [ 0x5555_5555_5555_5555, 0x3333_3333_3333_3333, @@ -90,6 +122,19 @@ fn reduce64(mut lut: u64, i: usize, bit: bool) -> u64 { } lut } +fn reduce_independent64(mut lut: u64, i: usize) -> Option { + let tmp0 = lut & M[i]; + let tmp1 = lut & !M[i]; + if tmp0 == (tmp1 >> (1 << i)) { + lut = tmp0; + for i in i..5 { + lut = (lut & A[i]) | ((lut & !A[i]) >> (1 << i)); + } + Some(lut) + } else { + None + } +} impl LNode { pub fn new(p_self: PBack, kind: LNodeKind, lowered_from: Option) -> Self { @@ -189,31 +234,13 @@ impl LNode { /// LUT output is independent with respect to the `i`th bit #[must_use] pub fn reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { - let nzbw = lut.nzbw(); - debug_assert!(nzbw.get().is_power_of_two()); - let next_bw = nzbw.get() / 2; - let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); - let mut tmp0 = Awi::zero(next_nzbw); - let mut tmp1 = Awi::zero(next_nzbw); - let w = 1 << i; - // LUT if the `i`th bit were 0 - let mut from = 0; - let mut to = 0; - while to < next_bw { - tmp0.field(to, lut, from, w).unwrap(); - from += 2 * w; - to += w; - } - // LUT if the `i`th bit were 1 - from = w; - to = 0; - while to < next_bw { - tmp1.field(to, lut, from, w).unwrap(); - from += 2 * w; - to += w; - } - if tmp0 == tmp1 { - *lut = tmp0; + debug_assert!(lut.bw().is_power_of_two()); + let half = NonZeroUsize::new(lut.bw() / 2).unwrap(); + if lut.bw() > 64 { + general_reduce_independent_lut(lut, i) + } else if let Some(halved) = reduce_independent64(lut.to_u64(), i) { + lut.zero_resize(half); + lut.u64_(halved); true } else { false From cc43cc30ddcee98badcd9323dad1b9f82ab10623 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 30 Dec 2023 12:42:47 -0600 Subject: [PATCH 044/119] working on dynamic LUTs --- starlight/src/ensemble/state.rs | 22 ++++- starlight/src/ensemble/together.rs | 55 +++++++++++++ starlight/src/ensemble/value.rs | 127 ++++++++++++++++++++++++++++- starlight/src/lower/lower_state.rs | 3 + 4 files changed, 204 insertions(+), 3 deletions(-) diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 8864506e..c101232e 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -12,7 +12,7 @@ use crate::{ awi, ensemble::{ value::{Change, Eval}, - Ensemble, PBack, Value, + DynamicValue, Ensemble, PBack, Value, }, epoch::EpochShared, }; @@ -464,6 +464,26 @@ fn lower_elementary_to_lnodes_intermediate( this.union_equiv(p_equiv0, p_equiv1).unwrap(); } } + Mux([lhs, rhs, b]) => { + let inx_bit = &this.stator.states[b].p_self_bits; + debug_assert_eq!(inx_bit.len(), 1); + let inx_bit = inx_bit[0]; + + let out_bw = this.stator.states[p_state].p_self_bits.len(); + for bit_i in 0..out_bw { + let lut0 = this.stator.states[lhs].p_self_bits[0].unwrap(); + let lut1 = this.stator.states[rhs].p_self_bits[0].unwrap(); + let p_equiv0 = this + .make_dynamic_lut( + &[inx_bit], + &[DynamicValue::Dynam(lut0), DynamicValue::Dynam(lut1)], + Some(p_state), + ) + .unwrap(); + let p_equiv1 = this.stator.states[p_state].p_self_bits[bit_i].unwrap(); + this.union_equiv(p_equiv0, p_equiv1).unwrap(); + } + } Opaque(ref v, name) => { if name == Some("LoopSource") { if v.len() != 1 { diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 0fd2d207..077daec5 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -9,6 +9,7 @@ use awint::{ Awi, Bits, }; +use super::DynamicValue; use crate::{ ensemble::{ value::Evaluator, LNode, LNodeKind, Notary, Optimizer, PLNode, PRNode, PTNode, State, @@ -550,6 +551,60 @@ impl Ensemble { Some(p_equiv) } + #[must_use] + pub fn make_dynamic_lut( + &mut self, + p_inxs: &[Option], + p_lut_bits: &[DynamicValue], + lowered_from: Option, + ) -> Option { + let num_entries = 1 << p_inxs.len(); + if p_lut_bits.len() != num_entries { + return None + } + for p_inx in p_inxs { + if let Some(p_inx) = p_inx { + if !self.backrefs.contains(*p_inx) { + return None + } + } + } + let p_equiv = self.backrefs.insert_with(|p_self_equiv| { + ( + Referent::ThisEquiv, + Equiv::new(p_self_equiv, Value::Unknown), + ) + }); + self.lnodes.insert_with(|p_lnode| { + let p_self = self + .backrefs + .insert_key(p_equiv, Referent::ThisLNode(p_lnode)) + .unwrap(); + let mut inp = smallvec![]; + for p_inx in p_inxs { + let p_back = self + .backrefs + .insert_key(p_inx.unwrap(), Referent::Input(p_lnode)) + .unwrap(); + inp.push(p_back); + } + let mut lut = vec![]; + for p_lut_bit in p_lut_bits { + if let DynamicValue::Dynam(p_lut_bit) = p_lut_bit { + let p_back = self + .backrefs + .insert_key(*p_lut_bit, Referent::Input(p_lnode)) + .unwrap(); + lut.push(DynamicValue::Dynam(p_back)); + } else { + lut.push(*p_lut_bit); + } + } + LNode::new(p_self, LNodeKind::DynamicLut(inp, lut), lowered_from) + }); + Some(p_equiv) + } + /// Sets up a loop from the loop source `p_looper` and driver `p_driver` #[must_use] pub fn make_loop( diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 2b6a8ead..de0455b3 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -1,11 +1,11 @@ use std::num::{NonZeroU64, NonZeroUsize}; use awint::{ + awi::*, awint_dag::{ triple_arena::{ptr_struct, Advancer, OrdArena}, EvalError, }, - Awi, }; use crate::{ @@ -268,10 +268,134 @@ impl Ensemble { return vec![]; } } + // FIXME does this go before and catch some more cases that can eval to a known + // val reduce the LUT based on fixed and known bits + for i in (0..len).rev() { + if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { + LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); + } + } + // if the LUT is all ones or all zeros, we can know that any unfixed or + // unknown changes will be unable to affect the + // output + if lut.is_zero() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(false), + })); + return vec![]; + } else if lut.is_umax() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(true), + })); + return vec![]; + } + // TODO prioritize bits that could lead to number_a optimization + /*let mut skip = 0; + for i in 0..len { + if fixed.get(i).unwrap() && !unknown.get(i).unwrap() { + skip += 1; + } else if unknown.get(i).unwrap() { + // assume unchanging + lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); + // + } else {} + }*/ + for i in (0..len).rev() { + if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { + res.push(RequestLNode { + depth: depth - 1, + number_a: 0, + p_back_lnode: inp[i], + }); + } + } + } + LNodeKind::DynamicLut(inp, original_lut) => { + let len = u8::try_from(inp.len()).unwrap(); + let mut len = usize::from(len); + // the nominal value of the inputs + let mut inp_val = Awi::zero(NonZeroUsize::new(len).unwrap()); + // corresponding bits are set if the input is either a const value or is + // already evaluated + let mut fixed = inp_val.clone(); + // corresponding bits are set if the input is `Value::Unknown` + let mut unknown = inp_val.clone(); + for i in 0..len { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + if let Value::Const(val) = equiv.val { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap(); + } else if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + if let Some(val) = equiv.val.known_value() { + inp_val.set(i, val).unwrap() + } else { + unknown.set(i, true).unwrap(); + } + } + } + let mut lut = Awi::zero(NonZeroUsize::new(original_lut.len()).unwrap()); + // this kind of unknown includes unfixed bits + let mut lut_unknown = lut.clone(); + for (i, value) in original_lut.iter().enumerate() { + match value { + DynamicValue::Unknown => lut_unknown.set(i, true).unwrap(), + DynamicValue::Const(b) => lut.set(i, *b).unwrap(), + DynamicValue::Dynam(p) => { + let equiv = self.backrefs.get_val(*p).unwrap(); + match equiv.val { + Value::Unknown => lut_unknown.set(i, true).unwrap(), + Value::Const(b) => lut.set(i, b).unwrap(), + Value::Dynam(b) => { + if equiv.change_visit == self.evaluator.change_visit_gen() { + lut.set(i, b).unwrap() + } else { + lut_unknown.set(i, true).unwrap(); + } + } + } + } + } + } // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); + LNode::reduce_lut(&mut lut_unknown, i, inp_val.get(i).unwrap()); + // remove the input bits + len -= 1; + let w = NonZeroUsize::new(len).unwrap(); + if i != len { + cc!(inp_val[(i + 1)..], ..i; inp_val).unwrap(); + cc!(fixed[(i + 1)..], ..i; fixed).unwrap(); + cc!(unknown[(i + 1)..], ..i; unknown).unwrap(); + } + inp_val.zero_resize(w); + fixed.zero_resize(w); + unknown.zero_resize(w); + } + } + for i in 0..len { + // if there are any fixed and unknown input bits, and there are any unknown bits + // in the reduced table at all, or the fixed and unknown bits can influence the + // value, then the value of this equivalence can also be + // fixed to unknown + if fixed.get(i).unwrap() + && unknown.get(i).unwrap() + && (!(lut_unknown.is_zero() + && LNode::reduce_independent_lut(&mut lut.clone(), i))) + { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; } } // if the LUT is all ones or all zeros, we can know that any unfixed or @@ -313,7 +437,6 @@ impl Ensemble { } } } - LNodeKind::DynamicLut(..) => todo!(), } res } diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index b08e75ed..9934a50f 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -216,6 +216,9 @@ impl Ensemble { let needs_lower = match lock.ensemble.stator.states[p_state].op { Opaque(..) | Literal(_) | Assert(_) | Copy(_) | StaticGet(..) | Repeat(_) | StaticLut(..) => false, + // for dynamic LUTs + // FIXME + Mux(_) => true, Lut([lut, inx]) => { if let Literal(ref lit) = lock.ensemble.stator.states[lut].op { let lit = lit.clone(); From f4da755c11c6be364a7393eac1c35f89bc0cc83f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 30 Dec 2023 16:44:49 -0600 Subject: [PATCH 045/119] fix `StarRng` --- CHANGELOG.md | 4 ++ starlight/src/misc/rng.rs | 67 ++++++++++++++++++------------ testcrate/tests/fuzz_elementary.rs | 2 +- testcrate/tests/rng.rs | 42 ++++++++++++++++++- 4 files changed, 87 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc12118..ced5919a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Changelog ## [0.3.0] - TODO +### Fixes +- Fixed a off-by-one issue with `StarRng::out_of_*` + ### Changes - merged `Epoch::assert_assertions` and `Epoch::assert_assertions_strict` - many fixes for `Epoch` behavior - `LNode`s now have a `LNodeKind` +- `StarRng::index` was renamed to `index_slice`, and a `index_slice_mut` and new `index` function were added ### Additions - Added `Epoch::suspend` diff --git a/starlight/src/misc/rng.rs b/starlight/src/misc/rng.rs index 650d7443..ed7a8201 100644 --- a/starlight/src/misc/rng.rs +++ b/starlight/src/misc/rng.rs @@ -71,7 +71,7 @@ macro_rules! out_of { let mut tmp: inlawi_ty!($bw) = InlAwi::zero(); tmp.u8_(num); self.next_bits(&mut tmp); - num >= tmp.to_u8() + num > tmp.to_u8() } } )* @@ -89,6 +89,9 @@ impl StarRng { next_u128 u128 from_u128 to_u128, ); + // note: do not implement `next_usize`, if it exists then there will inevitably + // be arch-dependent rng code in a lot of places + out_of!( out_of_4, 4, 2; out_of_8, 8, 3; @@ -98,9 +101,6 @@ impl StarRng { out_of_128, 128, 7; ); - // note: do not implement `next_usize`, if it exists then there will be - // arch-dependent rng code in a lot of places - /// Creates a new `StarRng` with the given seed pub fn new(seed: u64) -> Self { let mut rng = Xoshiro128StarStar::seed_from_u64(seed); @@ -119,6 +119,22 @@ impl StarRng { res } + /// Fractional chance of the output being true. + /// + /// If `num` is zero, it will always return `false`. + /// If `num` is equal to or larger than the denominator, + /// it will always return `true`. + pub fn out_of_256(&mut self, num: u8) -> bool { + if num == 0 { + false + } else { + let mut tmp = InlAwi::from_u8(num); + tmp.u8_(num); + self.next_bits(&mut tmp); + num > tmp.to_u8() + } + } + /// Assigns random value to `bits` pub fn next_bits(&mut self, bits: &mut Bits) { let mut processed = 0; @@ -148,39 +164,38 @@ impl StarRng { } } - /// Fractional chance of the output being true. - /// - /// If `num` is zero, it will always return `false`. - /// If `num` is equal to or larger than the denominator, - /// it will always return `true`. - pub fn out_of_256(&mut self, num: u8) -> bool { - if num == 0 { - false - } else { - let mut tmp = InlAwi::from_u8(num); - tmp.u8_(num); - self.next_bits(&mut tmp); - num >= tmp.to_u8() - } - } - - /// Takes a random index of a slice. Returns `None` if `slice.is_empty()`. + /// Returns a random index, given an exclusive maximum of `len`. Returns + /// `None` if `len == 0`. #[must_use] - pub fn index<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> { - let len = slice.len(); + pub fn index(&mut self, len: usize) -> Option { + // TODO there are more sophisticated methods to reduce bias if len == 0 { None } else if len <= (u8::MAX as usize) { let inx = self.next_u16(); - slice.get((inx as usize) % len) + Some((inx as usize) % len) } else if len <= (u16::MAX as usize) { let inx = self.next_u32(); - slice.get((inx as usize) % len) + Some((inx as usize) % len) } else { let inx = self.next_u64(); - slice.get((inx as usize) % len) + Some((inx as usize) % len) } } + + /// Takes a random index of a slice. Returns `None` if `slice.is_empty()`. + #[must_use] + pub fn index_slice<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> { + let inx = self.index(slice.len())?; + slice.get(inx) + } + + /// Takes a random index of a slice. Returns `None` if `slice.is_empty()`. + #[must_use] + pub fn index_slice_mut<'a, T>(&mut self, slice: &'a mut [T]) -> Option<&'a mut T> { + let inx = self.index(slice.len())?; + slice.get_mut(inx) + } } impl RngCore for StarRng { diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 5ae07cd6..f17c9c5c 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -58,7 +58,7 @@ impl Mem { pub fn next(&mut self, w: usize) -> P0 { let try_query = self.rng.out_of_4(3); if try_query && (!self.v[w].is_empty()) { - *self.rng.index(&self.v[w]).unwrap() + *self.rng.index_slice(&self.v[w]).unwrap() } else { let nzbw = NonZeroUsize::new(w).unwrap(); let mut lit = awi::Awi::zero(nzbw); diff --git a/testcrate/tests/rng.rs b/testcrate/tests/rng.rs index 6161fe77..ee03ada6 100644 --- a/testcrate/tests/rng.rs +++ b/testcrate/tests/rng.rs @@ -87,5 +87,45 @@ fn star_rng() { for _ in 0..(1 << 16) { yes += rng0.out_of_128(42) as u64; } - assert_eq!(yes, 22115); + assert_eq!(yes, 21597); + let mut yes = 0u64; + for _ in 0..(1 << 16) { + yes += rng0.out_of_256(84) as u64; + } + assert_eq!(yes, 21429); + for _ in 0..(1 << 16) { + assert!(!rng0.out_of_128(0)) + } + let mut yes = 0u64; + for _ in 0..(1 << 16) { + yes += rng0.out_of_128(127) as u64; + } + assert_eq!(yes, 65053); + for _ in 0..(1 << 16) { + assert!(rng0.out_of_128(128)) + } + for _ in 0..(1 << 16) { + assert!(!rng0.out_of_256(0)) + } + let mut yes = 0u64; + for _ in 0..(1 << 16) { + yes += rng0.out_of_256(255) as u64; + } + assert_eq!(yes, 65303); + let mut yes = 0u64; + for _ in 0..(1 << 16) { + yes += rng0.out_of_4(3) as u64; + } + assert_eq!(yes, 49176); + + let mut rng0 = StarRng::new(0); + assert!(rng0.index(0).is_none()); + assert!(rng0.index_slice(&[0u8; 0]).is_none()); + let mut slice = vec![0u64; 7]; + for _ in 0..(1 << 16) { + *rng0.index_slice_mut(&mut slice).unwrap() += 1; + } + for e in slice { + assert!((e > 9149) && (e < 9513)); + } } From 38549eb45c97026e91105c2379e6dce42d0bd09b Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 30 Dec 2023 18:20:14 -0600 Subject: [PATCH 046/119] add primitive variations of `retro_` and `eval` --- CHANGELOG.md | 1 + starlight/src/awi_structs/eval_awi.rs | 41 ++++++++++++++++++++++++++- starlight/src/awi_structs/lazy_awi.rs | 27 ++++++++++++++++++ testcrate/benches/bench.rs | 2 +- testcrate/tests/basic.rs | 22 +++++++------- testcrate/tests/loop.rs | 4 +-- testcrate/tests/luts.rs | 4 +-- testcrate/tests/stats.rs | 2 +- 8 files changed, 83 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ced5919a..d9e67fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Additions - Added `Epoch::suspend` - Optimization now compresses allocations +- Added many primitive versions of `retro_` and `eval` functions ## [0.2.0] - 2023-12-08 ### Crate diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index e637a7d2..1b55645d 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -2,7 +2,7 @@ use std::{fmt, num::NonZeroUsize, thread::panicking}; use awint::{ awint_dag::{dag, EvalError, Lineage, PState}, - awint_internals::forward_debug_fmt, + awint_internals::{forward_debug_fmt, BITS}, }; use crate::{ @@ -76,6 +76,24 @@ macro_rules! evalawi_from_impl { } } +macro_rules! eval_primitives { + ($($f:ident $x:ident $to_x:ident $w:expr);*;) => { + $( + /// The same as [EvalAwi::eval], except that it returns a primitive + /// and returns an error if the bitwidth of the evaluation does not + /// match the bitwidth of the primitive + pub fn $f(&self) -> Result<$x, EvalError> { + let awi = self.eval()?; + if awi.bw() == $w { + Ok(awi.$to_x()) + } else { + Err(EvalError::WrongBitwidth) + } + } + )* + }; +} + impl EvalAwi { evalawi_from_impl!( from_bool bool; @@ -93,6 +111,22 @@ impl EvalAwi { from_isize isize; ); + eval_primitives!( + eval_bool bool to_bool 1; + eval_u8 u8 to_u8 8; + eval_i8 i8 to_i8 8; + eval_u16 u16 to_u16 16; + eval_i16 i16 to_i16 16; + eval_u32 u32 to_u32 32; + eval_i32 i32 to_i32 32; + eval_u64 u64 to_u64 64; + eval_i64 i64 to_i64 64; + eval_u128 u128 to_u128 128; + eval_i128 i128 to_i128 128; + eval_usize usize to_usize BITS; + eval_isize isize to_isize BITS; + ); + pub fn p_external(&self) -> PExternal { self.p_external } @@ -150,6 +184,11 @@ impl EvalAwi { Self::from_state(bits.state()) } + /// Evaluates the value that `self` would evaluate to given the current + /// state of any `LazyAwi`s. Depending on the conditions of internal LUTs, + /// it may be possible to evaluate to a known value even if some inputs are + /// `opaque`, but in general this will return an error that a bit could not + /// be evaluated to a known value, if any upstream inputs are `opaque`. pub fn eval(&self) -> Result { let nzbw = self.try_get_nzbw()?; let mut res = awi::Awi::zero(nzbw); diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index c2b3e5ed..8d5f7c66 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -63,7 +63,34 @@ impl Lineage for LazyAwi { } } +macro_rules! retro_primitives { + ($($f:ident $x:ident);*;) => { + $( + /// Retroactively-assigns by `rhs` + pub fn $f(&self, rhs: $x) -> Result<(), EvalError> { + self.retro_(&awi::InlAwi::from(rhs)) + } + )* + }; +} + impl LazyAwi { + retro_primitives!( + retro_bool_ bool; + retro_u8_ u8; + retro_i8_ i8; + retro_u16_ u16; + retro_i16_ i16; + retro_u32_ u32; + retro_i32_ i32; + retro_u64_ u64; + retro_i64_ i64; + retro_u128_ u128; + retro_i128_ i128; + retro_usize_ usize; + retro_isize_ isize; + ); + fn internal_as_ref(&self) -> &dag::Bits { &self.opaque } diff --git a/testcrate/benches/bench.rs b/testcrate/benches/bench.rs index 92108dd2..40d23771 100644 --- a/testcrate/benches/bench.rs +++ b/testcrate/benches/bench.rs @@ -59,7 +59,7 @@ fn loop_net(bencher: &mut Bencher) { inx.usize_(i); lazy.retro_(&inx).unwrap(); epoch.drive_loops().unwrap(); - awi::assert_eq!(eval_res.eval().unwrap().to_bool(), i >= num_ports); + awi::assert_eq!(eval_res.eval_bool().unwrap(), i >= num_ports); if i < num_ports { awi::assert_eq!(eval_net.eval().unwrap().to_usize(), i); } diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 2359e61a..c3c9695c 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -44,13 +44,13 @@ fn invert_twice() { let y = EvalAwi::from(a); { - use awi::{assert_eq, *}; + use awi::assert; - x.retro_(&awi!(0)).unwrap(); - assert_eq!(y.eval().unwrap(), awi!(0)); + x.retro_bool_(false).unwrap(); + assert!(!y.eval_bool().unwrap()); epoch.verify_integrity().unwrap(); - x.retro_(&awi!(1)).unwrap(); - assert_eq!(y.eval().unwrap(), awi!(1)); + x.retro_bool_(true).unwrap(); + assert!(y.eval_bool().unwrap()); } drop(epoch); } @@ -65,16 +65,14 @@ fn multiplier() { let output = EvalAwi::from(output); { - use awi::*; - - input_a.retro_(&awi!(123u16)).unwrap(); - input_b.retro_(&awi!(77u16)).unwrap(); - std::assert_eq!(output.eval().unwrap(), awi!(9471u32)); + input_a.retro_u16_(123u16).unwrap(); + input_b.retro_u16_(77u16).unwrap(); + std::assert_eq!(output.eval_u32().unwrap(), 9471u32); epoch.optimize().unwrap(); - input_a.retro_(&awi!(10u16)).unwrap(); - std::assert_eq!(output.eval().unwrap(), awi!(770u32)); + input_a.retro_u16_(10u16).unwrap(); + std::assert_eq!(output.eval_u32().unwrap(), 770u32); } drop(epoch); } diff --git a/testcrate/tests/loop.rs b/testcrate/tests/loop.rs index 305ced2d..48f79057 100644 --- a/testcrate/tests/loop.rs +++ b/testcrate/tests/loop.rs @@ -137,12 +137,12 @@ fn loop_net() { let eval_res = EvalAwi::from_bool(res.is_none()); { use awi::{assert_eq, *}; - lazy.retro_(&awi!(0)).unwrap(); + lazy.retro_bool_(false).unwrap(); epoch.drive_loops().unwrap(); assert_eq!(eval_res.eval().unwrap(), awi!(0)); assert_eq!(eval_net.eval().unwrap(), awi!(0xa_u5)); // any nonzero index always returns a `None` from the function - lazy.retro_(&awi!(1)).unwrap(); + lazy.retro_bool_(true).unwrap(); epoch.drive_loops().unwrap(); assert_eq!(eval_res.eval().unwrap(), awi!(1)); } diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 3301f489..9453a8a8 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -2,7 +2,7 @@ use starlight::{awi, dag::*, ensemble::LNodeKind, Epoch, EvalAwi, LazyAwi, StarR // Test static LUT simplifications #[test] -fn luts_states() { +fn luts_optimization() { let mut rng = StarRng::new(0); let mut inp_bits = 0; for input_w in 1usize..=8 { @@ -45,8 +45,6 @@ fn luts_states() { let opt_res = EvalAwi::from(&x); - epoch.lower().unwrap(); - epoch.optimize().unwrap(); input.retro_(&original_input).unwrap(); diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index 8609c529..52cda2f8 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -86,7 +86,7 @@ fn stats_different_prunings() { inx.usize_(i); lazy.retro_(&inx).unwrap(); epoch.drive_loops().unwrap(); - awi::assert_eq!(eval_res.eval().unwrap().to_bool(), i >= num_ports); + awi::assert_eq!(eval_res.eval_bool().unwrap(), i >= num_ports); if i < num_ports { awi::assert_eq!(eval_net.eval().unwrap().to_usize(), i); } From c743457d51f9b0c8ec8e75f757ecfc98f706ab15 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sun, 31 Dec 2023 22:26:16 -0600 Subject: [PATCH 047/119] add better LUT tests --- starlight/src/awi_structs/lazy_awi.rs | 42 ++--- starlight/src/ensemble/lnode.rs | 3 + starlight/src/lower/meta.rs | 2 +- starlight/src/misc/rng.rs | 51 +++++ testcrate/tests/luts.rs | 260 +++++++++++++++++++++++++- 5 files changed, 324 insertions(+), 34 deletions(-) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index 8d5f7c66..f643c9fa 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -107,6 +107,7 @@ impl LazyAwi { self.nzbw().get() } + // TODO the name regards what it is initially dynamically set to, add zero etc pub fn opaque(w: NonZeroUsize) -> Self { let opaque = dag::Awi::opaque(w); let p_external = get_current_epoch() @@ -119,36 +120,6 @@ impl LazyAwi { Self { opaque, p_external } } - // TODO it probably does need to be an extra `Awi` in the `Opaque` variant, - // or does this make sense at all? - /*pub fn from_bits(bits: &awi::Bits) -> Self { - Self { opaque: dag::Awi::opaque(bits.nzbw()), lazy_value: Some(awi::Awi::from_bits(bits)) } - }*/ - - /*pub fn zero(w: NonZeroUsize) -> Self { - let mut res = Self { - opaque: dag::Awi::opaque(w), - }; - //res.retro_(&awi!(zero: ..w.get()).unwrap()).unwrap(); - res - }*/ - - /*pub fn umax(w: NonZeroUsize) -> Self { - Self::from_bits(&awi::Awi::umax(w)) - } - - pub fn imax(w: NonZeroUsize) -> Self { - Self::from_bits(&awi::Awi::imax(w)) - } - - pub fn imin(w: NonZeroUsize) -> Self { - Self::from_bits(&awi::Awi::imin(w)) - } - - pub fn uone(w: NonZeroUsize) -> Self { - Self::from_bits(&awi::Awi::uone(w)) - }*/ - /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch /// or if this is being called after the corresponding Epoch is dropped /// and states have been pruned. @@ -156,11 +127,22 @@ impl LazyAwi { Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) } + /* + /// Retroactively-unknown-assigns, the same as `retro_` except it sets the + /// bits to a dynamically unknown value + pub fn retro_unknown_(&self) -> Result<(), EvalError> { + Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + } + */ + /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it /// adds the guarantee that the value will never be changed again pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { Ensemble::change_thread_local_rnode_value(self.p_external, rhs, true) } + + // TODO + //pub fn retro_zero_ } impl Deref for LazyAwi { diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 08e4d54a..e055c95a 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -50,6 +50,7 @@ impl Recast for LNode { } } +/// When the `i`th input to a LUT is known to be `bit`, this will reduce the LUT fn general_reduce_lut(lut: &Awi, i: usize, bit: bool) -> Awi { let next_bw = lut.bw() / 2; let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); @@ -66,6 +67,8 @@ fn general_reduce_lut(lut: &Awi, i: usize, bit: bool) -> Awi { next_lut } +/// When a LUT's output is determined to be independent of the `i`th bit, this +/// will reduce it and return true fn general_reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { let nzbw = lut.nzbw(); debug_assert!(nzbw.get().is_power_of_two()); diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 9cb68775..3de2e600 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -31,7 +31,7 @@ const USIZE_BITS: usize = usize::BITS as usize; // even though we have later stages that would optimize LUTs, we find it good to // optimize as early as possible for this common case. -fn create_static_lut( +pub fn create_static_lut( mut inxs: SmallVec<[PState; 4]>, mut lut: awi::Awi, ) -> Result, PState> { diff --git a/starlight/src/misc/rng.rs b/starlight/src/misc/rng.rs index ed7a8201..4358d202 100644 --- a/starlight/src/misc/rng.rs +++ b/starlight/src/misc/rng.rs @@ -196,6 +196,57 @@ impl StarRng { let inx = self.index(slice.len())?; slice.get_mut(inx) } + + // TODO I think what I need is public "or,and,xor"_ones functions for `Bits` + // that the macros should probably also be using for common zero and umax cases + // and for the potential repeat cases. This would also eliminate padding + // needs in several places such as here + + /// This performs one step of a fuzzer where a random width of ones is + /// rotated randomly and randomly ORed, ANDed, or XORed to `x`. `pad` needs + /// to have the same bitwidth as `x`. + /// + /// In many cases there are issues that involve long lines of all set or + /// unset bits, and the `next_bits` function is unsuitable for this as + /// `x.bw()` gets larger than a few bits. This function produces random + /// length strings of ones and zeros concatenated together, which can + /// rapidly probe a more structured space even for large `x`. + /// + /// ``` + /// use starlight::{awi::*, StarRng}; + /// + /// let mut rng = StarRng::new(0); + /// let mut x = awi!(0u128); + /// let mut pad = x.clone(); + /// // this should be done in a loop with thousands of iterations, + /// // here I have unrolled a few for example + /// rng.linear_fuzz_step(&mut x, &mut pad); + /// assert_eq!(x, awi!(0x1ff_ffffffc0_00000000_u128)); + /// rng.linear_fuzz_step(&mut x, &mut pad); + /// assert_eq!(x, awi!(0xffffffff_fffffe00_3fffffc0_0000000f_u128)); + /// rng.linear_fuzz_step(&mut x, &mut pad); + /// assert_eq!(x, awi!(0xffffffff_e00001ff_c01fffc0_0000000f_u128)); + /// rng.linear_fuzz_step(&mut x, &mut pad); + /// assert_eq!(x, awi!(0x1ffffe00_3fe0003f_fffffff0_u128)); + /// rng.linear_fuzz_step(&mut x, &mut pad); + /// assert_eq!(x, awi!(0xffffffff_e03fffff_c01fffc0_0000000f_u128)); + /// ``` + pub fn linear_fuzz_step(&mut self, x: &mut Bits, pad: &mut Bits) { + let r0 = self.index(x.bw()).unwrap(); + let r1 = self.index(x.bw()).unwrap(); + pad.umax_(); + pad.shl_(r0).unwrap(); + pad.rotl_(r1).unwrap(); + // note: it needs to be 2 parts XOR to 1 part OR and 1 part AND, the ordering + // guarantees this + if self.next_bool() { + x.xor_(pad).unwrap(); + } else if self.next_bool() { + x.or_(pad).unwrap(); + } else { + x.and_(pad).unwrap(); + } + } } impl RngCore for StarRng { diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 9453a8a8..2d453554 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -1,8 +1,22 @@ -use starlight::{awi, dag::*, ensemble::LNodeKind, Epoch, EvalAwi, LazyAwi, StarRng}; +use std::num::NonZeroUsize; -// Test static LUT simplifications +use starlight::{ + awi, + awi::*, + awint_dag::{ + smallvec::{smallvec, SmallVec}, + Lineage, Op, + }, + dag, + ensemble::LNodeKind, + lower::meta::create_static_lut, + Epoch, EvalAwi, LazyAwi, StarRng, +}; + +// Test static LUT simplifications, this also handles input duplication cases #[test] -fn luts_optimization() { +fn lut_optimization_with_dup() { + use dag::*; let mut rng = StarRng::new(0); let mut inp_bits = 0; for input_w in 1usize..=8 { @@ -89,3 +103,243 @@ fn luts_optimization() { assert_eq!(inp_bits, 1386); } } + +// The first number is the base number of iterations, the others are counters to +// make sure the rng isn't broken +const N: (u64, u64, u64) = if cfg!(debug_assertions) { + (16, 193536, 14778) +} else { + (128, 1548288, 107245) +}; + +// these functions need to stay the same in case the ones in the library are +// changed + +/// When the `i`th input to a LUT is known to be `bit`, this will reduce the LUT +fn general_reduce_lut(lut: &Awi, i: usize, bit: bool) -> Awi { + let next_bw = lut.bw() / 2; + let mut next_lut = Awi::zero(NonZeroUsize::new(next_bw).unwrap()); + let w = 1 << i; + let mut from = 0; + let mut to = 0; + while to < next_bw { + next_lut + .field(to, lut, if bit { from + w } else { from }, w) + .unwrap(); + from += 2 * w; + to += w; + } + next_lut +} + +/// When a LUT's output is determined to be independent of the `i`th bit, this +/// will reduce it and return true +fn general_reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { + let nzbw = lut.nzbw(); + debug_assert!(nzbw.get().is_power_of_two()); + let next_bw = nzbw.get() / 2; + let next_nzbw = NonZeroUsize::new(next_bw).unwrap(); + let mut tmp0 = Awi::zero(next_nzbw); + let mut tmp1 = Awi::zero(next_nzbw); + let w = 1 << i; + // LUT if the `i`th bit were 0 + let mut from = 0; + let mut to = 0; + while to < next_bw { + tmp0.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + // LUT if the `i`th bit were 1 + from = w; + to = 0; + while to < next_bw { + tmp1.field(to, lut, from, w).unwrap(); + from += 2 * w; + to += w; + } + if tmp0 == tmp1 { + *lut = tmp0; + true + } else { + false + } +} + +enum DynamicBool { + Bool(bool), + Lazy(LazyAwi), +} +use DynamicBool::*; + +/// Test that various types of LUT simplification work, not using duplication +/// cases +#[test] +fn lut_optimization() { + let mut rng = StarRng::new(0); + let mut num_lut_bits = 0u64; + let mut num_simplified_lut_bits = 0u64; + let mut expected_output = awi!(0); + // LUTs with input sizes between 1 and 12 (the higher end is needed for some + // beyond 64 width cases) + for w in 1..=12 { + let n = if w > 6 { N.0 } else { N.0 * 32 }; + let lut_w = NonZeroUsize::new(1 << w).unwrap(); + let w = NonZeroUsize::new(w).unwrap(); + let mut lut_input = Awi::zero(w); + let mut known_inputs = Awi::zero(w); + let mut lut = Awi::zero(lut_w); + let mut pad = lut.clone(); + + for _ in 0..n { + num_lut_bits += lut.bw() as u64; + // Some bits will be known in some way to the epoch + rng.next_bits(&mut known_inputs); + rng.next_bits(&mut lut_input); + //rng.next_bits(&mut lut); + rng.linear_fuzz_step(&mut lut, &mut pad); + expected_output.lut_(&lut, &lut_input).unwrap(); + let mut expected_lut = lut.clone(); + let mut remaining_inp_len = w.get(); + for i in (0..w.get()).rev() { + if known_inputs.get(i).unwrap() { + expected_lut = general_reduce_lut(&expected_lut, i, lut_input.get(i).unwrap()); + remaining_inp_len -= 1; + } + } + for i in (0..remaining_inp_len).rev() { + if expected_lut.bw() == 1 { + break + } + general_reduce_independent_lut(&mut expected_lut, i); + } + num_simplified_lut_bits += expected_lut.bw() as u64; + + { + let epoch = Epoch::new(); + use dag::*; + // prepare inputs for the subtests + let mut inputs: SmallVec<[DynamicBool; 12]> = smallvec![]; + for i in 0..w.get() { + if known_inputs.get(i).unwrap() { + inputs.push(Bool(lut_input.get(i).unwrap())) + } else { + inputs.push(Lazy(LazyAwi::opaque(bw(1)))); + } + } + let mut total = Awi::zero(w); + for (i, input) in inputs.iter().enumerate() { + match input { + Bool(b) => total.set(i, *b).unwrap(), + Lazy(b) => total.set(i, b.to_bool()).unwrap(), + } + } + let mut p_state_inputs = smallvec![]; + for input in inputs.iter() { + match input { + Bool(b) => p_state_inputs.push(Awi::from_bool(*b).state()), + Lazy(b) => p_state_inputs.push(b.state()), + } + } + + // for the first subtest, we make sure that the metalowerer correctly creates + // simplified LUTs in its subroutines, we will compare this with what the + // `LNode` optimizer path does + let meta_res = create_static_lut(p_state_inputs, lut.clone()); + + // for the second subtest create a static LUT that will be optimized in the + // `LNode` optimization path + let mut output = Awi::zero(bw(1)); + output.lut_(&Awi::from(&lut), &total).unwrap(); + let output = EvalAwi::from(&output); + epoch.optimize().unwrap(); + + { + use awi::*; + epoch.ensemble(|ensemble| { + match &meta_res { + Ok(op) => { + match op { + Op::StaticLut(_, lut) => { + // get the sole `LNode` that should exist by this point + let mut tmp = ensemble.lnodes.vals(); + let lnode = tmp.next().unwrap(); + awi::assert!(tmp.next().is_none()); + match &lnode.kind { + LNodeKind::Lut(_, lnode_lut) => { + awi::assert_eq!(lnode_lut, lut); + awi::assert_eq!(expected_lut, *lut); + } + _ => unreachable!(), + } + } + Op::Literal(_) => { + // there should be no `LNode` since it was optimized to a + // constant + let mut tmp = ensemble.lnodes.vals(); + awi::assert!(tmp.next().is_none()); + awi::assert_eq!(expected_lut.bw(), 1); + } + _ => unreachable!(), + } + } + Err(_) => { + // it results in a copy of an input bit, there + // should be no `LNode` since any equivalence should + // be merged + let mut tmp = ensemble.lnodes.vals(); + awi::assert!(tmp.next().is_none()); + awi::assert_eq!(expected_lut.bw(), 2); + } + } + }); + } + + // set unknown inputs + for (i, input) in inputs.iter().enumerate() { + if let Lazy(b) = input { + b.retro_bool_(lut_input.get(i).unwrap()).unwrap(); + } + } + awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + drop(epoch); + } + + // subtest 3: make sure evaluation can handle dynamically unknown inputs in + // several cases + { + let epoch = Epoch::new(); + use dag::*; + // here, "known" will mean what bits are set to dynamically known values + let mut total = Awi::zero(w); + let mut inputs: SmallVec<[LazyAwi; 12]> = smallvec![]; + for i in 0..w.get() { + let tmp = LazyAwi::opaque(bw(1)); + total.set(i, tmp.to_bool()).unwrap(); + inputs.push(tmp); + } + + let mut output = Awi::zero(bw(1)); + output.lut_(&Awi::from(&lut), &total).unwrap(); + let output = EvalAwi::from(&output); + epoch.optimize().unwrap(); + + for i in 0..w.get() { + if known_inputs.get(i).unwrap() { + inputs[i].retro_bool_(lut_input.get(i).unwrap()).unwrap(); + } + } + if expected_lut.bw() == 1 { + // evaluation should produce a known value + awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + } else { + // evaluation fails + awi::assert!(output.eval().is_err()); + } + + drop(epoch); + } + } + } + assert_eq!((num_lut_bits, num_simplified_lut_bits), (N.1, N.2)); +} From b906f6e4b36164032dbd42817fbcc47f170cb753 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 00:20:01 -0600 Subject: [PATCH 048/119] better lazy inits --- starlight/src/awi_structs/lazy_awi.rs | 126 ++++++++++++++++++++++---- starlight/src/ensemble.rs | 2 +- starlight/src/ensemble/rnode.rs | 28 +++--- starlight/src/ensemble/value.rs | 71 +++++++++++++++ 4 files changed, 196 insertions(+), 31 deletions(-) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index f643c9fa..60b5ad2a 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -13,7 +13,7 @@ use awint::{ use crate::{ awi, - ensemble::{Ensemble, PExternal}, + ensemble::{BasicValue, BasicValueKind, CommonValue, Ensemble, PExternal}, epoch::get_current_epoch, }; @@ -74,6 +74,51 @@ macro_rules! retro_primitives { }; } +macro_rules! init { + ($($f:ident $retro_:ident);*;) => { + $( + /// Initializes a `LazyAwi` with the corresponding dynamic value + pub fn $f(w: NonZeroUsize) -> Self { + let res = Self::opaque(w); + res.$retro_().unwrap(); + res + } + )* + }; +} + +macro_rules! init_inl { + ($($f:ident $retro_:ident);*;) => { + $( + /// Initializes a `LazyInlAwi` with the corresponding dynamic value + pub fn $f() -> Self { + let res = Self::opaque(); + res.$retro_().unwrap(); + res + } + )* + }; +} + +macro_rules! retro { + ($($f:ident $kind:ident);*;) => { + $( + /// Retroactively-assigns by `rhs`. Returns an error if this + /// is being called after the corresponding Epoch is dropped. + pub fn $f(&self) -> Result<(), EvalError> { + Ensemble::change_thread_local_rnode_value( + self.p_external, + CommonValue::Basic(BasicValue { + kind: BasicValueKind::$kind, + nzbw: self.nzbw(), + }), + false, + ) + } + )* + }; +} + impl LazyAwi { retro_primitives!( retro_bool_ bool; @@ -91,6 +136,22 @@ impl LazyAwi { retro_isize_ isize; ); + init!( + zero retro_zero_; + umax retro_umax_; + imax retro_imax_; + imin retro_imin_; + uone retro_uone_; + ); + + retro!( + retro_zero_ Zero; + retro_umax_ Umax; + retro_imax_ Imax; + retro_imin_ Imin; + retro_uone_ Uone; + ); + fn internal_as_ref(&self) -> &dag::Bits { &self.opaque } @@ -107,7 +168,7 @@ impl LazyAwi { self.nzbw().get() } - // TODO the name regards what it is initially dynamically set to, add zero etc + /// Initializes a `LazyAwi` with an unknown dynamic value pub fn opaque(w: NonZeroUsize) -> Self { let opaque = dag::Awi::opaque(w); let p_external = get_current_epoch() @@ -121,28 +182,31 @@ impl LazyAwi { } /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch - /// or if this is being called after the corresponding Epoch is dropped - /// and states have been pruned. + /// or if this is being called after the corresponding Epoch is dropped. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), false) } - /* /// Retroactively-unknown-assigns, the same as `retro_` except it sets the /// bits to a dynamically unknown value pub fn retro_unknown_(&self) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + Ensemble::change_thread_local_rnode_value( + self.p_external, + CommonValue::Basic(BasicValue { + kind: BasicValueKind::Opaque, + nzbw: self.nzbw(), + }), + false, + ) } - */ /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it - /// adds the guarantee that the value will never be changed again + /// adds the guarantee that the value will never be changed again (or else + /// it will result in errors if you try another `retro_*` function on + /// `self`) pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs, true) + Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), true) } - - // TODO - //pub fn retro_zero_ } impl Deref for LazyAwi { @@ -231,6 +295,22 @@ impl Lineage for LazyInlAwi { } impl LazyInlAwi { + init_inl!( + zero retro_zero_; + umax retro_umax_; + imax retro_imax_; + imin retro_imin_; + uone retro_uone_; + ); + + retro!( + retro_zero_ Zero; + retro_umax_ Umax; + retro_imax_ Imax; + retro_imin_ Imin; + retro_uone_ Uone; + ); + pub fn p_external(&self) -> PExternal { self.p_external } @@ -261,16 +341,28 @@ impl LazyInlAwi { } /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch - /// or if this is being called after the corresponding Epoch is dropped - /// and states have been pruned. + /// or if this is being called after the corresponding Epoch is dropped. pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs, false) + Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), false) + } + + /// Retroactively-unknown-assigns, the same as `retro_` except it sets the + /// bits to a dynamically unknown value + pub fn retro_unknown_(&self) -> Result<(), EvalError> { + Ensemble::change_thread_local_rnode_value( + self.p_external, + CommonValue::Basic(BasicValue { + kind: BasicValueKind::Opaque, + nzbw: self.nzbw(), + }), + false, + ) } /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it /// adds the guarantee that the value will never be changed again pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { - Ensemble::change_thread_local_rnode_value(self.p_external, rhs, true) + Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), true) } } diff --git a/starlight/src/ensemble.rs b/starlight/src/ensemble.rs index 00bf63bb..c1027a8c 100644 --- a/starlight/src/ensemble.rs +++ b/starlight/src/ensemble.rs @@ -14,4 +14,4 @@ pub use rnode::{Notary, PExternal, PRNode, RNode}; pub use state::{State, Stator}; pub use tnode::{PTNode, TNode}; pub use together::{Ensemble, Equiv, PBack, Referent}; -pub use value::{DynamicValue, Evaluator, Value}; +pub use value::{BasicValue, BasicValueKind, CommonValue, DynamicValue, Evaluator, Value}; diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index deab405c..33ed0a8f 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -7,8 +7,8 @@ use awint::awint_dag::{ }; use crate::{ - awi, - ensemble::{Ensemble, PBack, Referent, Value}, + awi::*, + ensemble::{CommonValue, Ensemble, PBack, Referent, Value}, epoch::get_current_epoch, }; @@ -189,9 +189,11 @@ impl Ensemble { } } + /// Note: `make_const` cannot be true at the same time as the basic type is + /// opaque pub fn change_thread_local_rnode_value( p_external: PExternal, - bits: &awi::Bits, + common_value: CommonValue<'_>, make_const: bool, ) -> Result<(), EvalError> { let epoch_shared = get_current_epoch().unwrap(); @@ -200,21 +202,21 @@ impl Ensemble { if let Some(p_rnode) = ensemble.notary.rnodes.find_key(&p_external) { ensemble.initialize_rnode_if_needed(p_rnode, true)?; if !ensemble.notary.rnodes[p_rnode].bits.is_empty() { - if ensemble.notary.rnodes[p_rnode].bits.len() != bits.bw() { + if ensemble.notary.rnodes[p_rnode].bits.len() != common_value.bw() { return Err(EvalError::WrongBitwidth); } - for bit_i in 0..bits.bw() { + for bit_i in 0..common_value.bw() { let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; if let Some(p_back) = p_back { - if make_const { - ensemble - .change_value(p_back, Value::Const(bits.get(bit_i).unwrap())) - .unwrap(); + let bit = common_value.get(bit_i).unwrap(); + let bit = if make_const { + Value::Const(bit.unwrap()) + } else if let Some(bit) = bit { + Value::Dynam(bit) } else { - ensemble - .change_value(p_back, Value::Dynam(bits.get(bit_i).unwrap())) - .unwrap(); - } + Value::Unknown + }; + ensemble.change_value(p_back, bit).unwrap(); } } } diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index de0455b3..9b800546 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -13,6 +13,77 @@ use crate::{ epoch::EpochShared, }; +#[derive(Debug, Clone, Copy)] +pub enum BasicValueKind { + Opaque, + Zero, + Umax, + Imax, + Imin, + Uone, +} + +/// Used when we need to pass an argument that can multiplex over the basic +/// initial values +#[derive(Debug, Clone, Copy)] +pub struct BasicValue { + pub kind: BasicValueKind, + pub nzbw: NonZeroUsize, +} + +impl BasicValue { + pub fn nzbw(&self) -> NonZeroUsize { + self.nzbw + } + + pub fn bw(&self) -> usize { + self.nzbw().get() + } + + pub fn get(&self, inx: usize) -> Option> { + if inx >= self.bw() { + None + } else { + Some(match self.kind { + BasicValueKind::Opaque => None, + BasicValueKind::Zero => Some(false), + BasicValueKind::Umax => Some(true), + BasicValueKind::Imax => Some(inx != (self.bw() - 1)), + BasicValueKind::Imin => Some(inx == (self.bw() - 1)), + BasicValueKind::Uone => Some(inx == 0), + }) + } + } +} + +/// Used when we need to pass an argument that can multiplex over common initial +/// values +#[derive(Debug, Clone)] +pub enum CommonValue<'a> { + Bits(&'a Bits), + Basic(BasicValue), +} + +impl<'a> CommonValue<'a> { + pub fn nzbw(&self) -> NonZeroUsize { + match self { + CommonValue::Bits(x) => x.nzbw(), + CommonValue::Basic(basic) => basic.nzbw(), + } + } + + pub fn bw(&self) -> usize { + self.nzbw().get() + } + + pub fn get(&self, inx: usize) -> Option> { + match self { + CommonValue::Bits(bits) => bits.get(inx).map(Some), + CommonValue::Basic(basic) => basic.get(inx), + } + } +} + /// The value of a multistate boolean #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { From d5231db784908f672672a93ab51c2181bbfb803f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 12:27:52 -0600 Subject: [PATCH 049/119] `all_variations` test --- starlight/src/ensemble/rnode.rs | 2 +- testcrate/tests/basic.rs | 92 +++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 33ed0a8f..a26ced1a 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -216,7 +216,7 @@ impl Ensemble { } else { Value::Unknown }; - ensemble.change_value(p_back, bit).unwrap(); + ensemble.change_value(p_back, bit)?; } } } diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index c3c9695c..4c59feab 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -1,7 +1,8 @@ -use starlight::{awi, dag::*, Epoch, EvalAwi, LazyAwi}; +use starlight::{awi, awi::*, dag, Epoch, EvalAwi, LazyAwi}; #[test] -fn lazy_awi() -> Option<()> { +fn lazy_awi() { + use dag::*; let epoch = Epoch::new(); let x = LazyAwi::opaque(bw(1)); @@ -28,12 +29,11 @@ fn lazy_awi() -> Option<()> { // cleans up everything not still used by `LazyAwi`s, `LazyAwi`s deregister // rnodes when dropped drop(epoch); - - Some(()) } #[test] fn invert_twice() { + use dag::*; let epoch = Epoch::new(); let x = LazyAwi::opaque(bw(1)); let mut a = awi!(x); @@ -57,6 +57,7 @@ fn invert_twice() { #[test] fn multiplier() { + use dag::*; let epoch = Epoch::new(); let input_a = LazyAwi::opaque(bw(16)); let input_b = LazyAwi::opaque(bw(16)); @@ -76,3 +77,86 @@ fn multiplier() { } drop(epoch); } + +#[test] +fn all_variations() { + let epoch = Epoch::new(); + + let x1 = LazyAwi::opaque(bw(1)); + let x7 = LazyAwi::opaque(bw(7)); + let x8 = LazyAwi::opaque(bw(8)); + let x16 = LazyAwi::opaque(bw(16)); + let x32 = LazyAwi::opaque(bw(32)); + let x64 = LazyAwi::opaque(bw(64)); + let x128 = LazyAwi::opaque(bw(128)); + let x_zero = LazyAwi::zero(bw(7)); + let x_umax = LazyAwi::umax(bw(7)); + let x_imax = LazyAwi::imax(bw(7)); + let x_imin = LazyAwi::imin(bw(7)); + let x_uone = LazyAwi::uone(bw(7)); + + let y1 = EvalAwi::from(&x1); + let y7 = EvalAwi::from(&x7); + let y8 = EvalAwi::from(&x8); + let y16 = EvalAwi::from(&x16); + let y32 = EvalAwi::from(&x32); + let y64 = EvalAwi::from(&x64); + let y128 = EvalAwi::from(&x128); + let y_zero = EvalAwi::from(&x_zero); + let y_umax = EvalAwi::from(&x_umax); + let y_imax = EvalAwi::from(&x_imax); + let y_imin = EvalAwi::from(&x_imin); + let y_uone = EvalAwi::from(&x_uone); + + epoch.verify_integrity().unwrap(); + assert!(y1.eval().is_err()); + x1.retro_bool_(true).unwrap(); + assert_eq!(y1.eval_bool().unwrap(), true); + assert!(y8.eval().is_err()); + x8.retro_u8_(u8::MAX).unwrap(); + assert_eq!(y8.eval_u8().unwrap(), u8::MAX); + x8.retro_i8_(i8::MAX).unwrap(); + assert_eq!(y8.eval_i8().unwrap(), i8::MAX); + assert!(y16.eval().is_err()); + x16.retro_u16_(u16::MAX).unwrap(); + assert_eq!(y16.eval_u16().unwrap(), u16::MAX); + x16.retro_i16_(i16::MAX).unwrap(); + assert_eq!(y16.eval_i16().unwrap(), i16::MAX); + assert!(y32.eval().is_err()); + x32.retro_u32_(u32::MAX).unwrap(); + assert_eq!(y32.eval_u32().unwrap(), u32::MAX); + x32.retro_i32_(i32::MAX).unwrap(); + assert_eq!(y32.eval_i32().unwrap(), i32::MAX); + assert!(y64.eval().is_err()); + x64.retro_u64_(u64::MAX).unwrap(); + assert_eq!(y64.eval_u64().unwrap(), u64::MAX); + x64.retro_i64_(i64::MAX).unwrap(); + assert_eq!(y64.eval_i64().unwrap(), i64::MAX); + assert!(y128.eval().is_err()); + x128.retro_u128_(u128::MAX).unwrap(); + assert_eq!(y128.eval_u128().unwrap(), u128::MAX); + x128.retro_i128_(i128::MAX).unwrap(); + assert_eq!(y128.eval_i128().unwrap(), i128::MAX); + assert_eq!(y_zero.eval().unwrap(), awi!(0u7)); + assert_eq!(y_umax.eval().unwrap(), awi!(umax: ..7)); + assert_eq!(y_imax.eval().unwrap(), awi!(imax: ..7)); + assert_eq!(y_imin.eval().unwrap(), awi!(imin: ..7)); + assert_eq!(y_uone.eval().unwrap(), awi!(uone: ..7)); + x7.retro_zero_().unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(zero: ..7)); + x7.retro_umax_().unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(umax: ..7)); + x7.retro_imax_().unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(imax: ..7)); + x7.retro_imin_().unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(imin: ..7)); + x7.retro_uone_().unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(uone: ..7)); + x7.retro_unknown_().unwrap(); + assert!(y7.eval().is_err()); + x7.retro_const_(&awi!(-2i7)).unwrap(); + assert_eq!(y7.eval().unwrap(), awi!(-2i7)); + assert!(x7.retro_unknown_().is_err()); + + drop(epoch); +} From 7872694f84b1833f187ed3170888a7de6dfe8c04 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 13:09:50 -0600 Subject: [PATCH 050/119] prepare for actual Dynamic LUTs --- testcrate/tests/luts.rs | 189 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 8 deletions(-) diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 2d453554..81f78375 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -104,14 +104,6 @@ fn lut_optimization_with_dup() { } } -// The first number is the base number of iterations, the others are counters to -// make sure the rng isn't broken -const N: (u64, u64, u64) = if cfg!(debug_assertions) { - (16, 193536, 14778) -} else { - (128, 1548288, 107245) -}; - // these functions need to stay the same in case the ones in the library are // changed @@ -176,6 +168,13 @@ use DynamicBool::*; /// cases #[test] fn lut_optimization() { + // The first number is the base number of iterations, the others are counters to + // make sure the rng isn't broken + const N: (u64, u64, u64) = if cfg!(debug_assertions) { + (16, 193536, 14778) + } else { + (128, 1548288, 107245) + }; let mut rng = StarRng::new(0); let mut num_lut_bits = 0u64; let mut num_simplified_lut_bits = 0u64; @@ -343,3 +342,177 @@ fn lut_optimization() { } assert_eq!((num_lut_bits, num_simplified_lut_bits), (N.1, N.2)); } + +/// Test dynamic LUT optimizations +#[test] +fn lut_dynamic_optimization() { + // The first number is the base number of iterations, the others are counters to + // make sure the rng isn't broken + const N: (u64, u64, u64) = if cfg!(debug_assertions) { + (32, 1984, 470) + } else { + (512, 31744, 7371) + }; + let mut rng = StarRng::new(0); + let mut num_lut_bits = 0u64; + let mut num_simplified_lut_bits = 0u64; + let mut expected_output = awi!(0); + for w in 1..=5 { + let n = N.0; + let lut_w = NonZeroUsize::new(1 << w).unwrap(); + let w = NonZeroUsize::new(w).unwrap(); + let mut lut_input = Awi::zero(w); + let mut known_inputs = Awi::zero(w); + let mut lut = Awi::zero(lut_w); + let mut known_lut_bits = Awi::zero(lut_w); + let mut pad = lut.clone(); + let mut lut_pad = known_lut_bits.clone(); + + for _ in 0..n { + num_lut_bits += lut.bw() as u64; + rng.next_bits(&mut known_inputs); + rng.next_bits(&mut lut_input); + rng.linear_fuzz_step(&mut lut, &mut pad); + // now only some bits of the LUT might be known + rng.linear_fuzz_step(&mut known_lut_bits, &mut lut_pad); + let mut known_lut_bits_reduced = known_lut_bits.clone(); + expected_output.lut_(&lut, &lut_input).unwrap(); + let mut expected_lut = lut.clone(); + let mut remaining_inp_len = w.get(); + for i in (0..w.get()).rev() { + if known_inputs.get(i).unwrap() { + expected_lut = general_reduce_lut(&expected_lut, i, lut_input.get(i).unwrap()); + known_lut_bits_reduced = + general_reduce_lut(&known_lut_bits_reduced, i, lut_input.get(i).unwrap()); + remaining_inp_len -= 1; + } + } + for i in (0..remaining_inp_len).rev() { + if expected_lut.bw() == 1 { + break + } + if general_reduce_independent_lut(&mut expected_lut, i) { + known_lut_bits_reduced = + general_reduce_lut(&known_lut_bits_reduced, i, lut_input.get(i).unwrap()); + } + } + num_simplified_lut_bits += expected_lut.bw() as u64; + + { + let epoch = Epoch::new(); + use dag::*; + // prepare inputs for the subtests + let mut inputs: SmallVec<[DynamicBool; 12]> = smallvec![]; + for i in 0..w.get() { + if known_inputs.get(i).unwrap() { + inputs.push(Bool(lut_input.get(i).unwrap())) + } else { + inputs.push(Lazy(LazyAwi::opaque(bw(1)))); + } + } + let mut lut_bits = vec![]; + for i in 0..lut.bw() { + if known_lut_bits.get(i).unwrap() { + lut_bits.push(Bool(lut.get(i).unwrap())) + } else { + lut_bits.push(Lazy(LazyAwi::opaque(bw(1)))); + } + } + let mut total = Awi::zero(w); + for (i, input) in inputs.iter().enumerate() { + match input { + Bool(b) => total.set(i, *b).unwrap(), + Lazy(b) => total.set(i, b.to_bool()).unwrap(), + } + } + let mut total_lut_bits = Awi::zero(lut.nzbw()); + for (i, input) in lut_bits.iter().enumerate() { + match input { + Bool(b) => total_lut_bits.set(i, *b).unwrap(), + Lazy(b) => total_lut_bits.set(i, b.to_bool()).unwrap(), + } + } + + let mut output = Awi::zero(bw(1)); + output.lut_(&total_lut_bits, &total).unwrap(); + let output = EvalAwi::from(&output); + epoch.optimize().unwrap(); + + /*{ + use awi::*; + epoch.ensemble(|ensemble| { + if expected_lut.bw() == 1 { + // there should be no `LNode` since it was optimized to a + // constant + let mut tmp = ensemble.lnodes.vals(); + awi::assert!(tmp.next().is_none()); + awi::assert_eq!(expected_lut.bw(), 1); + } else { + // get the sole `LNode` that should exist by this point + let mut tmp = ensemble.lnodes.vals(); + let lnode = tmp.next().unwrap(); + awi::assert!(tmp.next().is_none()); + match &lnode.kind { + LNodeKind::Lut(_, lnode_lut) => { + awi::assert_eq!(*lnode_lut, expected_lut); + } + _ => unreachable!(), + } + } + }); + }*/ + + // set unknown inputs + for (i, input) in inputs.iter().enumerate() { + if let Lazy(b) = input { + b.retro_bool_(lut_input.get(i).unwrap()).unwrap(); + } + } + for (i, input) in lut_bits.iter().enumerate() { + if let Lazy(b) = input { + b.retro_bool_(lut.get(i).unwrap()).unwrap(); + } + } + awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + drop(epoch); + } + + /* + // subtest 3: make sure evaluation can handle dynamically unknown inputs in + // several cases + { + let epoch = Epoch::new(); + use dag::*; + // here, "known" will mean what bits are set to dynamically known values + let mut total = Awi::zero(w); + let mut inputs: SmallVec<[LazyAwi; 12]> = smallvec![]; + for i in 0..w.get() { + let tmp = LazyAwi::opaque(bw(1)); + total.set(i, tmp.to_bool()).unwrap(); + inputs.push(tmp); + } + + let mut output = Awi::zero(bw(1)); + output.lut_(&Awi::from(&lut), &total).unwrap(); + let output = EvalAwi::from(&output); + epoch.optimize().unwrap(); + + for i in 0..w.get() { + if known_inputs.get(i).unwrap() { + inputs[i].retro_bool_(lut_input.get(i).unwrap()).unwrap(); + } + } + if expected_lut.bw() == 1 { + // evaluation should produce a known value + awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + } else { + // evaluation fails + awi::assert!(output.eval().is_err()); + } + + drop(epoch); + }*/ + } + } + assert_eq!((num_lut_bits, num_simplified_lut_bits), (N.1, N.2)); +} From 915712cf2653acc5e50a631112fde5dc4a48e287 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 13:51:36 -0600 Subject: [PATCH 051/119] fix fixme --- starlight/src/ensemble/value.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 9b800546..b88dc785 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -323,6 +323,10 @@ impl Ensemble { } } let mut lut = original_lut.clone(); + // note: we do this in this order, it turns out that doing independence + // reduction instead of constant reduction first will not prevent optimizations, + // also we don't have to remove bits from `fixed` and `unknown` + // if fixed and unknown bits can influence the value, // then the value of this equivalence can also be fixed // to unknown @@ -339,8 +343,7 @@ impl Ensemble { return vec![]; } } - // FIXME does this go before and catch some more cases that can eval to a known - // val reduce the LUT based on fixed and known bits + // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); From 88869001e9183df9423b1a8497276f4bbd7eee57 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 21:31:49 -0600 Subject: [PATCH 052/119] first dynamic LUT optimizations --- starlight/src/ensemble/debug.rs | 5 +- starlight/src/ensemble/lnode.rs | 86 +++++++++++++++++++++++-- starlight/src/ensemble/optimize.rs | 100 ++++++++++++++++++++--------- starlight/src/ensemble/state.rs | 29 +++++++++ starlight/src/ensemble/together.rs | 60 +++++++++++------ starlight/src/ensemble/value.rs | 3 + starlight/src/lower/lower_state.rs | 6 +- starlight/src/misc/rng.rs | 3 + testcrate/tests/luts.rs | 6 +- 9 files changed, 234 insertions(+), 64 deletions(-) diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 88794d30..ca50b56d 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -5,9 +5,10 @@ use awint::{ awint_macro_internals::triple_arena::Arena, }; -use super::DynamicValue; use crate::{ - ensemble::{Ensemble, Equiv, LNode, LNodeKind, PBack, PRNode, PTNode, Referent, State}, + ensemble::{ + DynamicValue, Ensemble, Equiv, LNode, LNodeKind, PBack, PRNode, PTNode, Referent, State, + }, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, Epoch, diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index e055c95a..08f3bc59 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -1,9 +1,9 @@ -use std::num::NonZeroUsize; +use std::{mem, num::NonZeroUsize}; use awint::{ awint_dag::{ smallvec, - triple_arena::{Recast, Recaster}, + triple_arena::{Recast, Recaster, SurjectArena}, PState, }, Awi, @@ -11,7 +11,7 @@ use awint::{ use smallvec::SmallVec; use crate::{ - ensemble::{DynamicValue, PBack}, + ensemble::{DynamicValue, Equiv, PBack, Referent}, triple_arena::ptr_struct, }; @@ -43,10 +43,13 @@ impl Recast for LNode { recaster: &R, ) -> Result<(), ::Item> { self.p_self.recast(recaster)?; + let mut res = Ok(()); self.inputs_mut(|inp| { - inp.recast(recaster).unwrap(); + if let Err(e) = inp.recast(recaster) { + res = Err(e); + } }); - Ok(()) + res } } @@ -196,6 +199,7 @@ impl LNode { /// input bit to `bit` pub fn reduce_lut(lut: &mut Awi, i: usize, bit: bool) { debug_assert!(lut.bw().is_power_of_two()); + debug_assert!(i < (lut.bw().trailing_zeros() as usize)); let half = NonZeroUsize::new(lut.bw() / 2).unwrap(); if lut.bw() > 64 { *lut = general_reduce_lut(lut, i, bit); @@ -214,6 +218,7 @@ impl LNode { bit: bool, ) -> (Vec, Vec) { debug_assert!(lut.len().is_power_of_two()); + debug_assert!(i < (lut.len().trailing_zeros() as usize)); let next_bw = lut.len() / 2; let mut next_lut = vec![DynamicValue::Unknown; next_bw]; let mut removed = Vec::with_capacity(next_bw); @@ -222,8 +227,13 @@ impl LNode { let mut to = 0; while to < next_bw { for j in 0..w { - next_lut[to + j] = lut[if bit { from + j } else { from }]; - if let DynamicValue::Dynam(p_back) = lut[if !bit { from + j } else { from }] { + let mut tmp0 = lut[from + j]; + let mut tmp1 = lut[from + w + j]; + if bit { + mem::swap(&mut tmp0, &mut tmp1); + } + next_lut[to + j] = tmp0; + if let DynamicValue::Dynam(p_back) = tmp1 { removed.push(p_back); } } @@ -238,6 +248,7 @@ impl LNode { #[must_use] pub fn reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { debug_assert!(lut.bw().is_power_of_two()); + debug_assert!(i < (lut.bw().trailing_zeros() as usize)); let half = NonZeroUsize::new(lut.bw() / 2).unwrap(); if lut.bw() > 64 { general_reduce_independent_lut(lut, i) @@ -249,4 +260,65 @@ impl LNode { false } } + + /// The same as `reduce_independent_lut`, except it checks for independence + /// regarding dynamic LUT bits with equal constants or source equivalences + pub fn reduce_independent_dynamic_lut( + backrefs: &SurjectArena, + lut: &[DynamicValue], + i: usize, + ) -> Option<(Vec, Vec)> { + debug_assert!(lut.len().is_power_of_two()); + let next_bw = lut.len() / 2; + let w = 1 << i; + let mut from = 0; + let mut to = 0; + while to < next_bw { + for j in 0..w { + let tmp0 = &lut[from + j]; + let tmp1 = &lut[from + w + j]; + match tmp0 { + DynamicValue::Unknown => return None, + DynamicValue::Const(b0) => match tmp1 { + DynamicValue::Unknown => return None, + DynamicValue::Const(b1) => { + if *b0 != *b1 { + return None + } + } + DynamicValue::Dynam(_) => return None, + }, + DynamicValue::Dynam(p0) => match tmp1 { + DynamicValue::Unknown => return None, + DynamicValue::Const(_) => return None, + DynamicValue::Dynam(p1) => { + if !backrefs.in_same_set(*p0, *p1).unwrap() { + return None + } + } + }, + } + } + from += 2 * w; + to += w; + } + // we can reduce if the loop did not terminate early + let mut res = Vec::with_capacity(next_bw); + let mut removed = Vec::with_capacity(next_bw); + let mut from = 0; + let mut to = 0; + while to < next_bw { + for j in 0..w { + let tmp0 = &lut[from + j]; + let tmp1 = &lut[from + w + j]; + res.push(*tmp0); + if let DynamicValue::Dynam(p) = tmp1 { + removed.push(*p); + } + } + from += 2 * w; + to += w; + } + Some((res, removed)) + } } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 06987565..e7246008 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -10,7 +10,7 @@ use awint::{ }; use crate::{ - ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, + ensemble::{DynamicValue, Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, SmallMap, }; @@ -92,9 +92,9 @@ impl Ensemble { /// Removes all `Const` inputs and assigns `Const` result if possible. /// Returns if a `Const` result was assigned (`Optimization::ConstifyEquiv` /// needs to be run by the caller). - pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> bool { + pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> Result { let lnode = self.lnodes.get_mut(p_lnode).unwrap(); - match &mut lnode.kind { + Ok(match &mut lnode.kind { LNodeKind::Copy(inp) => { // wire propogation let input_equiv = self.backrefs.get_val_mut(*inp).unwrap(); @@ -196,15 +196,14 @@ impl Ensemble { false } } - LNodeKind::DynamicLut(_inp, _lut) => { - // FIXME - /* + LNodeKind::DynamicLut(inp, lut) => { // acquire LUT inputs, for every constant input reduce the LUT - let len = usize::from(u8::try_from(inp.len()).unwrap()); + let mut len = usize::from(u8::try_from(inp.len()).unwrap()); for i in (0..len).rev() { let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); if let Value::Const(val) = equiv.val { + len -= 1; // we will be removing the input, mark it to be investigated self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); @@ -222,6 +221,8 @@ impl Ensemble { } } + // FIXME + /* // check for duplicate inputs of the same source 'outer: loop { // we have to reset every time because the removals can mess up any range of @@ -262,31 +263,68 @@ impl Ensemble { } } break - } + }*/ + /* // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing - let len = inp.len(); for i in (0..len).rev() { - if lut.bw() > 1 { - if let Some(reduced) = LNode::reduce_independent_lut(&lut, i) { + if lut.len() > 1 { + if let Some((reduced, removed)) = + LNode::reduce_independent_dynamic_lut(&self.backrefs, &lut, i) + { // independent of the `i`th bit - lut = reduced; + *lut = reduced; let p_inp = inp.remove(i); let equiv = self.backrefs.get_val(p_inp).unwrap(); self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); self.backrefs.remove_key(p_inp).unwrap(); + for remove in removed { + let equiv = self.backrefs.get_val(remove).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(remove).unwrap(); + } } } } - */ // sort inputs so that `LNode`s can be compared later // TODO? + */ - //false - todo!() + // special case + if lut.len() == 1 { + let bit = lut[0]; + match bit { + DynamicValue::Unknown => { + //let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + //equiv.val = Value::Unknown; + // not sure if `DynamicValue` is something that should map to a stronger + // `Value::ConstUnknown` or `Value::unreachable` + return Err(EvalError::OtherStr( + "encountered a dynamic lookup table that has been reduced down to \ + `DynamicValue::Unknown`", + )); + } + DynamicValue::Const(b) => { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::Const(b); + return Ok(true) + } + DynamicValue::Dynam(bit) => { + lnode.kind = LNodeKind::Copy(bit); + } + } + } + + // TODO all const + + // input independence automatically reduces all zeros and all ones LUTs, so just + // need to check if the LUT is one bit for constant generation + + false } - } + }) } /// Assigns `Const` result if possible. @@ -308,7 +346,7 @@ impl Ensemble { /// always be applied before any further optimizations are applied, so that /// `RemoveUnused` and `ConstPropogate` can be handled before any other /// optimization - pub fn preinvestigate_equiv(&mut self, p_equiv: PBack) { + pub fn preinvestigate_equiv(&mut self, p_equiv: PBack) -> Result<(), EvalError> { let mut non_self_rc = 0usize; let equiv = self.backrefs.get_val(p_equiv).unwrap(); let mut is_const = matches!(equiv.val, Value::Const(_)); @@ -325,7 +363,7 @@ impl Ensemble { } Referent::ThisLNode(p_lnode) => { // avoid checking more if it was already determined to be constant - if !is_const && self.const_eval_lnode(p_lnode) { + if !is_const && self.const_eval_lnode(p_lnode)? { is_const = true; } } @@ -361,6 +399,7 @@ impl Ensemble { self.optimizer .insert(Optimization::InvestigateEquiv0(p_equiv)); } + Ok(()) } /// Does not perform the final step @@ -413,16 +452,16 @@ impl Ensemble { let mut adv = self.backrefs.advancer(); while let Some(p_back) = adv.advance(&self.backrefs) { if let Referent::ThisEquiv = self.backrefs.get_key(p_back).unwrap() { - self.preinvestigate_equiv(p_back); + self.preinvestigate_equiv(p_back)?; } } while let Some(p_optimization) = self.optimizer.optimizations.min() { - self.optimize(p_optimization); + self.optimize(p_optimization)?; } self.recast_all_internal_ptrs() } - pub fn optimize(&mut self, p_optimization: POpt) { + pub fn optimize(&mut self, p_optimization: POpt) -> Result<(), EvalError> { let optimization = self .optimizer .optimizations @@ -431,13 +470,13 @@ impl Ensemble { .0; match optimization { Optimization::Preinvestigate(p_equiv) => { - self.preinvestigate_equiv(p_equiv); + self.preinvestigate_equiv(p_equiv)?; } Optimization::RemoveEquiv(p_back) => { let p_equiv = if let Some(equiv) = self.backrefs.get_val(p_back) { equiv.p_self_equiv } else { - return + return Ok(()) }; // remove all associated LNodes first let mut adv = self.backrefs.advancer_surject(p_back); @@ -475,7 +514,7 @@ impl Ensemble { unreachable!() } } else { - return + return Ok(()) }; let mut adv = self.backrefs.advancer_surject(p_ident); while let Some(p_back) = adv.advance(&self.backrefs) { @@ -548,7 +587,7 @@ impl Ensemble { } Optimization::ConstifyEquiv(p_back) => { if !self.backrefs.contains(p_back) { - return + return Ok(()) }; // for removing `ThisLNode` safely let mut remove = SmallVec::<[PBack; 16]>::new(); @@ -583,13 +622,13 @@ impl Ensemble { } Optimization::RemoveLNode(p_back) => { if !self.backrefs.contains(p_back) { - return + return Ok(()) } todo!() } Optimization::InvestigateUsed(p_back) => { if !self.backrefs.contains(p_back) { - return + return Ok(()) }; let mut found_use = false; let mut adv = self.backrefs.advancer_surject(p_back); @@ -630,9 +669,9 @@ impl Ensemble { } Optimization::InvestigateConst(p_lnode) => { if !self.lnodes.contains(p_lnode) { - return + return Ok(()) }; - if self.const_eval_lnode(p_lnode) { + if self.const_eval_lnode(p_lnode)? { self.optimizer.insert(Optimization::ConstifyEquiv( self.lnodes.get(p_lnode).unwrap().p_self, )); @@ -640,7 +679,7 @@ impl Ensemble { } Optimization::InvestigateLoopDriverConst(p_tnode) => { if !self.tnodes.contains(p_tnode) { - return + return Ok(()) }; if self.const_eval_tnode(p_tnode) { self.optimizer.insert(Optimization::ConstifyEquiv( @@ -661,6 +700,7 @@ impl Ensemble { // with common inputs } } + Ok(()) } } diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index c101232e..495180a7 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -464,6 +464,35 @@ fn lower_elementary_to_lnodes_intermediate( this.union_equiv(p_equiv0, p_equiv1).unwrap(); } } + Lut([lut, inx]) => { + let inx_len = this.stator.states[inx].p_self_bits.len(); + let out_bw = this.stator.states[p_state].p_self_bits.len(); + let num_entries = 1usize.checked_shl(u32::try_from(inx_len).unwrap()).unwrap(); + // this must be handled upstream + debug_assert_eq!( + out_bw * num_entries, + this.stator.states[lut].p_self_bits.len() + ); + + let out_bw = this.stator.states[p_state].p_self_bits.len(); + for bit_i in 0..out_bw { + let mut p_lut_bits = vec![]; + let inx_bits = this.stator.states[inx].p_self_bits.clone(); + let lut_bits = &this.stator.states[lut].p_self_bits; + for i in 0..num_entries { + if let Some(p_back) = lut_bits[(i * out_bw) + bit_i] { + p_lut_bits.push(DynamicValue::Dynam(p_back)); + } else { + p_lut_bits.push(DynamicValue::Unknown); + } + } + let p_equiv0 = this + .make_dynamic_lut(&inx_bits, &p_lut_bits, Some(p_state)) + .unwrap(); + let p_equiv1 = this.stator.states[p_state].p_self_bits[bit_i].unwrap(); + this.union_equiv(p_equiv0, p_equiv1).unwrap(); + } + } Mux([lhs, rhs, b]) => { let inx_bit = &this.stator.states[b].p_self_bits; debug_assert_eq!(inx_bit.len(), 1); diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 077daec5..5e6355c6 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -9,11 +9,10 @@ use awint::{ Awi, Bits, }; -use super::DynamicValue; use crate::{ ensemble::{ - value::Evaluator, LNode, LNodeKind, Notary, Optimizer, PLNode, PRNode, PTNode, State, - Stator, TNode, Value, + value::Evaluator, DynamicValue, LNode, LNodeKind, Notary, Optimizer, PLNode, PRNode, + PTNode, State, Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, }; @@ -115,46 +114,64 @@ impl Ensemble { match referent { Referent::ThisEquiv => (), Referent::ThisLNode(p_lnode) => { - if p_lnode.recast(&p_lnode_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PLNode")); + if let Err(e) = p_lnode.recast(&p_lnode_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in a `Referent::ThisLNode`" + ))); } } Referent::ThisTNode(p_tnode) => { - if p_tnode.recast(&p_tnode_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PTNode")); + if let Err(e) = p_tnode.recast(&p_tnode_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in a `Referent::ThisTNode`" + ))); } } Referent::ThisStateBit(..) => unreachable!(), Referent::Input(p_lnode) => { - if p_lnode.recast(&p_lnode_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PLNode")); + if let Err(e) = p_lnode.recast(&p_lnode_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in a `Referent::Input`" + ))); } } Referent::LoopDriver(p_tnode) => { - if p_tnode.recast(&p_tnode_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PTNode")); + if let Err(e) = p_tnode.recast(&p_tnode_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in a `Referent::LoopDriver`" + ))); } } Referent::ThisRNode(p_rnode) => { - if p_rnode.recast(&p_rnode_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PRNode")); + if let Err(e) = p_rnode.recast(&p_rnode_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in a `Referent::ThisRNode`" + ))); } } } } let p_back_recaster = self.backrefs.compress_and_shrink_recaster(); - if self.backrefs.recast(&p_back_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PBack")); + if let Err(e) = self.backrefs.recast(&p_back_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in the backrefs" + ))); } - if self.notary.recast(&p_back_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PBack")); + if let Err(e) = self.notary.recast(&p_back_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in the notary" + ))); } - if self.lnodes.recast(&p_back_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PBack")); + if let Err(e) = self.lnodes.recast(&p_back_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in the lnodes" + ))); } - if self.tnodes.recast(&p_back_recaster).is_err() { - return Err(EvalError::OtherStr("recast error with a PBack")); + if let Err(e) = self.tnodes.recast(&p_back_recaster) { + return Err(EvalError::OtherString(format!( + "recast error with {e} in the tnodes" + ))); } Ok(()) } @@ -551,6 +568,7 @@ impl Ensemble { Some(p_equiv) } + /// Creates separate unique `Referent::Input`s as necessary #[must_use] pub fn make_dynamic_lut( &mut self, diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index b88dc785..4e349a68 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -438,6 +438,9 @@ impl Ensemble { } // reduce the LUT based on fixed and known bits for i in (0..len).rev() { + if len == 1 { + break + } if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); LNode::reduce_lut(&mut lut_unknown, i, inp_val.get(i).unwrap()); diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index 9934a50f..4f984bcb 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -242,10 +242,10 @@ impl Ensemble { StaticLut(ConcatType::from_iter([inx]), lit); } lock.ensemble.dec_rc(lut).unwrap(); - false - } else { - true } + // else it is a dynamic LUT that could be lowered on the + // `LNode` side if needed + false } Get([bits, inx]) => { if let Literal(ref lit) = lock.ensemble.stator.states[inx].op { diff --git a/starlight/src/misc/rng.rs b/starlight/src/misc/rng.rs index 4358d202..43302713 100644 --- a/starlight/src/misc/rng.rs +++ b/starlight/src/misc/rng.rs @@ -202,6 +202,9 @@ impl StarRng { // and for the potential repeat cases. This would also eliminate padding // needs in several places such as here + // TODO for the matching macro we probably want a general comparison that can + // match a partial range against a full range, and a partial against a partial + /// This performs one step of a fuzzer where a random width of ones is /// rotated randomly and randomly ORed, ANDed, or XORed to `x`. `pad` needs /// to have the same bitwidth as `x`. diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 81f78375..3b014e10 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -473,7 +473,11 @@ fn lut_dynamic_optimization() { b.retro_bool_(lut.get(i).unwrap()).unwrap(); } } - awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + // TODO + //awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + if let awi::Ok(res) = output.eval_bool() { + awi::assert_eq!(res, expected_output.to_bool()); + } drop(epoch); } From 6482cdc7323ba98fb7105f58925050e0b1a59f2c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 1 Jan 2024 23:38:36 -0600 Subject: [PATCH 053/119] improvements --- starlight/src/ensemble/optimize.rs | 42 +++++++++++++++++++++++++----- starlight/src/ensemble/value.rs | 18 +++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index e7246008..3873b74a 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroUsize; +use std::{mem, num::NonZeroUsize}; use awint::{ awint_dag::{ @@ -292,8 +292,10 @@ impl Ensemble { // TODO? */ - // special case - if lut.len() == 1 { + let w = NonZeroUsize::new(lut.len()).unwrap(); + + // special case forwarding + if w.get() == 1 { let bit = lut[0]; match bit { DynamicValue::Unknown => { @@ -313,15 +315,41 @@ impl Ensemble { } DynamicValue::Dynam(bit) => { lnode.kind = LNodeKind::Copy(bit); + self.optimizer + .insert(Optimization::ForwardEquiv(lnode.p_self)); + return Ok(false) } } } - // TODO all const - - // input independence automatically reduces all zeros and all ones LUTs, so just - // need to check if the LUT is one bit for constant generation + // check if all const + let mut all_const = true; + for lut_bit in lut.iter() { + match lut_bit { + DynamicValue::Const(_) => (), + DynamicValue::Unknown | DynamicValue::Dynam(_) => { + all_const = false; + break + } + } + } + if all_const { + let mut awi_lut = Awi::zero(w); + for (i, lut_bit) in lut.iter().enumerate() { + if let DynamicValue::Const(b) = lut_bit { + awi_lut.set(i, *b).unwrap(); + } + } + if (w.get() == 2) && awi_lut.get(1).unwrap() { + lnode.kind = LNodeKind::Copy(inp[0]); + self.optimizer + .insert(Optimization::ForwardEquiv(lnode.p_self)); + } else { + let inp = mem::take(inp); + lnode.kind = LNodeKind::Lut(inp, awi_lut); + } + } false } }) diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 4e349a68..8f0a59a8 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -414,6 +414,7 @@ impl Ensemble { } } let mut lut = Awi::zero(NonZeroUsize::new(original_lut.len()).unwrap()); + let mut reduced_lut = original_lut.clone(); // this kind of unknown includes unfixed bits let mut lut_unknown = lut.clone(); for (i, value) in original_lut.iter().enumerate() { @@ -442,8 +443,10 @@ impl Ensemble { break } if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { - LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); - LNode::reduce_lut(&mut lut_unknown, i, inp_val.get(i).unwrap()); + let bit = inp_val.get(i).unwrap(); + LNode::reduce_lut(&mut lut, i, bit); + LNode::reduce_lut(&mut lut_unknown, i, bit); + reduced_lut = LNode::reduce_dynamic_lut(&reduced_lut, i, bit).0; // remove the input bits len -= 1; let w = NonZeroUsize::new(len).unwrap(); @@ -513,6 +516,17 @@ impl Ensemble { }); } } + // make sure we only request the LUT bits we need + for lut_bit in reduced_lut { + if let DynamicValue::Dynam(p) = lut_bit { + // TODO make the priority make the index bits always requested fully first + res.push(RequestLNode { + depth: depth - 1, + number_a: 0, + p_back_lnode: p, + }); + } + } } } res From c886a1bf68c79d4e8a11529eab11f089fe168015 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 13:31:55 -0600 Subject: [PATCH 054/119] fixes --- starlight/src/ensemble/optimize.rs | 2 +- starlight/src/ensemble/value.rs | 135 +++++++++++++++++------------ testcrate/tests/basic.rs | 2 +- testcrate/tests/luts.rs | 42 +++++---- 4 files changed, 108 insertions(+), 73 deletions(-) diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 3873b74a..c3195bc4 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -210,7 +210,7 @@ impl Ensemble { self.backrefs.remove_key(p_inp).unwrap(); inp.remove(i); - let (tmp, removed) = LNode::reduce_dynamic_lut(&lut, i, val); + let (tmp, removed) = LNode::reduce_dynamic_lut(lut, i, val); *lut = tmp; for remove in removed { let equiv = self.backrefs.get_val(remove).unwrap(); diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 8f0a59a8..44794f1d 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -413,21 +413,37 @@ impl Ensemble { } } } - let mut lut = Awi::zero(NonZeroUsize::new(original_lut.len()).unwrap()); + let lut_w = NonZeroUsize::new(original_lut.len()).unwrap(); + let mut lut = Awi::zero(lut_w); let mut reduced_lut = original_lut.clone(); - // this kind of unknown includes unfixed bits - let mut lut_unknown = lut.clone(); + let mut lut_fixed = Awi::zero(lut_w); + let mut lut_unknown = Awi::zero(lut_w); for (i, value) in original_lut.iter().enumerate() { match value { - DynamicValue::Unknown => lut_unknown.set(i, true).unwrap(), - DynamicValue::Const(b) => lut.set(i, *b).unwrap(), + DynamicValue::Unknown => { + lut_fixed.set(i, true).unwrap(); + lut_unknown.set(i, true).unwrap(); + } + DynamicValue::Const(b) => { + lut_fixed.set(i, true).unwrap(); + lut.set(i, *b).unwrap() + } DynamicValue::Dynam(p) => { let equiv = self.backrefs.get_val(*p).unwrap(); match equiv.val { - Value::Unknown => lut_unknown.set(i, true).unwrap(), - Value::Const(b) => lut.set(i, b).unwrap(), + Value::Unknown => { + lut_unknown.set(i, true).unwrap(); + if equiv.change_visit == self.evaluator.change_visit_gen() { + lut_fixed.set(i, true).unwrap(); + } + } + Value::Const(b) => { + lut_fixed.set(i, true).unwrap(); + lut.set(i, b).unwrap() + } Value::Dynam(b) => { if equiv.change_visit == self.evaluator.change_visit_gen() { + lut_fixed.set(i, true).unwrap(); lut.set(i, b).unwrap() } else { lut_unknown.set(i, true).unwrap(); @@ -437,76 +453,85 @@ impl Ensemble { } } } - // reduce the LUT based on fixed and known bits + // we need to reduce first, reduce the LUT based on fixed and known bits for i in (0..len).rev() { - if len == 1 { - break - } if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { let bit = inp_val.get(i).unwrap(); LNode::reduce_lut(&mut lut, i, bit); + LNode::reduce_lut(&mut lut_fixed, i, bit); LNode::reduce_lut(&mut lut_unknown, i, bit); reduced_lut = LNode::reduce_dynamic_lut(&reduced_lut, i, bit).0; // remove the input bits - len -= 1; + len = len.checked_sub(1).unwrap(); + if len == 0 { + // only one LUT bit left, no inputs + if lut_fixed.get(0).unwrap() { + if lut_unknown.get(0).unwrap() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; + } else { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(lut.get(0).unwrap()), + })); + return vec![]; + } + } else { + let lut_bit = reduced_lut[0]; + match lut_bit { + DynamicValue::Unknown | DynamicValue::Const(_) => { + unreachable!() + } + DynamicValue::Dynam(p) => { + res.push(RequestLNode { + depth: depth - 1, + number_a: 0, + p_back_lnode: p, + }); + return res; + } + } + } + } let w = NonZeroUsize::new(len).unwrap(); if i != len { - cc!(inp_val[(i + 1)..], ..i; inp_val).unwrap(); - cc!(fixed[(i + 1)..], ..i; fixed).unwrap(); - cc!(unknown[(i + 1)..], ..i; unknown).unwrap(); + cc!(.., inp_val[(i + 1)..], ..i; inp_val).unwrap(); + cc!(.., fixed[(i + 1)..], ..i; fixed).unwrap(); + cc!(.., unknown[(i + 1)..], ..i; unknown).unwrap(); } inp_val.zero_resize(w); fixed.zero_resize(w); unknown.zero_resize(w); } } - for i in 0..len { - // if there are any fixed and unknown input bits, and there are any unknown bits - // in the reduced table at all, or the fixed and unknown bits can influence the - // value, then the value of this equivalence can also be - // fixed to unknown - if fixed.get(i).unwrap() - && unknown.get(i).unwrap() - && (!(lut_unknown.is_zero() - && LNode::reduce_independent_lut(&mut lut.clone(), i))) - { + // TODO better early `Unknown` change detection + + // if the LUT is all fixed and known ones or zeros, we can know that any unfixed + // or unknown changes will be unable to affect the + // output + if lut_fixed.is_umax() && lut_unknown.is_zero() { + if lut.is_zero() { self.evaluator.insert(Eval::Change(Change { depth, p_equiv, - value: Value::Unknown, + value: Value::Dynam(false), + })); + return vec![]; + } else if lut.is_umax() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(true), })); return vec![]; } } - // if the LUT is all ones or all zeros, we can know that any unfixed or - // unknown changes will be unable to affect the - // output - if lut.is_zero() { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Dynam(false), - })); - return vec![]; - } else if lut.is_umax() { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Dynam(true), - })); - return vec![]; - } - // TODO prioritize bits that could lead to number_a optimization - /*let mut skip = 0; - for i in 0..len { - if fixed.get(i).unwrap() && !unknown.get(i).unwrap() { - skip += 1; - } else if unknown.get(i).unwrap() { - // assume unchanging - lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); - // - } else {} - }*/ + for i in (0..len).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { res.push(RequestLNode { diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 4c59feab..b0a41eee 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -111,7 +111,7 @@ fn all_variations() { epoch.verify_integrity().unwrap(); assert!(y1.eval().is_err()); x1.retro_bool_(true).unwrap(); - assert_eq!(y1.eval_bool().unwrap(), true); + assert!(y1.eval_bool().unwrap()); assert!(y8.eval().is_err()); x8.retro_u8_(u8::MAX).unwrap(); assert_eq!(y8.eval_u8().unwrap(), u8::MAX); diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index 3b014e10..bfa00fd7 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -158,6 +158,7 @@ fn general_reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { } } +#[derive(Debug)] enum DynamicBool { Bool(bool), Lazy(LazyAwi), @@ -438,29 +439,42 @@ fn lut_dynamic_optimization() { let output = EvalAwi::from(&output); epoch.optimize().unwrap(); - /*{ + { use awi::*; epoch.ensemble(|ensemble| { - if expected_lut.bw() == 1 { - // there should be no `LNode` since it was optimized to a - // constant - let mut tmp = ensemble.lnodes.vals(); - awi::assert!(tmp.next().is_none()); - awi::assert_eq!(expected_lut.bw(), 1); + if known_lut_bits_reduced.is_umax() { + if known_lut_bits_reduced.bw() == 1 { + // there should be no `LNode` since it was optimized to a + // constant + let mut tmp = ensemble.lnodes.vals(); + awi::assert!(tmp.next().is_none()); + awi::assert_eq!(expected_lut.bw(), 1); + } else { + // there should be one static LUT `LNode` + let mut tmp = ensemble.lnodes.vals(); + let lnode = tmp.next().unwrap(); + awi::assert!(tmp.next().is_none()); + match &lnode.kind { + LNodeKind::Lut(_, lnode_lut) => { + awi::assert_eq!(*lnode_lut, expected_lut); + } + _ => unreachable!(), + } + } } else { - // get the sole `LNode` that should exist by this point + // there should be one dynamic LUT `LNode` let mut tmp = ensemble.lnodes.vals(); let lnode = tmp.next().unwrap(); awi::assert!(tmp.next().is_none()); match &lnode.kind { - LNodeKind::Lut(_, lnode_lut) => { - awi::assert_eq!(*lnode_lut, expected_lut); + LNodeKind::DynamicLut(_, lnode_lut) => { + awi::assert_eq!(lnode_lut.len(), expected_lut.bw()); } _ => unreachable!(), } } }); - }*/ + } // set unknown inputs for (i, input) in inputs.iter().enumerate() { @@ -473,11 +487,7 @@ fn lut_dynamic_optimization() { b.retro_bool_(lut.get(i).unwrap()).unwrap(); } } - // TODO - //awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); - if let awi::Ok(res) = output.eval_bool() { - awi::assert_eq!(res, expected_output.to_bool()); - } + awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); drop(epoch); } From d711f1fd928aac03bb2741872f140b9e0aaa0507 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 15:11:40 -0600 Subject: [PATCH 055/119] more fixes --- starlight/src/ensemble/optimize.rs | 19 +++++++++++--- testcrate/tests/luts.rs | 40 +++++++++++++++++++----------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index c3195bc4..537d5235 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -196,7 +196,20 @@ impl Ensemble { false } } - LNodeKind::DynamicLut(inp, lut) => { + LNodeKind::DynamicLut(inp, ref mut lut) => { + // acquire LUT table inputs, convert to constants + for lut_bit in lut.iter_mut() { + if let DynamicValue::Dynam(p) = lut_bit { + let equiv = self.backrefs.get_val(*p).unwrap(); + if let Value::Const(val) = equiv.val { + // we will be removing the input, mark it to be investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(*p).unwrap(); + *lut_bit = DynamicValue::Const(val); + } + } + } // acquire LUT inputs, for every constant input reduce the LUT let mut len = usize::from(u8::try_from(inp.len()).unwrap()); for i in (0..len).rev() { @@ -265,12 +278,11 @@ impl Ensemble { break }*/ - /* // now check for input independence, e.x. for 0101 the 2^1 bit changes nothing for i in (0..len).rev() { if lut.len() > 1 { if let Some((reduced, removed)) = - LNode::reduce_independent_dynamic_lut(&self.backrefs, &lut, i) + LNode::reduce_independent_dynamic_lut(&self.backrefs, lut, i) { // independent of the `i`th bit *lut = reduced; @@ -290,7 +302,6 @@ impl Ensemble { } // sort inputs so that `LNode`s can be compared later // TODO? - */ let w = NonZeroUsize::new(lut.len()).unwrap(); diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index bfa00fd7..c894ac02 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -350,9 +350,9 @@ fn lut_dynamic_optimization() { // The first number is the base number of iterations, the others are counters to // make sure the rng isn't broken const N: (u64, u64, u64) = if cfg!(debug_assertions) { - (32, 1984, 470) + (32, 1984, 690) } else { - (512, 31744, 7371) + (512, 31744, 9575) }; let mut rng = StarRng::new(0); let mut num_lut_bits = 0u64; @@ -388,13 +388,18 @@ fn lut_dynamic_optimization() { remaining_inp_len -= 1; } } - for i in (0..remaining_inp_len).rev() { - if expected_lut.bw() == 1 { - break - } - if general_reduce_independent_lut(&mut expected_lut, i) { - known_lut_bits_reduced = - general_reduce_lut(&known_lut_bits_reduced, i, lut_input.get(i).unwrap()); + if known_lut_bits_reduced.is_umax() { + for i in (0..remaining_inp_len).rev() { + if expected_lut.bw() == 1 { + break + } + if general_reduce_independent_lut(&mut expected_lut, i) { + known_lut_bits_reduced = general_reduce_lut( + &known_lut_bits_reduced, + i, + lut_input.get(i).unwrap(), + ); + } } } num_simplified_lut_bits += expected_lut.bw() as u64; @@ -440,15 +445,19 @@ fn lut_dynamic_optimization() { epoch.optimize().unwrap(); { - use awi::*; epoch.ensemble(|ensemble| { - if known_lut_bits_reduced.is_umax() { - if known_lut_bits_reduced.bw() == 1 { - // there should be no `LNode` since it was optimized to a - // constant + if known_lut_bits_reduced.bw() == 1 { + // there should be no `LNode` since it was optimized to a + // constant or forwarded + let mut tmp = ensemble.lnodes.vals(); + awi::assert!(tmp.next().is_none()); + awi::assert_eq!(expected_lut.bw(), 1); + } else if known_lut_bits_reduced.is_umax() { + if (expected_lut.bw() == 1) + || ((expected_lut.bw() == 2) && expected_lut.get(1).unwrap()) + { let mut tmp = ensemble.lnodes.vals(); awi::assert!(tmp.next().is_none()); - awi::assert_eq!(expected_lut.bw(), 1); } else { // there should be one static LUT `LNode` let mut tmp = ensemble.lnodes.vals(); @@ -488,6 +497,7 @@ fn lut_dynamic_optimization() { } } awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); + epoch.verify_integrity().unwrap(); drop(epoch); } From 3ec1ad54b00d7bfa5268dc859f108c714e743437 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 17:13:11 -0600 Subject: [PATCH 056/119] get tests working again --- starlight/src/ensemble/value.rs | 79 +++++++++++++++------------------ 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 44794f1d..43abc8e1 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -378,7 +378,7 @@ impl Ensemble { // } else {} }*/ - for i in (0..len).rev() { + for i in (0..inp.len()).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { res.push(RequestLNode { depth: depth - 1, @@ -463,50 +463,41 @@ impl Ensemble { reduced_lut = LNode::reduce_dynamic_lut(&reduced_lut, i, bit).0; // remove the input bits len = len.checked_sub(1).unwrap(); - if len == 0 { - // only one LUT bit left, no inputs - if lut_fixed.get(0).unwrap() { - if lut_unknown.get(0).unwrap() { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Unknown, - })); - return vec![]; - } else { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Dynam(lut.get(0).unwrap()), - })); - return vec![]; - } - } else { - let lut_bit = reduced_lut[0]; - match lut_bit { - DynamicValue::Unknown | DynamicValue::Const(_) => { - unreachable!() - } - DynamicValue::Dynam(p) => { - res.push(RequestLNode { - depth: depth - 1, - number_a: 0, - p_back_lnode: p, - }); - return res; - } - } - } + } + } + if len == 0 { + // only one LUT bit left, no inputs + if lut_fixed.get(0).unwrap() { + if lut_unknown.get(0).unwrap() { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; + } else { + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Dynam(lut.get(0).unwrap()), + })); + return vec![]; } - let w = NonZeroUsize::new(len).unwrap(); - if i != len { - cc!(.., inp_val[(i + 1)..], ..i; inp_val).unwrap(); - cc!(.., fixed[(i + 1)..], ..i; fixed).unwrap(); - cc!(.., unknown[(i + 1)..], ..i; unknown).unwrap(); + } else { + let lut_bit = reduced_lut[0]; + match lut_bit { + DynamicValue::Unknown | DynamicValue::Const(_) => { + unreachable!() + } + DynamicValue::Dynam(p) => { + res.push(RequestLNode { + depth: depth - 1, + number_a: 0, + p_back_lnode: p, + }); + return res; + } } - inp_val.zero_resize(w); - fixed.zero_resize(w); - unknown.zero_resize(w); } } // TODO better early `Unknown` change detection @@ -532,7 +523,7 @@ impl Ensemble { } } - for i in (0..len).rev() { + for i in (0..inp.len()).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { res.push(RequestLNode { depth: depth - 1, From 1e2f9db9c21d0323c0871f8267fc754589602005 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 17:23:29 -0600 Subject: [PATCH 057/119] add missing test --- testcrate/tests/luts.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index c894ac02..a1a5664f 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -501,8 +501,7 @@ fn lut_dynamic_optimization() { drop(epoch); } - /* - // subtest 3: make sure evaluation can handle dynamically unknown inputs in + // subtest to make sure evaluation can handle dynamically unknown inputs in // several cases { let epoch = Epoch::new(); @@ -515,9 +514,16 @@ fn lut_dynamic_optimization() { total.set(i, tmp.to_bool()).unwrap(); inputs.push(tmp); } + let mut total_lut_bits = Awi::zero(lut.nzbw()); + let mut lut_bits = vec![]; + for i in 0..lut.bw() { + let tmp = LazyAwi::opaque(bw(1)); + total_lut_bits.set(i, tmp.to_bool()).unwrap(); + lut_bits.push(tmp); + } let mut output = Awi::zero(bw(1)); - output.lut_(&Awi::from(&lut), &total).unwrap(); + output.lut_(&total_lut_bits, &total).unwrap(); let output = EvalAwi::from(&output); epoch.optimize().unwrap(); @@ -526,7 +532,12 @@ fn lut_dynamic_optimization() { inputs[i].retro_bool_(lut_input.get(i).unwrap()).unwrap(); } } - if expected_lut.bw() == 1 { + for i in 0..lut_w.get() { + if known_lut_bits.get(i).unwrap() { + lut_bits[i].retro_bool_(lut.get(i).unwrap()).unwrap(); + } + } + if (expected_lut.bw() == 1) && (known_lut_bits_reduced.is_umax()) { // evaluation should produce a known value awi::assert_eq!(output.eval_bool().unwrap(), expected_output.to_bool()); } else { @@ -535,7 +546,7 @@ fn lut_dynamic_optimization() { } drop(epoch); - }*/ + } } } assert_eq!((num_lut_bits, num_simplified_lut_bits), (N.1, N.2)); From 4d4b71b2f1d2d6beba5d336e0e80d87a716d2f92 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 19:46:15 -0600 Subject: [PATCH 058/119] begin enabling more --- starlight/src/awi_structs/lazy_awi.rs | 12 ++++++++++++ starlight/src/ensemble/state.rs | 8 +++++--- starlight/src/lower/lower_state.rs | 3 +-- testcrate/tests/luts.rs | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index 60b5ad2a..305e6af0 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -245,6 +245,18 @@ impl fmt::Debug for LazyAwi { forward_debug_fmt!(LazyAwi); +impl From<&LazyAwi> for dag::Awi { + fn from(value: &LazyAwi) -> Self { + dag::Awi::from(value.as_ref()) + } +} + +impl From<&LazyAwi> for dag::ExtAwi { + fn from(value: &LazyAwi) -> Self { + dag::ExtAwi::from(value.as_ref()) + } +} + /// The same as [LazyAwi](crate::LazyAwi), except that it allows for checking /// bitwidths at compile time. #[derive(Clone)] diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 495180a7..4b8953a6 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -494,14 +494,16 @@ fn lower_elementary_to_lnodes_intermediate( } } Mux([lhs, rhs, b]) => { + let out_bw = this.stator.states[p_state].p_self_bits.len(); let inx_bit = &this.stator.states[b].p_self_bits; debug_assert_eq!(inx_bit.len(), 1); + debug_assert_eq!(out_bw, this.stator.states[lhs].p_self_bits.len()); + debug_assert_eq!(out_bw, this.stator.states[rhs].p_self_bits.len()); let inx_bit = inx_bit[0]; - let out_bw = this.stator.states[p_state].p_self_bits.len(); for bit_i in 0..out_bw { - let lut0 = this.stator.states[lhs].p_self_bits[0].unwrap(); - let lut1 = this.stator.states[rhs].p_self_bits[0].unwrap(); + let lut0 = this.stator.states[lhs].p_self_bits[bit_i].unwrap(); + let lut1 = this.stator.states[rhs].p_self_bits[bit_i].unwrap(); let p_equiv0 = this .make_dynamic_lut( &[inx_bit], diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index 4f984bcb..66c5ff17 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -217,8 +217,7 @@ impl Ensemble { Opaque(..) | Literal(_) | Assert(_) | Copy(_) | StaticGet(..) | Repeat(_) | StaticLut(..) => false, // for dynamic LUTs - // FIXME - Mux(_) => true, + Mux(_) => false, Lut([lut, inx]) => { if let Literal(ref lit) = lock.ensemble.stator.states[lut].op { let lit = lit.clone(); diff --git a/testcrate/tests/luts.rs b/testcrate/tests/luts.rs index a1a5664f..1b12e391 100644 --- a/testcrate/tests/luts.rs +++ b/testcrate/tests/luts.rs @@ -1,3 +1,5 @@ +#![allow(clippy::needless_range_loop)] + use std::num::NonZeroUsize; use starlight::{ From 823f5721322606523c7a34c04280bd323bd2f66f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 21:14:49 -0600 Subject: [PATCH 059/119] general multiplexing --- starlight/src/awi_structs/temporal.rs | 15 +++---- starlight/src/lower/lower_op.rs | 2 +- starlight/src/lower/meta.rs | 58 ++++++++++++++++++--------- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index 8a1d295a..a70a6872 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -1,11 +1,11 @@ -use std::{borrow::Borrow, cmp::min, num::NonZeroUsize, ops::Deref}; +use std::{borrow::Borrow, num::NonZeroUsize, ops::Deref}; use awint::{ awint_dag::{smallvec::smallvec, Lineage, Op}, dag::{self, awi, Awi, Bits, InlAwi}, }; -use crate::{epoch::get_current_epoch, lower::meta::selector}; +use crate::{epoch::get_current_epoch, lower::meta::general_mux}; /// Provides a way to temporally wrap around a combinatorial circuit. /// @@ -240,22 +240,19 @@ impl Net { let mut in_range = should_stay_zero.is_zero(); let inx = if max_inx_bits < inx.bw() { awi!(inx[..max_inx_bits]).unwrap() + } else if max_inx_bits > inx.bw() { + awi!(zero: .., inx; ..max_inx_bits).unwrap() } else { Awi::from(inx) }; - let signals = selector(&inx, None); - if (!self.len().is_power_of_two()) && (inx.bw() == max_inx_bits) { + if (!self.len().is_power_of_two()) && (inx.bw() >= max_inx_bits) { // dance to avoid stuff that can get lowered into a full `BITS` sized comparison let mut max = Awi::zero(inx.nzbw()); max.usize_(max_inx); let le = inx.ule(&max).unwrap(); in_range &= le; } - - let mut tmp = Awi::zero(self.nzbw()); - for i in 0..min(self.len(), signals.len()) { - tmp.mux_(&self.ports[i], signals[i].to_bool()).unwrap(); - } + let tmp = general_mux(&self.ports, &inx); self.source.drive(&tmp).unwrap(); dag::Option::some_at_dagtime((), in_range) } diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 80465b0d..26f46004 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -661,7 +661,7 @@ pub fn lower_op( x0.clone() } } else { - mux_(&x0, &x1, &inx_tmp) + static_mux(&x0, &x1, &inx_tmp) }; m.graft(&[out.state(), x0.state(), x1.state(), inx_tmp.state()]); } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 3de2e600..708d1cd8 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -123,10 +123,6 @@ pub fn reverse(x: &Bits) -> Awi { concat(nzbw, out) } -/// Given `inx.bw()` bits, this returns `2^inx.bw()` signals for every possible -/// state of `inx`. The `i`th signal is true only if `inx.to_usize() == i`. -/// `cap` optionally restricts the number of signals. If `cap` is 0, there is -/// one signal line set to true unconditionally. pub fn selector(inx: &Bits, cap: Option) -> Vec { let num = cap.unwrap_or_else(|| 1usize << inx.bw()); if num == 0 { @@ -137,7 +133,7 @@ pub fn selector(inx: &Bits, cap: Option) -> Vec { return vec![inlawi!(1)] } let lb_num = num.next_power_of_two().trailing_zeros() as usize; - let mut signals = vec![]; + let mut signals = Vec::with_capacity(num); for i in 0..num { let mut signal = inlawi!(1); for j in 0..lb_num { @@ -180,6 +176,45 @@ pub fn selector_awi(inx: &Bits, cap: Option) -> Awi { concat(nzbw, signals) } +pub fn static_mux(x0: &Bits, x1: &Bits, inx: &Bits) -> Awi { + debug_assert_eq!(x0.bw(), x1.bw()); + debug_assert_eq!(inx.bw(), 1); + let nzbw = x0.nzbw(); + let mut signals = SmallVec::with_capacity(nzbw.get()); + for i in 0..x0.bw() { + let mut tmp = inlawi!(0); + static_lut!(tmp; 1100_1010; x0.get(i).unwrap(), x1.get(i).unwrap(), inx); + signals.push(tmp.state()); + } + concat(nzbw, signals) +} + +// uses dynamic LUTs to wholesale multiplex one or more inputs +pub fn general_mux(inputs: &[Awi], inx: &Bits) -> Awi { + debug_assert!(!inputs.is_empty()); + let nzbw = inputs[0].nzbw(); + let lut_w = NonZeroUsize::new(inputs.len().next_power_of_two()).unwrap(); + debug_assert_eq!(1 << inx.bw(), lut_w.get()); + let mut out_signals = SmallVec::with_capacity(nzbw.get()); + let unknown = Awi::opaque(bw(1)); + for out_i in 0..nzbw.get() { + let mut lut = Vec::with_capacity(lut_w.get()); + for input in inputs { + lut.push((input.state(), out_i, bw(1))); + } + // fill up the rest of the way as necessary + for _ in lut.len()..lut_w.get() { + lut.push((unknown.state(), 0, bw(1))); + } + let lut = Awi::new( + lut_w, + Op::ConcatFields(ConcatFieldsType::from_iter(lut.iter().cloned())), + ); + out_signals.push(Awi::new(bw(1), Op::Lut([lut.state(), inx.state()])).state()); + } + concat(nzbw, out_signals) +} + /// Trailing smear, given the value of `inx` it will set all bits in the vector /// up to but not including the one indexed by `inx`. This means that /// `inx.to_usize() == 0` sets no bits, and `inx.to_usize() == num_bits` sets @@ -254,19 +289,6 @@ pub fn tsmear_awi(inx: &Bits, num_signals: usize) -> Awi { concat(nzbw, signals) } -pub fn mux_(x0: &Bits, x1: &Bits, inx: &Bits) -> Awi { - debug_assert_eq!(x0.bw(), x1.bw()); - debug_assert_eq!(inx.bw(), 1); - let nzbw = x0.nzbw(); - let mut signals = SmallVec::with_capacity(nzbw.get()); - for i in 0..x0.bw() { - let mut tmp = inlawi!(0); - static_lut!(tmp; 1100_1010; x0.get(i).unwrap(), x1.get(i).unwrap(), inx); - signals.push(tmp.state()); - } - concat(nzbw, signals) -} - /* Normalize. Table size explodes really fast if trying to keep as a single LUT, let's use a meta LUT. From 3e3aa4c8575674980f90a58c1c0799691ad21f3e Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 21:39:34 -0600 Subject: [PATCH 060/119] Update stats.rs --- testcrate/tests/stats.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index 52cda2f8..bc7e9eda 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -51,7 +51,7 @@ fn stats_different_prunings() { epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); - assert_eq!(ensemble.stator.states.len(), 19); + assert_eq!(ensemble.stator.states.len(), 14); assert_eq!(ensemble.backrefs.len_keys(), 0); assert_eq!(ensemble.backrefs.len_vals(), 0); }); @@ -60,17 +60,17 @@ fn stats_different_prunings() { epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); - assert_eq!(ensemble.stator.states.len(), 17); - assert_eq!(ensemble.backrefs.len_keys(), 22); - assert_eq!(ensemble.backrefs.len_vals(), 6); + assert_eq!(ensemble.stator.states.len(), 11); + assert_eq!(ensemble.backrefs.len_keys(), 15); + assert_eq!(ensemble.backrefs.len_vals(), 4); }); epoch.lower_and_prune().unwrap(); epoch.verify_integrity().unwrap(); epoch.ensemble(|ensemble| { assert_eq!(ensemble.notary.rnodes().len(), 3); assert_eq!(ensemble.stator.states.len(), 0); - assert_eq!(ensemble.backrefs.len_keys(), 15); - assert_eq!(ensemble.backrefs.len_vals(), 6); + assert_eq!(ensemble.backrefs.len_keys(), 11); + assert_eq!(ensemble.backrefs.len_vals(), 4); }); epoch.optimize().unwrap(); epoch.verify_integrity().unwrap(); From 36fe34b820dc00a65ed3b501384e32c3b2253469 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 2 Jan 2024 22:27:17 -0600 Subject: [PATCH 061/119] Update meta.rs --- starlight/src/lower/meta.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 708d1cd8..addcd890 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -215,6 +215,31 @@ pub fn general_mux(inputs: &[Awi], inx: &Bits) -> Awi { concat(nzbw, out_signals) } +// uses dynamic LUTs under the hood +pub fn dynamic_to_static_get(bits: &Bits, inx: &Bits) -> inlawi_ty!(1) { + if bits.bw() == 1 { + return InlAwi::from(bits.to_bool()) + } + /*let signals = selector(inx, Some(bits.bw())); + let mut out = inlawi!(0); + for (i, signal) in signals.iter().enumerate() { + static_lut!(out; 1111_1000; signal, bits.get(i).unwrap(), out); + } + out*/ + let lut_w = NonZeroUsize::new(bits.bw().next_power_of_two()).unwrap(); + let inx_w = NonZeroUsize::new(lut_w.get().trailing_zeros() as usize).unwrap(); + let mut true_inx = Awi::zero(inx_w); + true_inx.field_width(&inx, inx_w.get()).unwrap(); + let base = if bits.bw() == lut_w.get() { + Awi::from(bits) + } else { + let unknowns = + Awi::opaque(NonZeroUsize::new(lut_w.get().checked_sub(bits.bw()).unwrap()).unwrap()); + concat(lut_w, smallvec![bits.state(), unknowns.state()]) + }; + InlAwi::new(Op::Lut([base.state(), true_inx.state()])) +} + /// Trailing smear, given the value of `inx` it will set all bits in the vector /// up to but not including the one indexed by `inx`. This means that /// `inx.to_usize() == 0` sets no bits, and `inx.to_usize() == num_bits` sets @@ -324,18 +349,6 @@ pub fn dynamic_to_static_lut(out: &mut Bits, table: &Bits, inx: &Bits) { concat_update(out, nzbw, tmp_output) } -pub fn dynamic_to_static_get(bits: &Bits, inx: &Bits) -> inlawi_ty!(1) { - if bits.bw() == 1 { - return InlAwi::from(bits.to_bool()) - } - let signals = selector(inx, Some(bits.bw())); - let mut out = inlawi!(0); - for (i, signal) in signals.iter().enumerate() { - static_lut!(out; 1111_1000; signal, bits.get(i).unwrap(), out); - } - out -} - pub fn dynamic_to_static_set(bits: &Bits, inx: &Bits, bit: &Bits) -> Awi { if bits.bw() == 1 { return Awi::from(bit) From f86dc306d1307c6c3fa98e105d29259f9c1ff8da Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 17:26:48 -0600 Subject: [PATCH 062/119] working on more efficient shifting --- starlight/src/lower/lower_op.rs | 15 ++++++++++++- starlight/src/lower/meta.rs | 38 +++++++++++++++++++++++++++------ testcrate/tests/stats.rs | 10 ++++----- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 26f46004..8451b23a 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -140,6 +140,19 @@ pub fn lower_op( let lhs = Awi::opaque(lhs_w); let rhs = Awi::opaque(rhs_w); let width = Awi::opaque(width_w); + /* + let max = min(lhs_w, rhs_w).get(); + let success = Bits::efficient_ule(width.to_usize(), max); + let max_width_w = if max == 1 { + bw(1) + } else { + NonZeroUsize::new(max.next_power_of_two().trailing_zeros() as usize).unwrap() + }; + let width_small = Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()).unwrap(); + // to achieve a no-op we simply set the width to zero + let mut tmp_width = Awi::zero(max_width_w); + tmp_width.mux_(&width_small, success.is_some()).unwrap(); + */ let fail = width.ugt(&InlAwi::from_usize(lhs_w.get())).unwrap() | width.ugt(&InlAwi::from_usize(rhs_w.get())).unwrap(); let mut tmp_width = width.clone(); @@ -151,7 +164,7 @@ pub fn lower_op( Funnel([x, s]) => { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = funnel_(&x, &s); + let out = funnel(&x, &s); m.graft(&[out.state(), x.state(), s.state()]); } FieldFrom([lhs, rhs, from, width]) => { diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index addcd890..a14da253 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -478,11 +478,13 @@ pub fn field_width(lhs: &Bits, rhs: &Bits, width: &Bits) -> Awi { } } +// old static strategy if we need it + /// Given the diagonal control lines and input of a crossbar with output width /// s.t. `input.bw() + out.bw() - 1 = signals.bw()`, returns the output. The -/// `i`th input bit and `j`th output bit are controlled by the `out.bw() -/// - 1 + i - j`th control line. `signal_range` uses a virtual `..` range of the -/// possible signals. +/// `i`th input bit and `j`th output bit are controlled by the +/// `out.bw() - 1 + i - j`th control line. +/// `signal_range` uses a virtual `..` range of the possible signals. pub fn crossbar( output: &mut Bits, input: &Bits, @@ -512,7 +514,7 @@ pub fn crossbar( concat_update(output, nzbw, tmp_output) } -pub fn funnel_(x: &Bits, s: &Bits) -> Awi { +/*pub fn funnel(x: &Bits, s: &Bits) -> Awi { debug_assert_eq!(x.bw() & 1, 0); debug_assert_eq!(x.bw() / 2, 1 << s.bw()); let mut out = Awi::zero(NonZeroUsize::new(x.bw() / 2).unwrap()); @@ -522,13 +524,25 @@ pub fn funnel_(x: &Bits, s: &Bits) -> Awi { let range = (out.bw() - 1, out.bw() - 1 + out.bw()); crossbar(&mut out, x, &signals, range); out +}*/ + +pub fn funnel(x: &Bits, s: &Bits) -> Awi { + debug_assert!((s.bw() < (USIZE_BITS - 1)) && ((2usize << s.bw()) == x.bw())); + let out_w = NonZeroUsize::new(1 << s.bw()).unwrap(); + let mut output = SmallVec::with_capacity(out_w.get()); + for j in 0..out_w.get() { + let lut = Awi::new( + out_w, + Op::ConcatFields(ConcatFieldsType::from_iter([(x.state(), j, out_w)])), + ); + output.push(Awi::new(bw(1), Op::Lut([lut.state(), s.state()])).state()); + } + concat(out_w, output) } /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { - debug_assert_eq!(from.bw(), USIZE_BITS); - debug_assert_eq!(width.bw(), USIZE_BITS); let mut out = Awi::from_bits(lhs); // the `width == 0` case will result in a no-op from the later `field_width` // part, so we need to be able to handle just `rhs.bw()` possible shifts for @@ -539,10 +553,20 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { let range = (rhs.bw() - 1, 2 * rhs.bw() - 1); let mut tmp = Awi::zero(rhs.nzbw()); crossbar(&mut tmp, rhs, &signals, range); - out.field_width(&tmp, width.to_usize()).unwrap(); + + /*let s_w = rhs.bw().next_power_of_two().trailing_zeros() as usize; + let mut s = Awi::zero(NonZeroUsize::new(s_w).unwrap()); + s.resize_(&from, false); + let mut x = Awi::opaque(NonZeroUsize::new(2 << s_w).unwrap()); + // this is done on purpose so there are opaque bits + x.field_width(&rhs, rhs.bw()).unwrap_at_runtime(); + let tmp = funnel(&x, &s);*/ + let _ = out.field_width(&tmp, width.to_usize()); out } +// FIXME use `unwrap_at_runtime` or dropping `let _ =` in more places + pub fn shl(x: &Bits, s: &Bits) -> Awi { debug_assert_eq!(s.bw(), USIZE_BITS); let mut signals = selector(s, Some(x.bw())); diff --git a/testcrate/tests/stats.rs b/testcrate/tests/stats.rs index bc7e9eda..c4326c60 100644 --- a/testcrate/tests/stats.rs +++ b/testcrate/tests/stats.rs @@ -15,16 +15,16 @@ fn stats_optimize_funnel() { epoch.lower().unwrap(); epoch.assert_assertions(true).unwrap(); epoch.ensemble(|ensemble| { - awi::assert_eq!(ensemble.stator.states.len(), 2356); - awi::assert_eq!(ensemble.backrefs.len_keys(), 8303); - awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); + awi::assert_eq!(ensemble.stator.states.len(), 68); + awi::assert_eq!(ensemble.backrefs.len_keys(), 2607); + awi::assert_eq!(ensemble.backrefs.len_vals(), 101); }); epoch.optimize().unwrap(); epoch.assert_assertions(true).unwrap(); epoch.ensemble(|ensemble| { awi::assert_eq!(ensemble.stator.states.len(), 0); - awi::assert_eq!(ensemble.backrefs.len_keys(), 5818); - awi::assert_eq!(ensemble.backrefs.len_vals(), 1237); + awi::assert_eq!(ensemble.backrefs.len_keys(), 1418); + awi::assert_eq!(ensemble.backrefs.len_vals(), 101); }); } From 5515c06447826f478c430e9060929c744f34f7ec Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 19:13:36 -0600 Subject: [PATCH 063/119] Update temporal.rs --- starlight/src/awi_structs/temporal.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index a70a6872..3b4e4e89 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, num::NonZeroUsize, ops::Deref}; use awint::{ awint_dag::{smallvec::smallvec, Lineage, Op}, - dag::{self, awi, Awi, Bits, InlAwi}, + dag::{self, awi, Awi, Bits}, }; use crate::{epoch::get_current_epoch, lower::meta::general_mux}; @@ -228,16 +228,10 @@ impl Net { return dag::Option::some_at_dagtime((), inx.is_zero()); } let max_inx = self.len() - 1; - let max_inx_bits = self.len().next_power_of_two().trailing_zeros() as usize; - // we detect overflow by seeing if any of these bits are nonzero or if the rest - // of the index is greater than the expected max bits (only needed if the - // self.len() is not a power of two) - let should_stay_zero = if max_inx_bits < inx.bw() { - awi!(inx[max_inx_bits..]).unwrap() - } else { - awi!(0) - }; - let mut in_range = should_stay_zero.is_zero(); + let max_inx_bits = usize::try_from(max_inx.next_power_of_two().trailing_zeros()) + .unwrap() + .checked_add(if max_inx.is_power_of_two() { 1 } else { 0 }) + .unwrap(); let inx = if max_inx_bits < inx.bw() { awi!(inx[..max_inx_bits]).unwrap() } else if max_inx_bits > inx.bw() { @@ -245,16 +239,9 @@ impl Net { } else { Awi::from(inx) }; - if (!self.len().is_power_of_two()) && (inx.bw() >= max_inx_bits) { - // dance to avoid stuff that can get lowered into a full `BITS` sized comparison - let mut max = Awi::zero(inx.nzbw()); - max.usize_(max_inx); - let le = inx.ule(&max).unwrap(); - in_range &= le; - } let tmp = general_mux(&self.ports, &inx); self.source.drive(&tmp).unwrap(); - dag::Option::some_at_dagtime((), in_range) + dag::Bits::efficient_ule(inx.to_usize(), max_inx) } // TODO we can do this From 0d493c524ec8968e78b6a0459d4352940609b489 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 20:09:28 -0600 Subject: [PATCH 064/119] fixes --- starlight/src/awi_structs/epoch.rs | 14 +++++++------- starlight/src/awi_structs/temporal.rs | 4 ++-- starlight/src/lower/meta.rs | 2 +- testcrate/tests/epoch.rs | 12 ++++++++++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 164237dc..89efed56 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -548,7 +548,7 @@ pub fn _callback() -> EpochCallback { #[derive(Debug)] struct EpochInnerDrop { epoch_shared: EpochShared, - is_current: bool, + is_suspended: bool, } impl Drop for EpochInnerDrop { @@ -559,7 +559,7 @@ impl Drop for EpochInnerDrop { if let Err(e) = self.epoch_shared.drop_associated() { panic!("{e}"); } - if self.is_current { + if !self.is_suspended { if let Err(e) = self.epoch_shared.remove_as_current() { panic!("panicked upon dropping an `Epoch`: {e}"); } @@ -680,7 +680,7 @@ impl SuspendedEpoch { /// Resumes the `Epoch` as current pub fn resume(mut self) -> Epoch { self.inner.epoch_shared.set_as_current(); - self.inner.is_current = true; + self.inner.is_suspended = false; Epoch { inner: self.inner } } @@ -703,7 +703,7 @@ impl Epoch { Self { inner: EpochInnerDrop { epoch_shared: new, - is_current: true, + is_suspended: false, }, } } @@ -719,7 +719,7 @@ impl Epoch { Self { inner: EpochInnerDrop { epoch_shared: shared, - is_current: true, + is_suspended: false, }, } } @@ -734,7 +734,7 @@ impl Epoch { fn check_current(&self) -> Result { let epoch_shared = get_current_epoch().unwrap(); if Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared().epoch_data) { - Ok(epoch_shared) + Ok(self.shared().clone()) } else { Err(EvalError::OtherStr("epoch is not the current epoch")) } @@ -747,7 +747,7 @@ impl Epoch { // there should be a variant that returns the `Epoch` to prevent it from being // dropped and causing another error self.inner.epoch_shared.remove_as_current().unwrap(); - self.inner.is_current = false; + self.inner.is_suspended = true; Ok(SuspendedEpoch { inner: self.inner }) } diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index 3b4e4e89..91a27286 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -232,14 +232,14 @@ impl Net { .unwrap() .checked_add(if max_inx.is_power_of_two() { 1 } else { 0 }) .unwrap(); - let inx = if max_inx_bits < inx.bw() { + let small_inx = if max_inx_bits < inx.bw() { awi!(inx[..max_inx_bits]).unwrap() } else if max_inx_bits > inx.bw() { awi!(zero: .., inx; ..max_inx_bits).unwrap() } else { Awi::from(inx) }; - let tmp = general_mux(&self.ports, &inx); + let tmp = general_mux(&self.ports, &small_inx); self.source.drive(&tmp).unwrap(); dag::Bits::efficient_ule(inx.to_usize(), max_inx) } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index a14da253..4dc38702 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -229,7 +229,7 @@ pub fn dynamic_to_static_get(bits: &Bits, inx: &Bits) -> inlawi_ty!(1) { let lut_w = NonZeroUsize::new(bits.bw().next_power_of_two()).unwrap(); let inx_w = NonZeroUsize::new(lut_w.get().trailing_zeros() as usize).unwrap(); let mut true_inx = Awi::zero(inx_w); - true_inx.field_width(&inx, inx_w.get()).unwrap(); + true_inx.field_width(inx, inx_w.get()).unwrap(); let base = if bits.bw() == lut_w.get() { Awi::from(bits) } else { diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index c06f4cfd..101d359f 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -86,6 +86,18 @@ fn epoch_shared0() { drop(epoch1); } +#[test] +fn epoch_shared1() { + let epoch0 = Epoch::new(); + let epoch1 = Epoch::shared_with(&epoch0); + let (lazy1, eval1) = ex(); + drop(epoch0); + epoch1.optimize().unwrap(); + drop(lazy1); + drop(eval1); + drop(epoch1); +} + #[test] fn epoch_suspension0() { let epoch0 = Epoch::new(); From 2d238e2d3ea78ca96ea301dbd512f3fe8e037d52 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 20:44:34 -0600 Subject: [PATCH 065/119] fixes --- starlight/src/lower/lower_op.rs | 12 +++++----- testcrate/tests/epoch.rs | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 8451b23a..619564de 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -593,13 +593,13 @@ pub fn lower_op( } op @ (IsZero(_) | IsUmax(_) | IsImax(_) | IsImin(_) | IsUone(_)) => { let x = Awi::opaque(m.get_nzbw(op.operands()[0])); - let w = x.bw(); + let w = x.nzbw(); let out = InlAwi::from(match op { - IsZero(_) => x.const_eq(&awi!(zero: ..w).unwrap()).unwrap(), - IsUmax(_) => x.const_eq(&awi!(umax: ..w).unwrap()).unwrap(), - IsImax(_) => x.const_eq(&awi!(imax: ..w).unwrap()).unwrap(), - IsImin(_) => x.const_eq(&awi!(imin: ..w).unwrap()).unwrap(), - IsUone(_) => x.const_eq(&awi!(uone: ..w).unwrap()).unwrap(), + IsZero(_) => x.const_eq(&Awi::zero(w)).unwrap(), + IsUmax(_) => x.const_eq(&Awi::umax(w)).unwrap(), + IsImax(_) => x.const_eq(&Awi::imax(w)).unwrap(), + IsImin(_) => x.const_eq(&Awi::imin(w)).unwrap(), + IsUone(_) => x.const_eq(&Awi::uone(w)).unwrap(), _ => unreachable!(), }); m.graft(&[out.state(), x.state()]); diff --git a/testcrate/tests/epoch.rs b/testcrate/tests/epoch.rs index 101d359f..5ebec4cc 100644 --- a/testcrate/tests/epoch.rs +++ b/testcrate/tests/epoch.rs @@ -69,6 +69,41 @@ fn epoch_nested_fail() { #[test] fn epoch_shared0() { + // checking assertions + let epoch0 = Epoch::new(); + let (lazy0, eval0) = ex(); + let epoch1 = Epoch::shared_with(&epoch0); + awi::assert_eq!( + epoch0.ensemble(|ensemble| ensemble.notary.rnodes().len()), + 3 + ); + awi::assert_eq!( + epoch1.ensemble(|ensemble| ensemble.notary.rnodes().len()), + 3 + ); + awi::assert_eq!(epoch0.assertions().bits.len(), 1); + awi::assert!(epoch1.assertions().bits.is_empty()); + drop(lazy0); + drop(eval0); + awi::assert_eq!( + epoch0.ensemble(|ensemble| ensemble.notary.rnodes().len()), + 1 + ); + awi::assert_eq!( + epoch1.ensemble(|ensemble| ensemble.notary.rnodes().len()), + 1 + ); + awi::assert_eq!(epoch0.assertions().bits.len(), 1); + drop(epoch0); + awi::assert!(epoch1.assertions().bits.is_empty()); + awi::assert!(epoch1.ensemble(|ensemble| ensemble.notary.rnodes().is_empty())); + epoch1.prune_unused_states().unwrap(); + awi::assert!(epoch1.ensemble(|ensemble| ensemble.stator.states.is_empty())); + drop(epoch1); +} + +#[test] +fn epoch_shared1() { let epoch0 = Epoch::new(); let (lazy0, eval0) = ex(); let epoch1 = Epoch::shared_with(&epoch0); @@ -81,13 +116,16 @@ fn epoch_shared0() { drop(lazy0); drop(eval0); drop(epoch0); + epoch1.assert_assertions(true).unwrap(); epoch1.prune_unused_states().unwrap(); + awi::assert!(epoch1.ensemble(|ensemble| ensemble.notary.rnodes().is_empty())); + awi::assert!(epoch1.assertions().bits.is_empty()); awi::assert!(epoch1.ensemble(|ensemble| ensemble.stator.states.is_empty())); drop(epoch1); } #[test] -fn epoch_shared1() { +fn epoch_shared2() { let epoch0 = Epoch::new(); let epoch1 = Epoch::shared_with(&epoch0); let (lazy1, eval1) = ex(); From 415a35eb49dbe96e2ba32b2de255d8a6a131e2ae Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 21:04:05 -0600 Subject: [PATCH 066/119] Update temporal.rs --- starlight/src/awi_structs/temporal.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index 91a27286..69e60ffa 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, num::NonZeroUsize, ops::Deref}; use awint::{ awint_dag::{smallvec::smallvec, Lineage, Op}, - dag::{self, awi, Awi, Bits}, + dag::{self, awi, Awi, Bits, InlAwi}, }; use crate::{epoch::get_current_epoch, lower::meta::general_mux}; @@ -232,6 +232,21 @@ impl Net { .unwrap() .checked_add(if max_inx.is_power_of_two() { 1 } else { 0 }) .unwrap(); + + let should_stay_zero = if max_inx_bits < inx.bw() { + awi!(inx[max_inx_bits..]).unwrap() + } else { + awi!(0) + }; + let mut in_range = should_stay_zero.is_zero(); + if (!self.len().is_power_of_two()) && (inx.bw() >= max_inx_bits) { + // dance to avoid stuff that can get lowered into a full `BITS` sized comparison + let mut max = Awi::zero(inx.nzbw()); + max.usize_(max_inx); + let le = inx.ule(&max).unwrap(); + in_range &= le; + } + let small_inx = if max_inx_bits < inx.bw() { awi!(inx[..max_inx_bits]).unwrap() } else if max_inx_bits > inx.bw() { @@ -241,7 +256,8 @@ impl Net { }; let tmp = general_mux(&self.ports, &small_inx); self.source.drive(&tmp).unwrap(); - dag::Bits::efficient_ule(inx.to_usize(), max_inx) + + dag::Option::some_at_dagtime((), in_range) } // TODO we can do this From a0d179fe682db60ee5b29e404585bbfba3346880 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 21:05:38 -0600 Subject: [PATCH 067/119] Update temporal.rs --- starlight/src/awi_structs/temporal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index 69e60ffa..cc01cec9 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -233,6 +233,8 @@ impl Net { .checked_add(if max_inx.is_power_of_two() { 1 } else { 0 }) .unwrap(); + // use this instead of `efficient_ule` because here we can avoid many cases if + // `max_inx_bits >= inx.bw()` let should_stay_zero = if max_inx_bits < inx.bw() { awi!(inx[max_inx_bits..]).unwrap() } else { From 7cf4a356bb68fcc418ebf512a1c41cfbb66a2cd6 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 3 Jan 2024 23:27:53 -0600 Subject: [PATCH 068/119] Update lower_op.rs --- starlight/src/lower/lower_op.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 619564de..69f16d36 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -140,23 +140,21 @@ pub fn lower_op( let lhs = Awi::opaque(lhs_w); let rhs = Awi::opaque(rhs_w); let width = Awi::opaque(width_w); - /* let max = min(lhs_w, rhs_w).get(); let success = Bits::efficient_ule(width.to_usize(), max); - let max_width_w = if max == 1 { - bw(1) - } else { - NonZeroUsize::new(max.next_power_of_two().trailing_zeros() as usize).unwrap() - }; - let width_small = Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()).unwrap(); + let max_width_w = NonZeroUsize::new( + usize::try_from(max.next_power_of_two().trailing_zeros()) + .unwrap() + .checked_add(if max.is_power_of_two() { 1 } else { 0 }) + .unwrap(), + ) + .unwrap(); + let width_small = + Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) + .unwrap(); // to achieve a no-op we simply set the width to zero let mut tmp_width = Awi::zero(max_width_w); tmp_width.mux_(&width_small, success.is_some()).unwrap(); - */ - let fail = width.ugt(&InlAwi::from_usize(lhs_w.get())).unwrap() - | width.ugt(&InlAwi::from_usize(rhs_w.get())).unwrap(); - let mut tmp_width = width.clone(); - tmp_width.mux_(&InlAwi::from_usize(0), fail).unwrap(); let out = field_width(&lhs, &rhs, &tmp_width); m.graft(&[out.state(), lhs.state(), rhs.state(), width.state()]); } From 53e2e76ffeafacfd97be37ffb024c565a2254237 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 00:14:10 -0600 Subject: [PATCH 069/119] updates --- starlight/src/awi_structs/temporal.rs | 5 +---- starlight/src/lower/lower_op.rs | 27 +++++++++++++-------------- starlight/src/lower/meta.rs | 15 +++++++++------ 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/starlight/src/awi_structs/temporal.rs b/starlight/src/awi_structs/temporal.rs index cc01cec9..8a7e165b 100644 --- a/starlight/src/awi_structs/temporal.rs +++ b/starlight/src/awi_structs/temporal.rs @@ -228,10 +228,7 @@ impl Net { return dag::Option::some_at_dagtime((), inx.is_zero()); } let max_inx = self.len() - 1; - let max_inx_bits = usize::try_from(max_inx.next_power_of_two().trailing_zeros()) - .unwrap() - .checked_add(if max_inx.is_power_of_two() { 1 } else { 0 }) - .unwrap(); + let max_inx_bits = Bits::nontrivial_bits(max_inx).unwrap().get(); // use this instead of `efficient_ule` because here we can avoid many cases if // `max_inx_bits >= inx.bw()` diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 69f16d36..0798f1bd 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -34,6 +34,7 @@ pub fn lower_op( out_w: NonZeroUsize, mut m: impl LowerManagement

, ) -> Result { + //dbg!(&start_op, out_w); match start_op { Invalid => return Err(EvalError::OtherStr("encountered `Invalid` in lowering")), Opaque(..) | Literal(_) | Assert(_) | Copy(_) | StaticGet(..) | Concat(_) @@ -142,13 +143,7 @@ pub fn lower_op( let width = Awi::opaque(width_w); let max = min(lhs_w, rhs_w).get(); let success = Bits::efficient_ule(width.to_usize(), max); - let max_width_w = NonZeroUsize::new( - usize::try_from(max.next_power_of_two().trailing_zeros()) - .unwrap() - .checked_add(if max.is_power_of_two() { 1 } else { 0 }) - .unwrap(), - ) - .unwrap(); + let max_width_w = Bits::nontrivial_bits(max).unwrap(); let width_small = Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) .unwrap(); @@ -186,7 +181,7 @@ pub fn lower_op( if o { out } else { - out.field_width(&tmp1, width.to_usize()).unwrap(); + let _ = out.field_width(&tmp1, width.to_usize()); out } } else { @@ -205,12 +200,16 @@ pub fn lower_op( let rhs = Awi::opaque(rhs_w); let from = Awi::opaque(m.get_nzbw(from)); let width = Awi::opaque(width_w); - let mut tmp = InlAwi::from_usize(rhs_w.get()); - tmp.sub_(&width).unwrap(); - // the other two fail conditions are in `field_width` - let fail = from.ugt(&tmp).unwrap(); - let mut tmp_width = width.clone(); - tmp_width.mux_(&InlAwi::from_usize(0), fail).unwrap(); + + let success = + Bits::efficient_add_then_ule(from.to_usize(), width.to_usize(), rhs.bw()); + let max_width_w = Bits::nontrivial_bits(rhs.bw()).unwrap(); + let width_small = + Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) + .unwrap(); + // to achieve a no-op we simply set the width to zero + let mut tmp_width = Awi::zero(max_width_w); + tmp_width.mux_(&width_small, success.is_some()).unwrap(); // the optimizations on `width` are done later on an inner `field_width` call let out = field_from(&lhs, &rhs, &from, &tmp_width); m.graft(&[ diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 4dc38702..322396ff 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -549,18 +549,21 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { // `width == 1` cases. There are `rhs.bw()` output bars needed. `from == 0` // should connect the zeroeth crossbars, so the offset is `rhs.bw() - 1 + 0 - // 0`. `j` stays zero and we have `0 <= i < rhs.bw()` - let signals = selector(from, Some(rhs.bw())); + /*let signals = selector(from, Some(rhs.bw())); let range = (rhs.bw() - 1, 2 * rhs.bw() - 1); let mut tmp = Awi::zero(rhs.nzbw()); - crossbar(&mut tmp, rhs, &signals, range); + crossbar(&mut tmp, rhs, &signals, range);*/ - /*let s_w = rhs.bw().next_power_of_two().trailing_zeros() as usize; + let s_w = rhs.bw().next_power_of_two().trailing_zeros() as usize; let mut s = Awi::zero(NonZeroUsize::new(s_w).unwrap()); s.resize_(&from, false); - let mut x = Awi::opaque(NonZeroUsize::new(2 << s_w).unwrap()); + // TODO make opaque + let mut x = Awi::zero(NonZeroUsize::new(2 << s_w).unwrap()); // this is done on purpose so there are opaque bits - x.field_width(&rhs, rhs.bw()).unwrap_at_runtime(); - let tmp = funnel(&x, &s);*/ + //let _ = x.field_width(&rhs, rhs.bw()); + x.resize_(rhs, false); + let tmp = funnel(&x, &s); + let _ = out.field_width(&tmp, width.to_usize()); out } From 672782c571ca25ae9aa35130d96432ae037f93b7 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 00:23:23 -0600 Subject: [PATCH 070/119] misc improvements --- starlight/src/lower/lower_op.rs | 7 +++---- starlight/src/lower/meta.rs | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 0798f1bd..98543fe5 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -438,7 +438,7 @@ pub fn lower_op( lhs.clone() } else if let Some(w) = NonZeroUsize::new(lhs.bw() - to_u) { let (mut lhs_hi, o) = static_field(&Awi::zero(w), 0, &lhs, to_u, w.get()); - lhs_hi.field_width(&rhs, width.to_usize()).unwrap(); + let _ = lhs_hi.field_width(&rhs, width.to_usize()); if o { lhs.clone() } else { @@ -475,10 +475,9 @@ pub fn lower_op( let from = Awi::opaque(m.get_nzbw(from)); let min_w = min(lhs.bw(), rhs.bw()); let mut tmp = Awi::zero(NonZeroUsize::new(min_w).unwrap()); - tmp.field_from(&rhs, from.to_usize(), width.to_usize()) - .unwrap(); + let _ = tmp.field_from(&rhs, from.to_usize(), width.to_usize()); let mut out = lhs.clone(); - out.field_to(to.to_usize(), &tmp, width.to_usize()).unwrap(); + let _ = out.field_to(to.to_usize(), &tmp, width.to_usize()); m.graft(&[ out.state(), diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 322396ff..69c418ed 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -554,11 +554,12 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { let mut tmp = Awi::zero(rhs.nzbw()); crossbar(&mut tmp, rhs, &signals, range);*/ - let s_w = rhs.bw().next_power_of_two().trailing_zeros() as usize; - let mut s = Awi::zero(NonZeroUsize::new(s_w).unwrap()); - s.resize_(&from, false); + // FIXME this can go lower + let s_w = Bits::nontrivial_bits(rhs.bw()).unwrap(); + let mut s = Awi::zero(s_w); + s.resize_(from, false); // TODO make opaque - let mut x = Awi::zero(NonZeroUsize::new(2 << s_w).unwrap()); + let mut x = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); // this is done on purpose so there are opaque bits //let _ = x.field_width(&rhs, rhs.bw()); x.resize_(rhs, false); @@ -1157,10 +1158,10 @@ pub fn division(duo: &Bits, div: &Bits) -> (Awi, Awi) { let original_w = duo.nzbw(); let w = NonZeroUsize::new(original_w.get() + 1).unwrap(); let mut tmp = Awi::zero(w); - tmp.zero_resize_(duo); + tmp.resize_(duo, false); let duo = tmp; let mut tmp = Awi::zero(w); - tmp.zero_resize_(div); + tmp.resize_(div, false); let div = tmp; let div_original = div.clone(); @@ -1246,7 +1247,7 @@ pub fn division(duo: &Bits, div: &Bits) -> (Awi, Awi) { // 1 << shl efficiently let tmp = selector_awi(&shl, Some(w.get())); let mut quo = Awi::zero(w); - quo.zero_resize_(&tmp); + quo.resize_(&tmp, false); // if duo < div_original let b = duo.ult(&div_original).unwrap(); @@ -1290,7 +1291,7 @@ pub fn division(duo: &Bits, div: &Bits) -> (Awi, Awi) { let mut tmp0 = Awi::zero(original_w); let mut tmp1 = Awi::zero(original_w); - tmp0.zero_resize_(&short_quo); - tmp1.zero_resize_(&short_rem); + tmp0.resize_(&short_quo, false); + tmp1.resize_(&short_rem, false); (tmp0, tmp1) } From 8ef318fe355bbf5f6d8b047b3717c47d935635f6 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 12:14:08 -0600 Subject: [PATCH 071/119] Update meta.rs --- starlight/src/lower/meta.rs | 45 +++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 69c418ed..07b51f96 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -540,32 +540,29 @@ pub fn funnel(x: &Bits, s: &Bits) -> Awi { concat(out_w, output) } -/// Setting `width` to 0 guarantees that nothing happens even with other -/// arguments being invalid +/// Assumes that `from` and `width` is in range, however setting `width` to 0 +/// guarantees that nothing happens to `lhs` even with `from` being out of range pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { let mut out = Awi::from_bits(lhs); - // the `width == 0` case will result in a no-op from the later `field_width` - // part, so we need to be able to handle just `rhs.bw()` possible shifts for - // `width == 1` cases. There are `rhs.bw()` output bars needed. `from == 0` - // should connect the zeroeth crossbars, so the offset is `rhs.bw() - 1 + 0 - - // 0`. `j` stays zero and we have `0 <= i < rhs.bw()` - /*let signals = selector(from, Some(rhs.bw())); - let range = (rhs.bw() - 1, 2 * rhs.bw() - 1); - let mut tmp = Awi::zero(rhs.nzbw()); - crossbar(&mut tmp, rhs, &signals, range);*/ - - // FIXME this can go lower - let s_w = Bits::nontrivial_bits(rhs.bw()).unwrap(); - let mut s = Awi::zero(s_w); - s.resize_(from, false); - // TODO make opaque - let mut x = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); - // this is done on purpose so there are opaque bits - //let _ = x.field_width(&rhs, rhs.bw()); - x.resize_(rhs, false); - let tmp = funnel(&x, &s); - - let _ = out.field_width(&tmp, width.to_usize()); + // the max shift value that can be anything but a no-op + if let Some(s_w) = Bits::nontrivial_bits(rhs.bw() - 1) { + let mut s = Awi::zero(s_w); + s.resize_(from, false); + // TODO make opaque + let mut x = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); + // this is done on purpose so there are opaque bits + //let _ = x.field_width(&rhs, rhs.bw()); + x.resize_(rhs, false); + let tmp = funnel(&x, &s); + + let max_width = min(lhs.bw(), rhs.bw()); + let mut small_width = Awi::zero(Bits::nontrivial_bits(max_width).unwrap()); + small_width.resize_(width, false); + let _ = out.field_width(&tmp, small_width.to_usize()); + } else { + let small_width = Awi::from_bool(width.lsb()); + let _ = out.field_width(&rhs, small_width.to_usize()); + } out } From 55b0638f905cc094f27021b12c1f625551158ae5 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 14:59:46 -0600 Subject: [PATCH 072/119] fix bug in evaluation --- starlight/src/ensemble/value.rs | 52 +++++++++++++++------------------ starlight/src/lower/lower_op.rs | 1 - starlight/src/lower/meta.rs | 8 ++--- testcrate/tests/basic.rs | 18 ++++++++++++ 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 43abc8e1..6e7ef68c 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -323,32 +323,24 @@ impl Ensemble { } } let mut lut = original_lut.clone(); - // note: we do this in this order, it turns out that doing independence - // reduction instead of constant reduction first will not prevent optimizations, - // also we don't have to remove bits from `fixed` and `unknown` - // if fixed and unknown bits can influence the value, - // then the value of this equivalence can also be fixed - // to unknown - for i in 0..len { - if fixed.get(i).unwrap() - && unknown.get(i).unwrap() - && (!LNode::reduce_independent_lut(&mut lut.clone(), i)) - { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Unknown, - })); - return vec![]; - } - } + // In an earlier version there was a test where if fixed and unknown bits + // influence the value, then the equiv can be fixed to unknown. However, + // consider a lookup table 0100 and an inputs (fixed unknown, 0), where the 0 + // may be a later fixed value. It will see that the output value is dependent on + // the fixed unknown, but misses that the 0 reduces the LUT to 00 which is + // independent of the fixed value. We have to wait for all bits to be fixed, + // reduce the LUT on known bits, then only change to unknown if the + // `lut.is_zero()` and `lut.is_umax` checks fail. The only tables where you + // could safely set to unknown earlier are unoptimized. + // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { LNode::reduce_lut(&mut lut, i, inp_val.get(i).unwrap()); } } + // if the LUT is all ones or all zeros, we can know that any unfixed or // unknown changes will be unable to affect the // output @@ -367,17 +359,19 @@ impl Ensemble { })); return vec![]; } + if fixed.is_umax() { + // there are fixed unknown bits influencing the value + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; + } // TODO prioritize bits that could lead to number_a optimization - /*let mut skip = 0; - for i in 0..len { - if fixed.get(i).unwrap() && !unknown.get(i).unwrap() { - skip += 1; - } else if unknown.get(i).unwrap() { - // assume unchanging - lut = LNode::reduce_lut(&lut, i, inp.get(i).unwrap()); - // - } else {} - }*/ + + // note: we can do this because `fixed` and `unknown` were not reduced along + // with the test `lut` for i in (0..inp.len()).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { res.push(RequestLNode { diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 98543fe5..315d4a0e 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -34,7 +34,6 @@ pub fn lower_op( out_w: NonZeroUsize, mut m: impl LowerManagement

, ) -> Result { - //dbg!(&start_op, out_w); match start_op { Invalid => return Err(EvalError::OtherStr("encountered `Invalid` in lowering")), Opaque(..) | Literal(_) | Assert(_) | Copy(_) | StaticGet(..) | Concat(_) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 07b51f96..73e9ca8c 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -548,10 +548,10 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { if let Some(s_w) = Bits::nontrivial_bits(rhs.bw() - 1) { let mut s = Awi::zero(s_w); s.resize_(from, false); - // TODO make opaque - let mut x = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); + let mut x = Awi::opaque(NonZeroUsize::new(2 << s_w.get()).unwrap()); // this is done on purpose so there are opaque bits - //let _ = x.field_width(&rhs, rhs.bw()); + let w = rhs.bw(); + let _ = x.field_width(rhs, w); x.resize_(rhs, false); let tmp = funnel(&x, &s); @@ -561,7 +561,7 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { let _ = out.field_width(&tmp, small_width.to_usize()); } else { let small_width = Awi::from_bool(width.lsb()); - let _ = out.field_width(&rhs, small_width.to_usize()); + let _ = out.field_width(rhs, small_width.to_usize()); } out } diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index b0a41eee..793a2efb 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -78,6 +78,24 @@ fn multiplier() { drop(epoch); } +#[test] +fn unknown_masking() { + use dag::*; + let epoch = Epoch::new(); + let x = awi!(opaque: ..3, 1); + let mut out = awi!(0u3); + let width = LazyAwi::uone(bw(2)); + out.field_width(&x, width.to_usize()).unwrap(); + let eval = EvalAwi::from(&out); + { + use awi::*; + awi::assert_eq!(eval.eval().unwrap(), awi!(1u3)); + epoch.optimize().unwrap(); + awi::assert_eq!(eval.eval().unwrap(), awi!(1u3)); + } + drop(epoch); +} + #[test] fn all_variations() { let epoch = Epoch::new(); From 9e721c515b3facfd352c418d7a39d55d162c3725 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 15:59:45 -0600 Subject: [PATCH 073/119] improve shl_ --- starlight/src/lower/lower_op.rs | 10 +++++++++- starlight/src/lower/meta.rs | 26 +++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 315d4a0e..acd931f8 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -234,7 +234,15 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = shl(&x, &s); + + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); + let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); + let s_small = + Bits::static_field(&Awi::zero(max_s_w), 0, &s, 0, max_s_w.get()).unwrap(); + // to achieve a no-op we simply set the shift to zero + let mut tmp_s = Awi::zero(max_s_w); + tmp_s.mux_(&s_small, success.is_some()).unwrap(); + let out = shl(&x, &tmp_s); m.graft(&[out.state(), x.state(), s.state()]); } } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 73e9ca8c..12451e73 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -552,7 +552,6 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { // this is done on purpose so there are opaque bits let w = rhs.bw(); let _ = x.field_width(rhs, w); - x.resize_(rhs, false); let tmp = funnel(&x, &s); let max_width = min(lhs.bw(), rhs.bw()); @@ -566,14 +565,27 @@ pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { out } -// FIXME use `unwrap_at_runtime` or dropping `let _ =` in more places - +/// Assumes that `s` is in range pub fn shl(x: &Bits, s: &Bits) -> Awi { - debug_assert_eq!(s.bw(), USIZE_BITS); - let mut signals = selector(s, Some(x.bw())); - signals.reverse(); let mut out = Awi::zero(x.nzbw()); - crossbar(&mut out, x, &signals, (0, x.bw())); + if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { + let mut small_s = Awi::zero(small_s_w); + small_s.resize_(s, false); + // FIXME + let mut wide_x = Awi::zero(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + let mut rev_x = Awi::zero(x.nzbw()); + rev_x.copy_(&x).unwrap(); + // we have two reversals so that the shift acts leftward + rev_x.rev_(); + let _ = wide_x.field_width(&rev_x, x.bw()); + let tmp = funnel(&wide_x, &small_s); + out.resize_(&tmp, false); + out.rev_(); + } else { + let small_width = Awi::from_bool(s.lsb()); + out.resize_(x, false); + let _ = out.field_width(x, small_width.to_usize()); + } out } From 5b23ce342a84da7184513e002c4566a6a06726db Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 16:36:03 -0600 Subject: [PATCH 074/119] Update meta.rs --- starlight/src/lower/meta.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 12451e73..44f82754 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -571,8 +571,9 @@ pub fn shl(x: &Bits, s: &Bits) -> Awi { if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { let mut small_s = Awi::zero(small_s_w); small_s.resize_(s, false); - // FIXME - let mut wide_x = Awi::zero(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + // need zeros for the bits that are shifted in + let _ = wide_x.field_to(x.bw(), &Awi::zero(x.nzbw()), x.bw()); let mut rev_x = Awi::zero(x.nzbw()); rev_x.copy_(&x).unwrap(); // we have two reversals so that the shift acts leftward From 84282befd885df1f415d42e93a999b330452dcf9 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 16:42:40 -0600 Subject: [PATCH 075/119] update lshr --- starlight/src/lower/lower_op.rs | 9 ++++++++- starlight/src/lower/meta.rs | 22 +++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index acd931f8..ca875847 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -260,7 +260,14 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = lshr(&x, &s); + + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); + let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); + let s_small = + Bits::static_field(&Awi::zero(max_s_w), 0, &s, 0, max_s_w.get()).unwrap(); + let mut tmp_s = Awi::zero(max_s_w); + tmp_s.mux_(&s_small, success.is_some()).unwrap(); + let out = lshr(&x, &tmp_s); m.graft(&[out.state(), x.state(), s.state()]); } } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 44f82754..7fd2ebfc 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -573,9 +573,9 @@ pub fn shl(x: &Bits, s: &Bits) -> Awi { small_s.resize_(s, false); let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); // need zeros for the bits that are shifted in - let _ = wide_x.field_to(x.bw(), &Awi::zero(x.nzbw()), x.bw()); + let _ = wide_x.field_to(x.bw(), &Awi::zero(x.nzbw()), x.bw() - 1); let mut rev_x = Awi::zero(x.nzbw()); - rev_x.copy_(&x).unwrap(); + rev_x.copy_(x).unwrap(); // we have two reversals so that the shift acts leftward rev_x.rev_(); let _ = wide_x.field_width(&rev_x, x.bw()); @@ -590,11 +590,23 @@ pub fn shl(x: &Bits, s: &Bits) -> Awi { out } +/// Assumes that `s` is in range pub fn lshr(x: &Bits, s: &Bits) -> Awi { - debug_assert_eq!(s.bw(), USIZE_BITS); - let signals = selector(s, Some(x.bw())); let mut out = Awi::zero(x.nzbw()); - crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); + if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { + let mut small_s = Awi::zero(small_s_w); + small_s.resize_(s, false); + let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + // need zeros for the bits that are shifted in + let _ = wide_x.field_to(x.bw(), &Awi::zero(x.nzbw()), x.bw() - 1); + let _ = wide_x.field_width(x, x.bw()); + let tmp = funnel(&wide_x, &small_s); + out.resize_(&tmp, false); + } else { + let small_width = Awi::from_bool(s.lsb()); + out.resize_(x, false); + let _ = out.field_width(x, small_width.to_usize()); + } out } From d5134b265c014ced0ce6538820d211f303bbfe92 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 17:01:18 -0600 Subject: [PATCH 076/119] update ashr --- starlight/src/lower/lower_op.rs | 8 ++++- starlight/src/lower/meta.rs | 52 ++++++++++++--------------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index ca875847..7491bb79 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -288,7 +288,13 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = ashr(&x, &s); + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); + let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); + let s_small = + Bits::static_field(&Awi::zero(max_s_w), 0, &s, 0, max_s_w.get()).unwrap(); + let mut tmp_s = Awi::zero(max_s_w); + tmp_s.mux_(&s_small, success.is_some()).unwrap(); + let out = ashr(&x, &tmp_s); m.graft(&[out.state(), x.state(), s.state()]); } } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 7fd2ebfc..e9aa94f1 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -610,43 +610,27 @@ pub fn lshr(x: &Bits, s: &Bits) -> Awi { out } +/// Assumes that `s` is in range pub fn ashr(x: &Bits, s: &Bits) -> Awi { - debug_assert_eq!(s.bw(), USIZE_BITS); - let signals = selector(s, Some(x.bw())); let mut out = Awi::zero(x.nzbw()); - crossbar(&mut out, x, &signals, (x.bw() - 1, 2 * x.bw() - 1)); - // Not sure if there is a better way to do this. If we try to use the crossbar - // signals in some way, we are guaranteed some kind of > O(1) time thing. - - let msb = x.msb(); - // get the `lb_num` that `tsmear_inx` uses, it can be `x.bw() - 1` because of - // the `s < x.bw()` requirement, this single bit of difference is important - // for powers of two because of the `lb_num += 1` condition it avoids. - let num = x.bw() - 1; - let next_pow = num.next_power_of_two(); - let mut lb_num = next_pow.trailing_zeros() as usize; - if next_pow == num { - // need extra bit to get all `n + 1` - lb_num += 1; - } - if let Some(w) = NonZeroUsize::new(lb_num) { - let mut gated_s = Awi::zero(w); - // `gated_s` will be zero if `x.msb()` is zero, in which case `tsmear_inx` - // produces all zeros to be ORed - for i in 0..gated_s.bw() { - let mut tmp1 = inlawi!(0); - static_lut!(tmp1; 1000; s.get(i).unwrap(), msb); - gated_s.set(i, tmp1.to_bool()).unwrap(); - } - let or_mask = tsmear_awi(&gated_s, num); - for i in 0..or_mask.bw() { - let out_i = out.bw() - 1 - i; - let mut tmp1 = inlawi!(0); - static_lut!(tmp1; 1110; out.get(out_i).unwrap(), or_mask.get(i).unwrap()); - out.set(out_i, tmp1.to_bool()).unwrap(); - } + if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { + let mut small_s = Awi::zero(small_s_w); + small_s.resize_(s, false); + let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + // extension for the bits that are shifted in + let _ = wide_x.field_to( + x.bw(), + &Awi::new(x.nzbw(), Op::Repeat([x.msb().state()])), + x.bw() - 1, + ); + let _ = wide_x.field_width(x, x.bw()); + let tmp = funnel(&wide_x, &small_s); + out.resize_(&tmp, false); + } else { + let small_width = Awi::from_bool(s.lsb()); + out.resize_(x, false); + let _ = out.field_width(x, small_width.to_usize()); } - out } From 228c7bc3a0a958f05cbee8343577349fcb7e47f7 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 17:20:36 -0600 Subject: [PATCH 077/119] implement remaining rotations --- starlight/src/lower/lower_op.rs | 19 +++++++++-- starlight/src/lower/meta.rs | 57 ++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 7491bb79..d8fb22cd 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -288,6 +288,7 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); let s_small = @@ -312,7 +313,14 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = rotl(&x, &s); + + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); + let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); + let s_small = + Bits::static_field(&Awi::zero(max_s_w), 0, &s, 0, max_s_w.get()).unwrap(); + let mut tmp_s = Awi::zero(max_s_w); + tmp_s.mux_(&s_small, success.is_some()).unwrap(); + let out = rotl(&x, &tmp_s); m.graft(&[out.state(), x.state(), s.state()]); } } @@ -330,7 +338,14 @@ pub fn lower_op( } else { let x = Awi::opaque(m.get_nzbw(x)); let s = Awi::opaque(m.get_nzbw(s)); - let out = rotr(&x, &s); + + let success = Bits::efficient_ule(s.to_usize(), x.bw() - 1); + let max_s_w = Bits::nontrivial_bits(x.bw() - 1).unwrap_or(bw(1)); + let s_small = + Bits::static_field(&Awi::zero(max_s_w), 0, &s, 0, max_s_w.get()).unwrap(); + let mut tmp_s = Awi::zero(max_s_w); + tmp_s.mux_(&s_small, success.is_some()).unwrap(); + let out = rotr(&x, &tmp_s); m.graft(&[out.state(), x.state(), s.state()]); } } diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index e9aa94f1..865adf32 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -635,35 +635,46 @@ pub fn ashr(x: &Bits, s: &Bits) -> Awi { } pub fn rotl(x: &Bits, s: &Bits) -> Awi { - debug_assert_eq!(s.bw(), USIZE_BITS); - let signals = selector(s, Some(x.bw())); - // we will use the whole cross bar, with every signal controlling two diagonals - // for the wraparound except for the `x.bw() - 1` one - let mut rolled_signals = vec![inlawi!(0); 2 * x.bw() - 1]; - rolled_signals[x.bw() - 1].copy_(&signals[0]).unwrap(); - for i in 0..(x.bw() - 1) { - rolled_signals[i].copy_(&signals[i + 1]).unwrap(); - rolled_signals[i + x.bw()].copy_(&signals[i + 1]).unwrap(); - } - rolled_signals.reverse(); let mut out = Awi::zero(x.nzbw()); - crossbar(&mut out, x, &rolled_signals, (0, 2 * x.bw() - 1)); + if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { + let mut small_s = Awi::zero(small_s_w); + small_s.resize_(s, false); + + let mut rev_x = Awi::zero(x.nzbw()); + rev_x.copy_(x).unwrap(); + rev_x.rev_(); + + let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + // extension for the bits that are shifted in + let _ = wide_x.field_to(x.bw(), &rev_x, x.bw() - 1); + let _ = wide_x.field_width(&rev_x, x.bw()); + let tmp = funnel(&wide_x, &small_s); + out.resize_(&tmp, false); + out.rev_(); + } else { + let small_width = Awi::from_bool(s.lsb()); + out.resize_(x, false); + let _ = out.field_width(x, small_width.to_usize()); + } out } pub fn rotr(x: &Bits, s: &Bits) -> Awi { - debug_assert_eq!(s.bw(), USIZE_BITS); - let signals = selector(s, Some(x.bw())); - // we will use the whole cross bar, with every signal controlling two diagonals - // for the wraparound except for the `x.bw() - 1` one - let mut rolled_signals = vec![inlawi!(0); 2 * x.bw() - 1]; - rolled_signals[x.bw() - 1].copy_(&signals[0]).unwrap(); - for i in 0..(x.bw() - 1) { - rolled_signals[i].copy_(&signals[i + 1]).unwrap(); - rolled_signals[i + x.bw()].copy_(&signals[i + 1]).unwrap(); - } let mut out = Awi::zero(x.nzbw()); - crossbar(&mut out, x, &rolled_signals, (0, 2 * x.bw() - 1)); + if let Some(small_s_w) = Bits::nontrivial_bits(x.bw() - 1) { + let mut small_s = Awi::zero(small_s_w); + small_s.resize_(s, false); + let mut wide_x = Awi::opaque(NonZeroUsize::new(2 << small_s_w.get()).unwrap()); + // extension for the bits that are shifted in + let _ = wide_x.field_to(x.bw(), x, x.bw() - 1); + let _ = wide_x.field_width(x, x.bw()); + let tmp = funnel(&wide_x, &small_s); + out.resize_(&tmp, false); + } else { + let small_width = Awi::from_bool(s.lsb()); + out.resize_(x, false); + let _ = out.field_width(x, small_width.to_usize()); + } out } From 8e7faa0ccad7b777760768e2f9a1943a378300ef Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Jan 2024 17:44:54 -0600 Subject: [PATCH 078/119] prepare --- starlight/src/lower/lower_op.rs | 15 ++++++++++++++- starlight/src/lower/meta.rs | 12 ++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index d8fb22cd..176a04f8 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -202,7 +202,8 @@ pub fn lower_op( let success = Bits::efficient_add_then_ule(from.to_usize(), width.to_usize(), rhs.bw()); - let max_width_w = Bits::nontrivial_bits(rhs.bw()).unwrap(); + let max = min(lhs.bw(), rhs.bw()); + let max_width_w = Bits::nontrivial_bits(max).unwrap(); let width_small = Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) .unwrap(); @@ -491,6 +492,18 @@ pub fn lower_op( ]); } else { let to = Awi::opaque(m.get_nzbw(to)); + + let success = + Bits::efficient_add_then_ule(to.to_usize(), width.to_usize(), lhs.bw()); + let max = min(lhs.bw(), rhs.bw()); + let max_width_w = Bits::nontrivial_bits(max).unwrap(); + let width_small = + Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) + .unwrap(); + // to achieve a no-op we simply set the width to zero + let mut tmp_width = Awi::zero(max_width_w); + tmp_width.mux_(&width_small, success.is_some()).unwrap(); + let out = field_to(&lhs, &to, &rhs, &width); m.graft(&[ out.state(), diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 865adf32..8b7f1575 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -813,10 +813,14 @@ pub fn negator(x: &Bits, neg: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { - debug_assert_eq!(to.bw(), USIZE_BITS); - debug_assert_eq!(width.bw(), USIZE_BITS); - - // simplified version of `field` below + let mut out = Awi::from_bits(lhs); + // the max shift value that can be anything but a no-op + if let Some(s_w) = Bits::nontrivial_bits(lhs.bw() - 1) { + } else { + let small_width = Awi::from_bool(width.lsb()); + let _ = out.field_width(rhs, small_width.to_usize()); + return out + } let num = lhs.bw(); let next_pow = num.next_power_of_two(); From daa6a42ab09ed4a733872aa5cd8dd702eac639ae Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 00:16:44 -0600 Subject: [PATCH 079/119] tmp --- starlight/src/lower/lower_op.rs | 2 +- starlight/src/lower/meta.rs | 53 +++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 176a04f8..21847791 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -504,7 +504,7 @@ pub fn lower_op( let mut tmp_width = Awi::zero(max_width_w); tmp_width.mux_(&width_small, success.is_some()).unwrap(); - let out = field_to(&lhs, &to, &rhs, &width); + let out = field_to(&lhs, &to, &rhs, &tmp_width); m.graft(&[ out.state(), lhs.state(), diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 8b7f1575..dd76d800 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -252,7 +252,7 @@ pub fn tsmear_inx(inx: &Bits, num_signals: usize) -> Vec { // need extra bit to get all `n + 1` lb_num += 1; } - let mut signals = vec![]; + let mut signals = Vec::with_capacity(num_signals); for i in 0..num_signals { // if `inx < i` let mut signal = inlawi!(0); @@ -544,7 +544,7 @@ pub fn funnel(x: &Bits, s: &Bits) -> Awi { /// guarantees that nothing happens to `lhs` even with `from` being out of range pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { let mut out = Awi::from_bits(lhs); - // the max shift value that can be anything but a no-op + // the max shift value that can be anything but an effective no-op if let Some(s_w) = Bits::nontrivial_bits(rhs.bw() - 1) { let mut s = Awi::zero(s_w); s.resize_(from, false); @@ -813,15 +813,56 @@ pub fn negator(x: &Bits, neg: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { - let mut out = Awi::from_bits(lhs); - // the max shift value that can be anything but a no-op + // the max shift value that can be anything but an effective no-op if let Some(s_w) = Bits::nontrivial_bits(lhs.bw() - 1) { + // first, create the shifted image of `rhs` + let mut s = Awi::zero(s_w); + s.resize_(&to, false); + let mut wide_rhs = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); + let mut rev_rhs = Awi::zero(rhs.nzbw()); + rev_rhs.copy_(&rhs).unwrap(); + rev_rhs.rev_(); + let _ = wide_rhs.field_to((1 << s_w.get()) - rhs.bw(), &rev_rhs, rhs.bw()); + let tmp = funnel(&wide_rhs, &s); + let mut funnel_res = Awi::zero(lhs.nzbw()); + funnel_res.resize_(&tmp, false); + funnel_res.rev_(); + + // second, we need a mask that indicates where the `width`-sized window is + // placed + + // need an extra bit for the `tsmear_inx` to work in all circumstances + let s_w = NonZeroUsize::new(s_w.get().checked_add(1).unwrap()).unwrap(); + let mut small_to = Awi::zero(s_w); + small_to.usize_(to.to_usize()); + let mut small_width = Awi::zero(s_w); + small_width.usize_(width.to_usize()); + // to + width + let mut to_plus_width = small_width; + to_plus_width.add_(&small_to).unwrap(); + // trailing mask that trails `to + width`, exclusive + let tmask = tsmear_inx(&to_plus_width, lhs.bw()); + // leading mask that leads `to`, inclusive, implemented by negating a trailing + // mask of `to` + let lmask = tsmear_inx(&small_to, lhs.bw()); + + let mut out = SmallVec::with_capacity(lhs.bw()); + for i in 0..lhs.bw() { + let mut signal = inlawi!(0); + static_lut!(signal; 1111_1011_0100_0000; lmask[i], tmask[i], funnel_res.get(i).unwrap(), lhs.get(i).unwrap()); + //static_lut!(signal; 1111000011110000; lmask[i], tmask[i], + // funnel_res.get(i).unwrap(), lhs.get(i).unwrap()); + out.push(signal.state()); + } + + return concat(lhs.nzbw(), out); } else { + let mut out = Awi::from_bits(lhs); let small_width = Awi::from_bool(width.lsb()); let _ = out.field_width(rhs, small_width.to_usize()); return out } - + /* let num = lhs.bw(); let next_pow = num.next_power_of_two(); let mut lb_num = next_pow.trailing_zeros() as usize; @@ -867,7 +908,7 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { let mut out = awi!(0); out.lut_(&lut, width).unwrap(); out - } + }*/ } /// Setting `width` to 0 guarantees that nothing happens even with other From ccaaf899b6f6cfc6f48b41ba4fa40cf570d29567 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 13:38:04 -0600 Subject: [PATCH 080/119] Update meta.rs --- starlight/src/lower/meta.rs | 63 ++++++------------------------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index dd76d800..d20e0646 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -822,13 +822,18 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { let mut rev_rhs = Awi::zero(rhs.nzbw()); rev_rhs.copy_(&rhs).unwrap(); rev_rhs.rev_(); - let _ = wide_rhs.field_to((1 << s_w.get()) - rhs.bw(), &rev_rhs, rhs.bw()); + if let Some(field_to) = lhs.bw().checked_sub(rhs.bw()) { + let _ = wide_rhs.field_to(field_to, &rev_rhs, rhs.bw()); + } else { + let field_from = rhs.bw().wrapping_sub(lhs.bw()); + let _ = wide_rhs.field_from(&rev_rhs, field_from, lhs.bw()); + } let tmp = funnel(&wide_rhs, &s); let mut funnel_res = Awi::zero(lhs.nzbw()); funnel_res.resize_(&tmp, false); funnel_res.rev_(); - // second, we need a mask that indicates where the `width`-sized window is + // second, we need two masks to indicate where the `width`-sized window is // placed // need an extra bit for the `tsmear_inx` to work in all circumstances @@ -846,69 +851,21 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { // mask of `to` let lmask = tsmear_inx(&small_to, lhs.bw()); + // third, multiplex based on the masks let mut out = SmallVec::with_capacity(lhs.bw()); for i in 0..lhs.bw() { let mut signal = inlawi!(0); static_lut!(signal; 1111_1011_0100_0000; lmask[i], tmask[i], funnel_res.get(i).unwrap(), lhs.get(i).unwrap()); - //static_lut!(signal; 1111000011110000; lmask[i], tmask[i], - // funnel_res.get(i).unwrap(), lhs.get(i).unwrap()); out.push(signal.state()); } - return concat(lhs.nzbw(), out); + concat(lhs.nzbw(), out) } else { let mut out = Awi::from_bits(lhs); let small_width = Awi::from_bool(width.lsb()); let _ = out.field_width(rhs, small_width.to_usize()); - return out - } - /* - let num = lhs.bw(); - let next_pow = num.next_power_of_two(); - let mut lb_num = next_pow.trailing_zeros() as usize; - if next_pow == num { - // need extra bit to get all `n + 1` - lb_num += 1; - } - if let Some(w) = NonZeroUsize::new(lb_num) { - let mut signals = selector(to, Some(num)); - signals.reverse(); - - let mut rhs_to_lhs = Awi::zero(lhs.nzbw()); - crossbar(&mut rhs_to_lhs, rhs, &signals, (0, lhs.bw())); - - // to + width - let mut tmp = Awi::zero(w); - tmp.usize_(to.to_usize()); - tmp.add_(&awi!(width[..(w.get())]).unwrap()).unwrap(); - let tmask = tsmear_inx(&tmp, lhs.bw()); - // lhs.bw() - to - let mut tmp = Awi::zero(w); - tmp.usize_(lhs.bw()); - tmp.sub_(&awi!(to[..(w.get())]).unwrap()).unwrap(); - let mut lmask = tsmear_inx(&tmp, lhs.bw()); - lmask.reverse(); - - let nzbw = lhs.nzbw(); - let mut out = SmallVec::with_capacity(nzbw.get()); - // when `tmask` and `lmask` are both set, mux_ in `rhs` - for i in 0..lhs.bw() { - let mut lut_out = inlawi!(0); - static_lut!(lut_out; 1011_1111_1000_0000; - rhs_to_lhs.get(i).unwrap(), - tmask[i], - lmask[i], - lhs.get(i).unwrap() - ); - out.push(lut_out.state()); - } - concat(nzbw, out) - } else { - let lut = inlawi!(rhs[0], lhs[0]).unwrap(); - let mut out = awi!(0); - out.lut_(&lut, width).unwrap(); out - }*/ + } } /// Setting `width` to 0 guarantees that nothing happens even with other From b00ff3fe66c61b1ff72faded4a2e6d5ce08bd41b Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 13:43:19 -0600 Subject: [PATCH 081/119] Update meta.rs --- starlight/src/lower/meta.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index d20e0646..0e9415bc 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -817,10 +817,10 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { if let Some(s_w) = Bits::nontrivial_bits(lhs.bw() - 1) { // first, create the shifted image of `rhs` let mut s = Awi::zero(s_w); - s.resize_(&to, false); - let mut wide_rhs = Awi::zero(NonZeroUsize::new(2 << s_w.get()).unwrap()); + s.resize_(to, false); + let mut wide_rhs = Awi::opaque(NonZeroUsize::new(2 << s_w.get()).unwrap()); let mut rev_rhs = Awi::zero(rhs.nzbw()); - rev_rhs.copy_(&rhs).unwrap(); + rev_rhs.copy_(rhs).unwrap(); rev_rhs.rev_(); if let Some(field_to) = lhs.bw().checked_sub(rhs.bw()) { let _ = wide_rhs.field_to(field_to, &rev_rhs, rhs.bw()); From 8db187f69bbbff9c9c80b78fa099464b0fe4c929 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 14:39:14 -0600 Subject: [PATCH 082/119] finally get rid of old crossbar system --- starlight/src/lower/lower_op.rs | 17 ++++- starlight/src/lower/meta.rs | 112 ++++++++++++++++---------------- 2 files changed, 73 insertions(+), 56 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 21847791..edd57b92 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -538,7 +538,22 @@ pub fn lower_op( } else { let to = Awi::opaque(m.get_nzbw(to)); let from = Awi::opaque(m.get_nzbw(from)); - let out = field(&lhs, &to, &rhs, &from, &width); + + let success = + Bits::efficient_add_then_ule(to.to_usize(), width.to_usize(), lhs.bw()) + .is_some() + & Bits::efficient_add_then_ule(from.to_usize(), width.to_usize(), rhs.bw()) + .is_some(); + let max = min(lhs.bw(), rhs.bw()); + let max_width_w = Bits::nontrivial_bits(max).unwrap(); + let width_small = + Bits::static_field(&Awi::zero(max_width_w), 0, &width, 0, max_width_w.get()) + .unwrap(); + // to achieve a no-op we simply set the width to zero + let mut tmp_width = Awi::zero(max_width_w); + tmp_width.mux_(&width_small, success).unwrap(); + + let out = field(&lhs, &to, &rhs, &from, &tmp_width); m.graft(&[ out.state(), lhs.state(), diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 0e9415bc..2e9bfe36 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -479,7 +479,7 @@ pub fn field_width(lhs: &Bits, rhs: &Bits, width: &Bits) -> Awi { } // old static strategy if we need it - +/* /// Given the diagonal control lines and input of a crossbar with output width /// s.t. `input.bw() + out.bw() - 1 = signals.bw()`, returns the output. The /// `i`th input bit and `j`th output bit are controlled by the @@ -513,6 +513,7 @@ pub fn crossbar( } concat_update(output, nzbw, tmp_output) } +*/ /*pub fn funnel(x: &Bits, s: &Bits) -> Awi { debug_assert_eq!(x.bw() & 1, 0); @@ -871,68 +872,69 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { /// Setting `width` to 0 guarantees that nothing happens even with other /// arguments being invalid pub fn field(lhs: &Bits, to: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { - debug_assert_eq!(to.bw(), USIZE_BITS); - debug_assert_eq!(from.bw(), USIZE_BITS); - debug_assert_eq!(width.bw(), USIZE_BITS); - - // we use some summation to get the fielding done with a single crossbar - - // the basic shift offset is based on `to - from`, to keep the shift value - // positive in case of `to == 0` and `from == rhs.bw()` we add `rhs.bw()` to - // this value. The opposite extreme is therefore `to == lhs.bw()` and `from == - // 0`, which will be equal to `lhs.bw() + rhs.bw()` because of the added - // `rhs.bw()`. - let num = lhs.bw() + rhs.bw(); - let lb_num = num.next_power_of_two().trailing_zeros() as usize; - if let Some(w) = NonZeroUsize::new(lb_num) { - let mut shift = Awi::zero(w); - shift.usize_(rhs.bw()); - shift.add_(&awi!(to[..(w.get())]).unwrap()).unwrap(); - shift.sub_(&awi!(from[..(w.get())]).unwrap()).unwrap(); - - let mut signals = selector(&shift, Some(num)); - signals.reverse(); + // we can shift both ways now, from the msb of `rhs` to the lsb of `lhs` and the + // lsb of `rhs` to the msb of `lhs`. + if let Some(s_w) = Bits::nontrivial_bits(lhs.bw() + rhs.bw() - 2) { + // we do this to achieve fielding with a single shift construct + + // `from` cannot be more than `rhs.bw() - 1` under valid no-op conditions, so we + // calculate `to - from` offsetted by `rhs.bw() - 1` to keep it positive. The + // opposite extreme of `to == lhs.bw() - 1` and `from == 0` cannot overflow + // because of the way `s_w` was made. + let mut s = Awi::zero(s_w); + let mut small_from = Awi::zero(s_w); + let mut small_to = Awi::zero(s_w); + small_from.resize_(from, false); + small_to.resize_(to, false); + s.usize_(rhs.bw() - 1); + s.sub_(&small_from).unwrap(); + s.add_(&small_to).unwrap(); - let mut rhs_to_lhs = Awi::zero(lhs.nzbw()); - // really what `field` is is a well defined full crossbar, the masking part - // after this is optimized to nothing if `rhs` is zero. - crossbar(&mut rhs_to_lhs, rhs, &signals, (0, num)); + // first, create the shifted image of `rhs` + let mut wide_rhs = Awi::opaque(NonZeroUsize::new(2 << s_w.get()).unwrap()); + let mut rev_rhs = Awi::zero(rhs.nzbw()); + rev_rhs.copy_(rhs).unwrap(); + rev_rhs.rev_(); + let _ = wide_rhs.field_to(lhs.bw() - 1, &rev_rhs, rhs.bw()); + let tmp = funnel(&wide_rhs, &s); + let mut funnel_res = Awi::zero(lhs.nzbw()); + funnel_res.resize_(&tmp, false); + funnel_res.rev_(); - // `rhs` is now shifted correctly but we need a mask to overwrite the correct - // bits of `lhs`. We use opposing `tsmears` and AND them together to get the - // `width` window in the correct spot. + // second, we need two masks to indicate where the `width`-sized window is + // placed + // need an extra bit for the `tsmear_inx` to work in all circumstances + let s_w = NonZeroUsize::new(s_w.get().checked_add(1).unwrap()).unwrap(); + let mut small_to = Awi::zero(s_w); + small_to.usize_(to.to_usize()); + let mut small_width = Awi::zero(s_w); + small_width.usize_(width.to_usize()); // to + width - let mut tmp = Awi::zero(w); - tmp.usize_(to.to_usize()); - tmp.add_(&awi!(width[..(w.get())]).unwrap()).unwrap(); - let tmask = tsmear_inx(&tmp, lhs.bw()); - // lhs.bw() - to - let mut tmp = Awi::zero(w); - tmp.usize_(lhs.bw()); - tmp.sub_(&awi!(to[..(w.get())]).unwrap()).unwrap(); - let mut lmask = tsmear_inx(&tmp, lhs.bw()); - lmask.reverse(); - - let nzbw = lhs.nzbw(); - let mut out = SmallVec::with_capacity(nzbw.get()); - // when `tmask` and `lmask` are both set, mux_ in `rhs` + let mut to_plus_width = small_width; + to_plus_width.add_(&small_to).unwrap(); + // trailing mask that trails `to + width`, exclusive + let tmask = tsmear_inx(&to_plus_width, lhs.bw()); + // leading mask that leads `to`, inclusive, implemented by negating a trailing + // mask of `to` + let lmask = tsmear_inx(&small_to, lhs.bw()); + + // third, multiplex based on the masks + let mut out = SmallVec::with_capacity(lhs.bw()); for i in 0..lhs.bw() { - let mut lut_out = inlawi!(0); - static_lut!(lut_out; 1011_1111_1000_0000; - rhs_to_lhs.get(i).unwrap(), - tmask[i], - lmask[i], - lhs.get(i).unwrap() + let mut signal = inlawi!(0); + static_lut!( + signal; 1111_1011_0100_0000; + lmask[i], tmask[i], funnel_res.get(i).unwrap(), lhs.get(i).unwrap() ); - out.push(lut_out.state()); + out.push(signal.state()); } - concat(nzbw, out) + + concat(lhs.nzbw(), out) } else { - // `lhs.bw() == 1`, `rhs.bw() == 1`, `width` is the only thing that matters - let lut = inlawi!(rhs[0], lhs[0]).unwrap(); - let mut out = awi!(0); - out.lut_(&lut, width).unwrap(); + let mut out = Awi::from_bits(lhs); + let small_width = Awi::from_bool(width.lsb()); + let _ = out.field_width(rhs, small_width.to_usize()); out } } From bdd473c9280be66de7211139a04204160effa21d Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 14:45:36 -0600 Subject: [PATCH 083/119] remove old `static_field` --- starlight/src/lower/lower_op.rs | 38 +++++++++++++------------ starlight/src/lower/meta.rs | 49 --------------------------------- 2 files changed, 20 insertions(+), 67 deletions(-) diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index edd57b92..749d996c 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -129,7 +129,7 @@ pub fn lower_op( let rhs = Awi::opaque(rhs_w); // If `width_u` is out of bounds `out` is created as a no-op of `lhs` as // expected - let out = static_field(&lhs, 0, &rhs, 0, width_u).0; + let out = Bits::static_field(&lhs, 0, &rhs, 0, width_u).unwrap(); m.graft(&[ out.state(), lhs.state(), @@ -175,13 +175,12 @@ pub fn lower_op( let sub_rhs_w = rhs.bw() - from_u; if let Some(w) = NonZeroUsize::new(sub_rhs_w) { let tmp0 = Awi::zero(w); - let (tmp1, o) = static_field(&tmp0, 0, &rhs, from_u, sub_rhs_w); - let mut out = lhs.clone(); - if o { - out - } else { + if let Some(tmp1) = Bits::static_field(&tmp0, 0, &rhs, from_u, sub_rhs_w) { + let mut out = lhs.clone(); let _ = out.field_width(&tmp1, width.to_usize()); out + } else { + lhs.clone() } } else { lhs.clone() @@ -229,7 +228,7 @@ pub fn lower_op( x.clone() } else { let tmp = Awi::zero(x.nzbw()); - static_field(&tmp, s_u, &x, 0, x.bw() - s_u).0 + Bits::static_field(&tmp, s_u, &x, 0, x.bw() - s_u).unwrap() }; m.graft(&[out.state(), x.state(), Awi::opaque(m.get_nzbw(s)).state()]); } else { @@ -255,7 +254,7 @@ pub fn lower_op( x.clone() } else { let tmp = Awi::zero(x.nzbw()); - static_field(&tmp, 0, &x, s_u, x.bw() - s_u).0 + Bits::static_field(&tmp, 0, &x, s_u, x.bw() - s_u).unwrap() }; m.graft(&[out.state(), x.state(), Awi::opaque(m.get_nzbw(s)).state()]); } else { @@ -283,7 +282,7 @@ pub fn lower_op( for i in 0..x.bw() { tmp.set(i, x.msb()).unwrap(); } - static_field(&tmp, 0, &x, s_u, x.bw() - s_u).0 + Bits::static_field(&tmp, 0, &x, s_u, x.bw() - s_u).unwrap() }; m.graft(&[out.state(), x.state(), Awi::opaque(m.get_nzbw(s)).state()]); } else { @@ -307,8 +306,9 @@ pub fn lower_op( let out = if (s_u == 0) || (x.bw() <= s_u) { x.clone() } else { - let tmp = static_field(&Awi::zero(x.nzbw()), s_u, &x, 0, x.bw() - s_u).0; - static_field(&tmp, 0, &x, x.bw() - s_u, s_u).0 + let tmp = + Bits::static_field(&Awi::zero(x.nzbw()), s_u, &x, 0, x.bw() - s_u).unwrap(); + Bits::static_field(&tmp, 0, &x, x.bw() - s_u, s_u).unwrap() }; m.graft(&[out.state(), x.state(), Awi::opaque(m.get_nzbw(s)).state()]); } else { @@ -332,8 +332,9 @@ pub fn lower_op( let out = if (s_u == 0) || (x.bw() <= s_u) { x.clone() } else { - let tmp = static_field(&Awi::zero(x.nzbw()), 0, &x, s_u, x.bw() - s_u).0; - static_field(&tmp, x.bw() - s_u, &x, 0, s_u).0 + let tmp = + Bits::static_field(&Awi::zero(x.nzbw()), 0, &x, s_u, x.bw() - s_u).unwrap(); + Bits::static_field(&tmp, x.bw() - s_u, &x, 0, s_u).unwrap() }; m.graft(&[out.state(), x.state(), Awi::opaque(m.get_nzbw(s)).state()]); } else { @@ -473,12 +474,13 @@ pub fn lower_op( let out = if lhs.bw() < to_u { lhs.clone() } else if let Some(w) = NonZeroUsize::new(lhs.bw() - to_u) { - let (mut lhs_hi, o) = static_field(&Awi::zero(w), 0, &lhs, to_u, w.get()); - let _ = lhs_hi.field_width(&rhs, width.to_usize()); - if o { - lhs.clone() + if let Some(mut lhs_hi) = + Bits::static_field(&Awi::zero(w), 0, &lhs, to_u, w.get()) + { + let _ = lhs_hi.field_width(&rhs, width.to_usize()); + Bits::static_field(&lhs, to_u, &lhs_hi, 0, w.get()).unwrap() } else { - static_field(&lhs, to_u, &lhs_hi, 0, w.get()).0 + lhs.clone() } } else { lhs.clone() diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 2e9bfe36..3437a5d1 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -403,55 +403,6 @@ pub fn resize_cond(x: &Bits, w: NonZeroUsize, signed: &Bits) -> Awi { } } -/// Returns (`lhs`, true) if there are invalid values -pub fn static_field(lhs: &Bits, to: usize, rhs: &Bits, from: usize, width: usize) -> (Awi, bool) { - if (width > lhs.bw()) - || (width > rhs.bw()) - || (to > (lhs.bw() - width)) - || (from > (rhs.bw() - width)) - { - return (Awi::from_bits(lhs), true); - } - let res = if let Some(width) = NonZeroUsize::new(width) { - if let Some(lhs_rem_lo) = NonZeroUsize::new(to) { - if let Some(lhs_rem_hi) = NonZeroUsize::new(from) { - Awi::new( - lhs.nzbw(), - Op::ConcatFields(ConcatFieldsType::from_iter([ - (lhs.state(), 0usize, lhs_rem_lo), - (rhs.state(), from, width), - (lhs.state(), to + width.get(), lhs_rem_hi), - ])), - ) - } else { - Awi::new( - lhs.nzbw(), - Op::ConcatFields(ConcatFieldsType::from_iter([ - (lhs.state(), 0usize, lhs_rem_lo), - (rhs.state(), from, width), - ])), - ) - } - } else if let Some(lhs_rem_hi) = NonZeroUsize::new(lhs.bw() - width.get()) { - Awi::new( - lhs.nzbw(), - Op::ConcatFields(ConcatFieldsType::from_iter([ - (rhs.state(), from, width), - (lhs.state(), width.get(), lhs_rem_hi), - ])), - ) - } else { - Awi::new( - lhs.nzbw(), - Op::ConcatFields(ConcatFieldsType::from_iter([(rhs.state(), from, width)])), - ) - } - } else { - Awi::from_bits(lhs) - }; - (res, false) -} - /// This does not handle invalid arguments; set `width` to zero to cause no-ops pub fn field_width(lhs: &Bits, rhs: &Bits, width: &Bits) -> Awi { let min_w = min(lhs.bw(), rhs.bw()); From 02bdbc7d45a16f68ed7ff2e38d0ef702f0c698c8 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:00:38 -0600 Subject: [PATCH 084/119] Update fuzz_lower.rs --- testcrate/tests/fuzz_lower.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index ddec29cc..6944962e 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -12,9 +12,9 @@ use rand_xoshiro::{ use starlight::{ awi, awint_dag::EvalError, - dag, + dag::{self}, triple_arena::{ptr_struct, Arena}, - Epoch, EvalAwi, LazyAwi, + Epoch, EvalAwi, LazyAwi, StarRng, }; // miri is just here to check that the unsized deref hacks are working @@ -43,7 +43,7 @@ struct Mem { // random querying v: Vec>, v_len: usize, - rng: Xoshiro128StarStar, + rng: StarRng, } impl Mem { @@ -58,7 +58,7 @@ impl Mem { roots: vec![], v, v_len, - rng: Xoshiro128StarStar::seed_from_u64(0), + rng: StarRng::new(0), } } @@ -73,19 +73,16 @@ impl Mem { /// Randomly creates a new pair or gets an existing one under the `cap` pub fn next_capped(&mut self, w: usize, cap: usize) -> P0 { - let try_query = (self.rng.next_u32() % 4) != 0; - if try_query && (!self.v[w].is_empty()) { - let p = self.v[w][(self.rng.next_u32() as usize) % self.v[w].len()]; - if self.get_awi(p).to_usize() < cap { - return p + if self.rng.out_of_4(3) && (!self.v[w].is_empty()) { + let p = self.rng.index_slice(&self.v[w]).unwrap(); + if self.get_awi(*p).to_usize() < cap { + return *p } } let nzbw = NonZeroUsize::new(w).unwrap(); let lazy = LazyAwi::opaque(nzbw); let mut lit = awi::Awi::zero(nzbw); - lit.rand_(&mut self.rng).unwrap(); - let tmp = lit.to_usize() % cap; - lit.usize_(tmp); + lit.usize_(self.rng.index(cap).unwrap()); let p = self.a.insert(Pair { awi: lit.clone(), dag: dag::Awi::from(lazy.as_ref()), @@ -98,14 +95,13 @@ impl Mem { /// Randomly creates a new pair or gets an existing one pub fn next(&mut self, w: usize) -> P0 { - let try_query = (self.rng.next_u32() % 4) != 0; - if try_query && (!self.v[w].is_empty()) { - self.v[w][(self.rng.next_u32() as usize) % self.v[w].len()] + if self.rng.out_of_4(3) && (!self.v[w].is_empty()) { + *self.rng.index_slice(&self.v[w]).unwrap() } else { let nzbw = NonZeroUsize::new(w).unwrap(); let lazy = LazyAwi::opaque(nzbw); let mut lit = awi::Awi::zero(nzbw); - lit.rand_(&mut self.rng).unwrap(); + self.rng.next_bits(&mut lit); let p = self.a.insert(Pair { awi: lit.clone(), dag: dag::Awi::from(lazy.as_ref()), @@ -120,7 +116,7 @@ impl Mem { /// Calls `next` with a random integer in 1..5, returning a tuple of the /// width chosen and the Ptr to what `next` returned. pub fn next1_5(&mut self) -> (usize, P0) { - let w = ((self.rng.next_u32() as usize) % 4) + 1; + let w = ((self.rng.next_u8() as usize) % 4) + 1; (w, self.next(w)) } @@ -159,7 +155,7 @@ impl Mem { // set half of the roots randomly let len = self.roots.len(); for _ in 0..(len / 2) { - let inx = (self.rng.next_u64() % (self.roots.len() as u64)) as usize; + let inx = self.rng.index(self.roots.len()).unwrap(); let (lazy, lit) = self.roots.remove(inx); lazy.retro_(&lit).unwrap(); } From 5b19ba7a55ae60423ee43d1a057a5a4440fff00c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:02:49 -0600 Subject: [PATCH 085/119] renaming --- testcrate/tests/fuzz_elementary.rs | 12 ++-- testcrate/tests/fuzz_lower.rs | 88 +++++++++++++++--------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index f17c9c5c..b2289cc1 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -86,7 +86,7 @@ impl Mem { } } - pub fn next1_5(&mut self) -> (usize, P0) { + pub fn next4(&mut self) -> (usize, P0) { let w = ((self.rng.next_u8() as usize) % 4) + 1; (w, self.next(w)) } @@ -125,7 +125,7 @@ fn operation(rng: &mut StarRng, m: &mut Mem) { 0 => { // doesn't actually do anything on the DAG side, but we use it to get parallel // things in the fuzzing - let (w, from) = m.next1_5(); + let (w, from) = m.next4(); let to = m.next(w); if to != from { let (to, from) = m.a.get2_mut(to, from).unwrap(); @@ -135,8 +135,8 @@ fn operation(rng: &mut StarRng, m: &mut Mem) { } // Get-Set 1 => { - let (w0, from) = m.next1_5(); - let (w1, to) = m.next1_5(); + let (w0, from) = m.next4(); + let (w1, to) = m.next4(); let usize_inx0 = (rng.next_u32() as usize) % w0; let usize_inx1 = (rng.next_u32() as usize) % w1; let b = m.a[from].awi.get(usize_inx0).unwrap(); @@ -146,8 +146,8 @@ fn operation(rng: &mut StarRng, m: &mut Mem) { } // Lut 2 => { - let (out_w, out) = m.next1_5(); - let (inx_w, inx) = m.next1_5(); + let (out_w, out) = m.next4(); + let (inx_w, inx) = m.next4(); let lut = m.next(out_w * (1 << inx_w)); let lut_a = m.get(lut); let inx_a = m.get(inx); diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 6944962e..9c218194 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -115,7 +115,7 @@ impl Mem { /// Calls `next` with a random integer in 1..5, returning a tuple of the /// width chosen and the Ptr to what `next` returned. - pub fn next1_5(&mut self) -> (usize, P0) { + pub fn next4(&mut self) -> (usize, P0) { let w = ((self.rng.next_u8() as usize) % 4) + 1; (w, self.next(w)) } @@ -182,8 +182,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { match next_op { // Lut, StaticLut 0 => { - let (out_w, out) = m.next1_5(); - let (inx_w, inx) = m.next1_5(); + let (out_w, out) = m.next4(); + let (inx_w, inx) = m.next4(); let lut = m.next(out_w * (1 << inx_w)); let lut_a = m.get_awi(lut); let inx_a = m.get_awi(inx); @@ -194,7 +194,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Get, StaticGet 1 => { - let (bits_w, bits) = m.next1_5(); + let (bits_w, bits) = m.next4(); let inx = m.next_usize(bits_w); let out = m.next(1); let bits_a = m.get_awi(bits); @@ -208,7 +208,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Set, StaticSet 2 => { - let (bits_w, bits) = m.next1_5(); + let (bits_w, bits) = m.next4(); let inx = m.next_usize(bits_w); let bit = m.next(1); let inx_a = m.get_awi(inx); @@ -224,9 +224,9 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // FieldBit 3 => { - let (lhs_w, lhs) = m.next1_5(); + let (lhs_w, lhs) = m.next4(); let to = m.next_usize(lhs_w); - let (rhs_w, rhs) = m.next1_5(); + let (rhs_w, rhs) = m.next4(); let from = m.next_usize(rhs_w); let to_a = m.get_awi(to); let rhs_a = m.get_awi(rhs); @@ -243,8 +243,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // ZeroResize 4 => { - let lhs = m.next1_5().1; - let rhs = m.next1_5().1; + let lhs = m.next4().1; + let rhs = m.next4().1; let rhs_a = m.get_awi(rhs); m.get_mut_awi(lhs).zero_resize_(&rhs_a); let rhs_b = m.get_dag(rhs); @@ -252,8 +252,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // SignResize 5 => { - let lhs = m.next1_5().1; - let rhs = m.next1_5().1; + let lhs = m.next4().1; + let rhs = m.next4().1; let rhs_a = m.get_awi(rhs); m.get_mut_awi(lhs).sign_resize_(&rhs_a); let rhs_b = m.get_dag(rhs); @@ -261,13 +261,13 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Not 6 => { - let x = m.next1_5().1; + let x = m.next4().1; m.get_mut_awi(x).not_(); m.get_mut_dag(x).not_(); } // Or, And, Xor 7 => { - let (lhs_w, lhs) = m.next1_5(); + let (lhs_w, lhs) = m.next4(); let rhs = m.next(lhs_w); let rhs_a = m.get_awi(rhs); let rhs_b = m.get_dag(rhs); @@ -289,7 +289,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Inc, IncCout, Dec, DecCout 8 => { - let x = m.next1_5().1; + let x = m.next4().1; let cin = m.next(1); let cout = m.next(1); let cin_a = m.get_awi(cin); @@ -309,7 +309,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { // CinSum, UnsignedOverflow, SignedOverflow 9 => { let cin = m.next(1); - let (lhs_w, lhs) = m.next1_5(); + let (lhs_w, lhs) = m.next4(); let rhs = m.next(lhs_w); let out = m.next(lhs_w); let unsigned = m.next(1); @@ -337,7 +337,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Lsb, Msb 10 => { - let x = m.next1_5().1; + let x = m.next4().1; let out = m.next(1); if (rng.next_u32() & 1) == 0 { let a = m.get_awi(x).lsb(); @@ -353,7 +353,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Neg, Abs 11 => { - let x = m.next1_5().1; + let x = m.next4().1; if (rng.next_u32() & 1) == 0 { let neg = m.next(1); let a = m.get_awi(neg).to_bool(); @@ -380,8 +380,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // FieldWidth 13 => { - let (w0, lhs) = m.next1_5(); - let (w1, rhs) = m.next1_5(); + let (w0, lhs) = m.next4(); + let (w1, rhs) = m.next4(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let rhs_a = m.get_awi(rhs); @@ -397,8 +397,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // FieldFrom 14 => { - let (w0, lhs) = m.next1_5(); - let (w1, rhs) = m.next1_5(); + let (w0, lhs) = m.next4(); + let (w1, rhs) = m.next4(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let from = m.next_usize(1 + w1 - m.get_awi(width).to_usize()); @@ -417,7 +417,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Shl, Lshr, Ashr, Rotl, Rotr 15 => { - let (w, x) = m.next1_5(); + let (w, x) = m.next4(); let s = m.next_usize(w); let s_a = m.get_awi(s); let s_b = m.get_dag(s); @@ -447,8 +447,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // FieldTo 16 => { - let (w0, lhs) = m.next1_5(); - let (w1, rhs) = m.next1_5(); + let (w0, lhs) = m.next4(); + let (w1, rhs) = m.next4(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let to = m.next_usize(1 + w0 - m.get_awi(width).to_usize()); @@ -467,7 +467,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Add, Sub, Rsb 17 => { - let (w, lhs) = m.next1_5(); + let (w, lhs) = m.next4(); let rhs = m.next(w); let rhs_a = m.get_awi(rhs); let rhs_b = m.get_dag(rhs); @@ -489,8 +489,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Field 18 => { - let (w0, lhs) = m.next1_5(); - let (w1, rhs) = m.next1_5(); + let (w0, lhs) = m.next4(); + let (w1, rhs) = m.next4(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let to = m.next_usize(1 + w0 - m.get_awi(width).to_usize()); @@ -523,13 +523,13 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Rev 19 => { - let x = m.next1_5().1; + let x = m.next4().1; m.get_mut_awi(x).rev_(); m.get_mut_dag(x).rev_(); } // Eq, Ne, Ult, Ule, Ilt, Ile 20 => { - let (w, lhs) = m.next1_5(); + let (w, lhs) = m.next4(); let rhs = m.next(w); let lhs_a = m.get_awi(lhs); let lhs_b = m.get_dag(lhs); @@ -566,7 +566,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // IsZero, IsUmax, IsImax, IsImin, IsUone 21 => { - let x = m.next1_5().1; + let x = m.next4().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); let out = m.next(1); @@ -596,7 +596,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // CountOnes, Lz, Tz, Sig 22 => { - let x = m.next1_5().1; + let x = m.next4().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); let out = m.next_usize(usize::MAX); @@ -622,8 +622,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // LutSet 23 => { - let (entry_w, entry) = m.next1_5(); - let (inx_w, inx) = m.next1_5(); + let (entry_w, entry) = m.next4(); + let (inx_w, inx) = m.next4(); let table_w = entry_w * (1 << inx_w); let table = m.next(table_w); let entry_a = m.get_awi(entry); @@ -635,8 +635,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Resize 24 => { - let lhs = m.next1_5().1; - let rhs = m.next1_5().1; + let lhs = m.next4().1; + let rhs = m.next4().1; let b = m.next(1); let rhs_a = m.get_awi(rhs); let b_a = m.get_awi(b); @@ -647,8 +647,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // ZeroResizeOverflow, SignResizeOverflow 25 => { - let lhs = m.next1_5().1; - let rhs = m.next1_5().1; + let lhs = m.next4().1; + let rhs = m.next4().1; let out = m.next(1); let mut lhs_a = m.get_awi(lhs); let rhs_a = m.get_awi(rhs); @@ -668,7 +668,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // ArbMulAdd 26 => { - let (w, lhs) = m.next1_5(); + let (w, lhs) = m.next4(); match rng.next_u32() % 3 { 0 => { let rhs = m.next(w); @@ -681,8 +681,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { m.get_mut_dag(out).mul_add_(&lhs_b, &rhs_b).unwrap(); } 1 => { - let rhs = m.next1_5().1; - let out = m.next1_5().1; + let rhs = m.next4().1; + let out = m.next4().1; let lhs_a = m.get_awi(lhs); let rhs_a = m.get_awi(rhs); let lhs_b = m.get_dag(lhs); @@ -691,8 +691,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { m.get_mut_dag(out).arb_umul_add_(&lhs_b, &rhs_b); } 2 => { - let rhs = m.next1_5().1; - let out = m.next1_5().1; + let rhs = m.next4().1; + let out = m.next4().1; let mut lhs_a = m.get_awi(lhs); let mut rhs_a = m.get_awi(rhs); let mut lhs_b = m.get_dag(lhs); @@ -705,7 +705,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Mux 27 => { - let (w, lhs) = m.next1_5(); + let (w, lhs) = m.next4(); let rhs = m.next(w); let b = m.next(1); let rhs_a = m.get_awi(rhs); @@ -717,7 +717,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // UQuo, URem, IQuo, IRem 28 => { - let (w, duo) = m.next1_5(); + let (w, duo) = m.next4(); let div = m.next(w); let quo = m.next(w); let rem = m.next(w); From 08321d37156ceb51f6a915a1b16e9828d9fbd078 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:09:14 -0600 Subject: [PATCH 086/119] enhancing the fuzzing --- testcrate/tests/fuzz_elementary.rs | 2 ++ testcrate/tests/fuzz_lower.rs | 53 ++++++++++++++++-------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index b2289cc1..2526a02e 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -86,6 +86,8 @@ impl Mem { } } + /// Calls `next` with a random integer in 1..=4, returning a tuple of the + /// width chosen and the Ptr to what `next` returned. pub fn next4(&mut self) -> (usize, P0) { let w = ((self.rng.next_u8() as usize) % 4) + 1; (w, self.next(w)) diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 9c218194..1a8b406f 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -5,10 +5,6 @@ use std::{ num::NonZeroUsize, }; -use rand_xoshiro::{ - rand_core::{RngCore, SeedableRng}, - Xoshiro128StarStar, -}; use starlight::{ awi, awint_dag::EvalError, @@ -113,13 +109,20 @@ impl Mem { } } - /// Calls `next` with a random integer in 1..5, returning a tuple of the + /// Calls `next` with a random integer in 1..=4, returning a tuple of the /// width chosen and the Ptr to what `next` returned. pub fn next4(&mut self) -> (usize, P0) { let w = ((self.rng.next_u8() as usize) % 4) + 1; (w, self.next(w)) } + /// Calls `next` with a random integer in 1..=9, returning a tuple of the + /// width chosen and the Ptr to what `next` returned. + pub fn next9(&mut self) -> (usize, P0) { + let w = ((self.rng.next_u8() as usize) % 9) + 1; + (w, self.next(w)) + } + pub fn next_usize(&mut self, cap: usize) -> P0 { self.next_capped(usize::BITS as usize, cap) } @@ -177,7 +180,7 @@ impl Mem { } } -fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { +fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let next_op = rng.next_u32() % 29; match next_op { // Lut, StaticLut @@ -194,7 +197,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Get, StaticGet 1 => { - let (bits_w, bits) = m.next4(); + let (bits_w, bits) = m.next9(); let inx = m.next_usize(bits_w); let out = m.next(1); let bits_a = m.get_awi(bits); @@ -208,7 +211,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Set, StaticSet 2 => { - let (bits_w, bits) = m.next4(); + let (bits_w, bits) = m.next9(); let inx = m.next_usize(bits_w); let bit = m.next(1); let inx_a = m.get_awi(inx); @@ -224,9 +227,9 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // FieldBit 3 => { - let (lhs_w, lhs) = m.next4(); + let (lhs_w, lhs) = m.next9(); let to = m.next_usize(lhs_w); - let (rhs_w, rhs) = m.next4(); + let (rhs_w, rhs) = m.next9(); let from = m.next_usize(rhs_w); let to_a = m.get_awi(to); let rhs_a = m.get_awi(rhs); @@ -243,8 +246,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // ZeroResize 4 => { - let lhs = m.next4().1; - let rhs = m.next4().1; + let lhs = m.next9().1; + let rhs = m.next9().1; let rhs_a = m.get_awi(rhs); m.get_mut_awi(lhs).zero_resize_(&rhs_a); let rhs_b = m.get_dag(rhs); @@ -252,8 +255,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // SignResize 5 => { - let lhs = m.next4().1; - let rhs = m.next4().1; + let lhs = m.next9().1; + let rhs = m.next9().1; let rhs_a = m.get_awi(rhs); m.get_mut_awi(lhs).sign_resize_(&rhs_a); let rhs_b = m.get_dag(rhs); @@ -261,17 +264,17 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Not 6 => { - let x = m.next4().1; + let x = m.next9().1; m.get_mut_awi(x).not_(); m.get_mut_dag(x).not_(); } // Or, And, Xor 7 => { - let (lhs_w, lhs) = m.next4(); + let (lhs_w, lhs) = m.next9(); let rhs = m.next(lhs_w); let rhs_a = m.get_awi(rhs); let rhs_b = m.get_dag(rhs); - match rng.next_u32() % 3 { + match rng.index(3).unwrap() { 0 => { m.get_mut_awi(lhs).or_(&rhs_a).unwrap(); m.get_mut_dag(lhs).or_(&rhs_b).unwrap(); @@ -289,14 +292,14 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Inc, IncCout, Dec, DecCout 8 => { - let x = m.next4().1; + let x = m.next9().1; let cin = m.next(1); let cout = m.next(1); let cin_a = m.get_awi(cin); let cin_b = m.get_dag(cin); let out_a; let out_b; - if (rng.next_u32() & 1) == 0 { + if rng.next_bool() { out_a = m.get_mut_awi(x).inc_(cin_a.to_bool()); out_b = m.get_mut_dag(x).inc_(cin_b.to_bool()); } else { @@ -309,7 +312,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { // CinSum, UnsignedOverflow, SignedOverflow 9 => { let cin = m.next(1); - let (lhs_w, lhs) = m.next4(); + let (lhs_w, lhs) = m.next9(); let rhs = m.next(lhs_w); let out = m.next(lhs_w); let unsigned = m.next(1); @@ -337,9 +340,9 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Lsb, Msb 10 => { - let x = m.next4().1; + let x = m.next9().1; let out = m.next(1); - if (rng.next_u32() & 1) == 0 { + if rng.next_bool() { let a = m.get_awi(x).lsb(); m.get_mut_awi(out).bool_(a); let b = m.get_dag(x).lsb(); @@ -353,8 +356,8 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { } // Neg, Abs 11 => { - let x = m.next4().1; - if (rng.next_u32() & 1) == 0 { + let x = m.next9().1; + if rng.next_bool() { let neg = m.next(1); let a = m.get_awi(neg).to_bool(); m.get_mut_awi(x).neg_(a); @@ -759,7 +762,7 @@ fn num_dag_duo(rng: &mut Xoshiro128StarStar, m: &mut Mem) { #[test] fn fuzz_lower() { - let mut rng = Xoshiro128StarStar::seed_from_u64(0); + let mut rng = StarRng::new(0); let mut m = Mem::new(); for _ in 0..N.1 { From 64c83f44bb4cf51f0d4b5adbe670f9bb9d3fa907 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:27:58 -0600 Subject: [PATCH 087/119] Update fuzz_lower.rs --- testcrate/tests/fuzz_lower.rs | 41 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 1a8b406f..78bd6490 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -370,10 +370,9 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Funnel 12 => { - let w = 1 << (((m.rng.next_u32() as usize) % 2) + 1); - let lhs = m.next(w); - let rhs = m.next(w * 2); - let s = m.next(w.trailing_zeros() as usize); + let (w, s) = m.next4(); + let lhs = m.next(1 << w); + let rhs = m.next(2 << w); let a = m.get_awi(rhs); let a_s = m.get_awi(s); m.get_mut_awi(lhs).funnel_(&a, &a_s).unwrap(); @@ -383,8 +382,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // FieldWidth 13 => { - let (w0, lhs) = m.next4(); - let (w1, rhs) = m.next4(); + let (w0, lhs) = m.next9(); + let (w1, rhs) = m.next9(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let rhs_a = m.get_awi(rhs); @@ -400,8 +399,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // FieldFrom 14 => { - let (w0, lhs) = m.next4(); - let (w1, rhs) = m.next4(); + let (w0, lhs) = m.next9(); + let (w1, rhs) = m.next9(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let from = m.next_usize(1 + w1 - m.get_awi(width).to_usize()); @@ -420,11 +419,11 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Shl, Lshr, Ashr, Rotl, Rotr 15 => { - let (w, x) = m.next4(); + let (w, x) = m.next9(); let s = m.next_usize(w); let s_a = m.get_awi(s); let s_b = m.get_dag(s); - match rng.next_u32() % 5 { + match rng.index(5).unwrap() { 0 => { m.get_mut_awi(x).shl_(s_a.to_usize()).unwrap(); m.get_mut_dag(x).shl_(s_b.to_usize()).unwrap(); @@ -450,8 +449,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // FieldTo 16 => { - let (w0, lhs) = m.next4(); - let (w1, rhs) = m.next4(); + let (w0, lhs) = m.next9(); + let (w1, rhs) = m.next9(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let to = m.next_usize(1 + w0 - m.get_awi(width).to_usize()); @@ -470,11 +469,11 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Add, Sub, Rsb 17 => { - let (w, lhs) = m.next4(); + let (w, lhs) = m.next9(); let rhs = m.next(w); let rhs_a = m.get_awi(rhs); let rhs_b = m.get_dag(rhs); - match rng.next_u32() % 3 { + match rng.index(3).unwrap() { 0 => { m.get_mut_awi(lhs).add_(&rhs_a).unwrap(); m.get_mut_dag(lhs).add_(&rhs_b).unwrap(); @@ -492,8 +491,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Field 18 => { - let (w0, lhs) = m.next4(); - let (w1, rhs) = m.next4(); + let (w0, lhs) = m.next9(); + let (w1, rhs) = m.next9(); let min_w = min(w0, w1); let width = m.next_usize(min_w + 1); let to = m.next_usize(1 + w0 - m.get_awi(width).to_usize()); @@ -526,20 +525,20 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Rev 19 => { - let x = m.next4().1; + let x = m.next9().1; m.get_mut_awi(x).rev_(); m.get_mut_dag(x).rev_(); } // Eq, Ne, Ult, Ule, Ilt, Ile 20 => { - let (w, lhs) = m.next4(); + let (w, lhs) = m.next9(); let rhs = m.next(w); let lhs_a = m.get_awi(lhs); let lhs_b = m.get_dag(lhs); let rhs_a = m.get_awi(rhs); let rhs_b = m.get_dag(rhs); let out = m.next(1); - match rng.next_u32() % 6 { + match rng.index(6).unwrap() { 0 => { m.get_mut_awi(out).bool_(lhs_a.const_eq(&rhs_a).unwrap()); m.get_mut_dag(out).bool_(lhs_b.const_eq(&rhs_b).unwrap()); @@ -569,11 +568,11 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // IsZero, IsUmax, IsImax, IsImin, IsUone 21 => { - let x = m.next4().1; + let x = m.next9().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); let out = m.next(1); - match rng.next_u32() % 5 { + match rng.index(5).unwrap() { 0 => { m.get_mut_awi(out).bool_(x_a.is_zero()); m.get_mut_dag(out).bool_(x_b.is_zero()); From 6ca761761903c67cf7b91434b173117c94c77384 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:32:36 -0600 Subject: [PATCH 088/119] Update fuzz_lower.rs --- testcrate/tests/fuzz_lower.rs | 42 ++++++++++++++--------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 78bd6490..daf478d2 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -602,7 +602,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let x_a = m.get_awi(x); let x_b = m.get_dag(x); let out = m.next_usize(usize::MAX); - match rng.next_u32() % 4 { + match rng.index(4).unwrap() { 0 => { m.get_mut_awi(out).usize_(x_a.count_ones()); m.get_mut_dag(out).usize_(x_b.count_ones()); @@ -656,22 +656,18 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let rhs_a = m.get_awi(rhs); let mut lhs_b = m.get_dag(lhs); let rhs_b = m.get_dag(rhs); - match rng.next_u32() % 2 { - 0 => { - m.get_mut_awi(out).bool_(lhs_a.zero_resize_(&rhs_a)); - m.get_mut_dag(out).bool_(lhs_b.zero_resize_(&rhs_b)); - } - 1 => { - m.get_mut_awi(out).bool_(lhs_a.sign_resize_(&rhs_a)); - m.get_mut_dag(out).bool_(lhs_b.sign_resize_(&rhs_b)); - } - _ => unreachable!(), + if rng.next_bool() { + m.get_mut_awi(out).bool_(lhs_a.zero_resize_(&rhs_a)); + m.get_mut_dag(out).bool_(lhs_b.zero_resize_(&rhs_b)); + } else { + m.get_mut_awi(out).bool_(lhs_a.sign_resize_(&rhs_a)); + m.get_mut_dag(out).bool_(lhs_b.sign_resize_(&rhs_b)); } } // ArbMulAdd 26 => { - let (w, lhs) = m.next4(); - match rng.next_u32() % 3 { + let (w, lhs) = m.next9(); + match rng.index(3).unwrap() { 0 => { let rhs = m.next(w); let out = m.next(w); @@ -707,7 +703,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Mux 27 => { - let (w, lhs) = m.next4(); + let (w, lhs) = m.next9(); let rhs = m.next(w); let b = m.next(1); let rhs_a = m.get_awi(rhs); @@ -719,7 +715,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // UQuo, URem, IQuo, IRem 28 => { - let (w, duo) = m.next4(); + let (w, duo) = m.next9(); let div = m.next(w); let quo = m.next(w); let rem = m.next(w); @@ -739,16 +735,12 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let mut div_b = m.get_dag(div); let mut quo_b = m.get_dag(quo); let mut rem_b = m.get_dag(rem); - match rng.next_u32() % 2 { - 0 => { - awi::Bits::udivide(&mut quo_a, &mut rem_a, &duo_a, &div_a).unwrap(); - dag::Bits::udivide(&mut quo_b, &mut rem_b, &duo_b, &div_b).unwrap(); - } - 1 => { - awi::Bits::idivide(&mut quo_a, &mut rem_a, &mut duo_a, &mut div_a).unwrap(); - dag::Bits::idivide(&mut quo_b, &mut rem_b, &mut duo_b, &mut div_b).unwrap(); - } - _ => unreachable!(), + if rng.next_bool() { + awi::Bits::udivide(&mut quo_a, &mut rem_a, &duo_a, &div_a).unwrap(); + dag::Bits::udivide(&mut quo_b, &mut rem_b, &duo_b, &div_b).unwrap(); + } else { + awi::Bits::idivide(&mut quo_a, &mut rem_a, &mut duo_a, &mut div_a).unwrap(); + dag::Bits::idivide(&mut quo_b, &mut rem_b, &mut duo_b, &mut div_b).unwrap(); } m.get_mut_awi(out0).copy_(&quo_a).unwrap(); m.get_mut_awi(out1).copy_(&rem_a).unwrap(); From 202a507646f10932da81e0650f02c08c9ed00df3 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:36:09 -0600 Subject: [PATCH 089/119] Update fuzz_lower.rs --- testcrate/tests/fuzz_lower.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index daf478d2..8d9ce14e 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -598,7 +598,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // CountOnes, Lz, Tz, Sig 22 => { - let x = m.next4().1; + let x = m.next9().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); let out = m.next_usize(usize::MAX); @@ -637,8 +637,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // Resize 24 => { - let lhs = m.next4().1; - let rhs = m.next4().1; + let lhs = m.next9().1; + let rhs = m.next9().1; let b = m.next(1); let rhs_a = m.get_awi(rhs); let b_a = m.get_awi(b); @@ -649,8 +649,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } // ZeroResizeOverflow, SignResizeOverflow 25 => { - let lhs = m.next4().1; - let rhs = m.next4().1; + let lhs = m.next9().1; + let rhs = m.next9().1; let out = m.next(1); let mut lhs_a = m.get_awi(lhs); let rhs_a = m.get_awi(rhs); @@ -679,8 +679,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(out).mul_add_(&lhs_b, &rhs_b).unwrap(); } 1 => { - let rhs = m.next4().1; - let out = m.next4().1; + let rhs = m.next9().1; + let out = m.next9().1; let lhs_a = m.get_awi(lhs); let rhs_a = m.get_awi(rhs); let lhs_b = m.get_dag(lhs); @@ -689,8 +689,8 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(out).arb_umul_add_(&lhs_b, &rhs_b); } 2 => { - let rhs = m.next4().1; - let out = m.next4().1; + let rhs = m.next9().1; + let out = m.next9().1; let mut lhs_a = m.get_awi(lhs); let mut rhs_a = m.get_awi(rhs); let mut lhs_b = m.get_dag(lhs); From abdd8b084649c0a670d4364d6408da9b555e289d Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 15:40:52 -0600 Subject: [PATCH 090/119] improve dynamic eval --- starlight/src/ensemble/value.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 6e7ef68c..38c0070f 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -447,7 +447,7 @@ impl Ensemble { } } } - // we need to reduce first, reduce the LUT based on fixed and known bits + // reduce the LUT based on fixed and known bits for i in (0..len).rev() { if fixed.get(i).unwrap() && (!unknown.get(i).unwrap()) { let bit = inp_val.get(i).unwrap(); @@ -494,7 +494,6 @@ impl Ensemble { } } } - // TODO better early `Unknown` change detection // if the LUT is all fixed and known ones or zeros, we can know that any unfixed // or unknown changes will be unable to affect the @@ -517,6 +516,16 @@ impl Ensemble { } } + if fixed.is_umax() && lut_fixed.is_umax() { + // if we did not evaluate to a value earlier, then we know our value is unknown + self.evaluator.insert(Eval::Change(Change { + depth, + p_equiv, + value: Value::Unknown, + })); + return vec![]; + } + for i in (0..inp.len()).rev() { if (!fixed.get(i).unwrap()) || unknown.get(i).unwrap() { res.push(RequestLNode { From c294703007293d2199587fcf37860bd4460c1fb3 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 16:38:24 -0600 Subject: [PATCH 091/119] Update fuzz_elementary.rs --- testcrate/tests/fuzz_elementary.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 2526a02e..8af57b01 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -86,10 +86,10 @@ impl Mem { } } - /// Calls `next` with a random integer in 1..=4, returning a tuple of the + /// Calls `next` with a random integer in 1..=6, returning a tuple of the /// width chosen and the Ptr to what `next` returned. - pub fn next4(&mut self) -> (usize, P0) { - let w = ((self.rng.next_u8() as usize) % 4) + 1; + pub fn next6(&mut self) -> (usize, P0) { + let w = ((self.rng.next_u8() as usize) % 6) + 1; (w, self.next(w)) } @@ -121,13 +121,12 @@ impl Mem { } fn operation(rng: &mut StarRng, m: &mut Mem) { - let next_op = rng.next_u8() % 3; - match next_op { + match rng.index(3).unwrap() { // Copy 0 => { // doesn't actually do anything on the DAG side, but we use it to get parallel // things in the fuzzing - let (w, from) = m.next4(); + let (w, from) = m.next6(); let to = m.next(w); if to != from { let (to, from) = m.a.get2_mut(to, from).unwrap(); @@ -137,20 +136,20 @@ fn operation(rng: &mut StarRng, m: &mut Mem) { } // Get-Set 1 => { - let (w0, from) = m.next4(); - let (w1, to) = m.next4(); - let usize_inx0 = (rng.next_u32() as usize) % w0; - let usize_inx1 = (rng.next_u32() as usize) % w1; + let (w0, from) = m.next6(); + let (w1, to) = m.next6(); + let usize_inx0 = rng.index(w0).unwrap(); + let usize_inx1 = rng.index(w1).unwrap(); let b = m.a[from].awi.get(usize_inx0).unwrap(); m.a[to].awi.set(usize_inx1, b).unwrap(); let b = m.a[from].dag.get(usize_inx0).unwrap(); m.a[to].dag.set(usize_inx1, b).unwrap(); } - // Lut + // Lut and dynamic luts 2 => { - let (out_w, out) = m.next4(); - let (inx_w, inx) = m.next4(); - let lut = m.next(out_w * (1 << inx_w)); + let out = m.next(1); + let (inx_w, inx) = m.next6(); + let lut = m.next(1 << inx_w); let lut_a = m.get(lut); let inx_a = m.get(inx); m.a[out].awi.lut_(&lut_a.awi, &inx_a.awi).unwrap(); From 15571cc261f1e2f03d749001ade4fd04b263a7c6 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 5 Jan 2024 21:09:00 -0600 Subject: [PATCH 092/119] finish improving the fuzzers --- testcrate/tests/fuzz_elementary.rs | 67 ++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 8af57b01..2d786307 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroUsize; +use std::{cmp::min, num::NonZeroUsize}; use starlight::{ awint::{awi, awint_dag::EvalError, dag}, @@ -86,6 +86,28 @@ impl Mem { } } + /// Randomly creates a new pair or gets an existing one under the `cap` + pub fn next_capped(&mut self, w: usize, cap: usize) -> P0 { + if self.rng.out_of_4(3) && (!self.v[w].is_empty()) { + let p = self.rng.index_slice(&self.v[w]).unwrap(); + if self.get(*p).awi.to_usize() < cap { + return *p + } + } + let nzbw = NonZeroUsize::new(w).unwrap(); + let lazy = LazyAwi::opaque(nzbw); + let mut lit = awi::Awi::zero(nzbw); + lit.usize_(self.rng.index(cap).unwrap()); + let p = self.a.insert(Pair { + awi: lit.clone(), + dag: dag::Awi::from(lazy.as_ref()), + eval: None, + }); + self.roots.push((lazy, lit)); + self.v[w].push(p); + p + } + /// Calls `next` with a random integer in 1..=6, returning a tuple of the /// width chosen and the Ptr to what `next` returned. pub fn next6(&mut self) -> (usize, P0) { @@ -93,6 +115,10 @@ impl Mem { (w, self.next(w)) } + pub fn next_usize(&mut self, cap: usize) -> P0 { + self.next_capped(usize::BITS as usize, cap) + } + pub fn get(&self, inx: P0) -> Pair { self.a[inx].clone() } @@ -121,7 +147,7 @@ impl Mem { } fn operation(rng: &mut StarRng, m: &mut Mem) { - match rng.index(3).unwrap() { + match rng.index(4).unwrap() { // Copy 0 => { // doesn't actually do anything on the DAG side, but we use it to get parallel @@ -145,8 +171,43 @@ fn operation(rng: &mut StarRng, m: &mut Mem) { let b = m.a[from].dag.get(usize_inx0).unwrap(); m.a[to].dag.set(usize_inx1, b).unwrap(); } - // Lut and dynamic luts + // static fielding needed for interacting with the large tables 2 => { + let w0 = 4 << rng.index(4).unwrap(); + let w1 = 4 << rng.index(4).unwrap(); + let min_w = min(w0, w1); + let width = m.next_usize(min_w + 1); + let from = m.next_usize(1 + w0 - m.get(width).awi.to_usize()); + let to = m.next_usize(1 + w1 - m.get(width).awi.to_usize()); + let rhs = m.next(w0); + let lhs = m.next(w1); + + let from_a = m.get(from); + let to_a = m.get(to); + let width_a = m.get(width); + let rhs_a = m.get(rhs); + m.a[lhs] + .awi + .field( + to_a.awi.to_usize(), + &rhs_a.awi, + from_a.awi.to_usize(), + width_a.awi.to_usize(), + ) + .unwrap(); + // use the `awi` versions for the shift information + m.a[lhs] + .dag + .field( + to_a.awi.to_usize(), + &rhs_a.dag, + from_a.awi.to_usize(), + width_a.awi.to_usize(), + ) + .unwrap(); + } + // Lut and dynamic luts + 3 => { let out = m.next(1); let (inx_w, inx) = m.next6(); let lut = m.next(1 << inx_w); From 7845192f1acba6cd7a2141cdc9aed292377c47a0 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 6 Jan 2024 00:02:30 -0600 Subject: [PATCH 093/119] more ideas --- starlight/src/route/cedge.rs | 38 ++++++++++++++++++++++++++++++---- starlight/src/route/channel.rs | 5 ++--- testcrate/tests/fuzz_lower.rs | 3 +++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index d9f11369..c8406cda 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -6,7 +6,7 @@ use awint::{ use crate::{ awint_dag::smallvec::SmallVec, ensemble, - ensemble::{Ensemble, LNodeKind}, + ensemble::{DynamicValue, Ensemble, LNodeKind}, route::{channel::Referent, Channeler, PBack}, triple_arena::ptr_struct, Epoch, @@ -30,12 +30,21 @@ pub enum Behavior { /// Nothing can happen between nodes, used for connecting top level nodes /// that have no connection to each other Noop, - /// Routes the bit from `source` to `sink` - RouteBit, + StaticLut(Awi), + + // `DynamicLut`s can go in one of two ways: the table bits directly connect with configurable + // bits and thus it can behave as an `ArbitraryLut`, or the inx bits directly connect with + // configurable bits and thus can behave as `SelectorLut`s. Currently we will trigger + // lowerings when a LUT doesn't fit into any category and lower down into just `StaticLut`s if + // necessary. /// Can behave as an arbitrary lookup table outputting a bit and taking the /// input bits. ArbitraryLut(usize), + /// Can behave as an arbitrary selector that multiplexes one of the input + /// bits to the output + SelectorLut(usize), + /// Bulk behavior Bulk(BulkBehavior), } @@ -161,7 +170,28 @@ impl Channeler { instruction: Instruction::new(), }); } - LNodeKind::DynamicLut(inp, lut) => todo!(), + LNodeKind::DynamicLut(inp, lut) => { + let mut v: SmallVec<[PBack; 8]> = + smallvec![translate(ensemble, &channeler, lnode.p_self)]; + for input in inp { + v.push(translate(ensemble, &channeler, *input)); + } + for lut_bit in lut.iter() { + match lut_bit { + DynamicValue::Unknown => todo!(), + DynamicValue::Const(_) => todo!(), + DynamicValue::Dynam(_) => todo!(), + } + //v.push(translate(ensemble, &channeler, *input)); + } + // TODO detect config bit effects + + //SelectorLut + /*channeler.make_cedge(&v, Programmability { + behavior: Behavior::ArbitraryLut(awi.clone()), + instruction: Instruction::new(), + });*/ + } } } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index c9363809..18e2f2f2 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -195,13 +195,12 @@ impl Channeler { for p_cedge in self.cedges.ptrs() { let cedge = self.cedges.get(p_cedge).unwrap(); let ok = match &cedge.programmability().behavior { - Behavior::Noop | Behavior::RouteBit | Behavior::Bulk(_) => { - cedge.incidences.len() == 2 - } + Behavior::Noop | Behavior::Bulk(_) => cedge.incidences.len() == 2, Behavior::StaticLut(lut) => { lut.bw().is_power_of_two() && ((lut.bw().trailing_zeros() as usize + 1) == cedge.incidences.len()) } + Behavior::SelectorLut(_) => todo!(), Behavior::ArbitraryLut(input_len) => *input_len == cedge.incidences.len(), }; if !ok { diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 8d9ce14e..c1fa2c2d 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -180,6 +180,9 @@ impl Mem { } } +// TODO I don't think I have fully tested the no-op cases. I should also have +// some kind of way to test coverage. + fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let next_op = rng.next_u32() % 29; match next_op { From 9a46ca29bb05c8ce0d9ce2d7b1c4baa8eca7ba57 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sun, 7 Jan 2024 22:33:17 -0600 Subject: [PATCH 094/119] introduce `ConstUnknown` --- starlight/src/awi_structs/epoch.rs | 8 +- starlight/src/awi_structs/eval_awi.rs | 2 +- starlight/src/awi_structs/lazy_awi.rs | 4 +- starlight/src/ensemble/lnode.rs | 8 +- starlight/src/ensemble/optimize.rs | 173 ++++++++++++++++++-------- starlight/src/ensemble/rnode.rs | 19 ++- starlight/src/ensemble/state.rs | 2 +- starlight/src/ensemble/together.rs | 12 +- starlight/src/ensemble/value.rs | 133 ++++++++++---------- starlight/src/route/cedge.rs | 2 +- 10 files changed, 226 insertions(+), 137 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 89efed56..c322e280 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -21,7 +21,10 @@ use awint::{ bw, dag, }; -use crate::{ensemble::Ensemble, EvalAwi}; +use crate::{ + ensemble::{Ensemble, Value}, + EvalAwi, +}; /// A list of single bit `EvalAwi`s for assertions #[derive(Debug, Clone)] @@ -338,6 +341,9 @@ impl EpochShared { // Wait for all bits to be checked for falsity unknown = Some((p_external, p_state)); } + if (val == Value::ConstUnknown) && strict && unknown.is_none() { + unknown = Some((p_external, p_state)); + } if val.is_const() { // remove the assertion let mut epoch_data = self.epoch_data.borrow_mut(); diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index 1b55645d..6095a951 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -153,7 +153,7 @@ impl EvalAwi { pub fn from_state(p_state: PState) -> Self { if let Some(epoch) = get_current_epoch() { let mut lock = epoch.epoch_data.borrow_mut(); - match lock.ensemble.make_rnode_for_pstate(p_state, true) { + match lock.ensemble.make_rnode_for_pstate(p_state, true, true) { Some(p_external) => { lock.ensemble .stator diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index 305e6af0..e65e5c15 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -176,7 +176,7 @@ impl LazyAwi { .epoch_data .borrow_mut() .ensemble - .make_rnode_for_pstate(opaque.state(), false) + .make_rnode_for_pstate(opaque.state(), false, false) .unwrap(); Self { opaque, p_external } } @@ -347,7 +347,7 @@ impl LazyInlAwi { .epoch_data .borrow_mut() .ensemble - .make_rnode_for_pstate(opaque.state(), false) + .make_rnode_for_pstate(opaque.state(), false, false) .unwrap(); Self { opaque, p_external } } diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 08f3bc59..47befd7c 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -220,7 +220,7 @@ impl LNode { debug_assert!(lut.len().is_power_of_two()); debug_assert!(i < (lut.len().trailing_zeros() as usize)); let next_bw = lut.len() / 2; - let mut next_lut = vec![DynamicValue::Unknown; next_bw]; + let mut next_lut = vec![DynamicValue::ConstUnknown; next_bw]; let mut removed = Vec::with_capacity(next_bw); let w = 1 << i; let mut from = 0; @@ -278,9 +278,9 @@ impl LNode { let tmp0 = &lut[from + j]; let tmp1 = &lut[from + w + j]; match tmp0 { - DynamicValue::Unknown => return None, + DynamicValue::ConstUnknown => return None, DynamicValue::Const(b0) => match tmp1 { - DynamicValue::Unknown => return None, + DynamicValue::ConstUnknown => return None, DynamicValue::Const(b1) => { if *b0 != *b1 { return None @@ -289,7 +289,7 @@ impl LNode { DynamicValue::Dynam(_) => return None, }, DynamicValue::Dynam(p0) => match tmp1 { - DynamicValue::Unknown => return None, + DynamicValue::ConstUnknown => return None, DynamicValue::Const(_) => return None, DynamicValue::Dynam(p1) => { if !backrefs.in_same_set(*p0, *p1).unwrap() { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 537d5235..d654c020 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -98,9 +98,10 @@ impl Ensemble { LNodeKind::Copy(inp) => { // wire propogation let input_equiv = self.backrefs.get_val_mut(*inp).unwrap(); - if let Value::Const(val) = input_equiv.val { + let val = input_equiv.val; + if val.is_const() { let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); - equiv.val = Value::Const(val); + equiv.val = val; self.optimizer .insert(Optimization::ConstifyEquiv(equiv.p_self_equiv)); true @@ -114,16 +115,22 @@ impl Ensemble { let mut lut = original_lut.clone(); // acquire LUT inputs, for every constant input reduce the LUT let len = usize::from(u8::try_from(inp.len()).unwrap()); + let mut encountered_const_unknown = false; for i in (0..len).rev() { let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - // we will be removing the input, mark it to be investigated - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(p_inp).unwrap(); - inp.remove(i); - LNode::reduce_lut(&mut lut, i, val); + match equiv.val { + Value::ConstUnknown => encountered_const_unknown = true, + Value::Const(val) => { + // we will reducing the LUT and removing this input, mark it to be + // investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(p_inp).unwrap(); + inp.remove(i); + LNode::reduce_lut(&mut lut, i, val); + } + Value::Unknown | Value::Dynam(_) => (), } } @@ -183,30 +190,67 @@ impl Ensemble { // fix the `lut` to its new state, do this even if we are doing the constant // optimization *original_lut = lut; - true + return Ok(true) } else if (lut.bw() == 2) && lut.get(1).unwrap() { // the only `lut.bw() == 2` cases that survive independence removal is identity // and inversion. If it is identity, register this for forwarding lnode.kind = LNodeKind::Copy(inp[0]); self.optimizer .insert(Optimization::ForwardEquiv(lnode.p_self)); - false - } else { - *original_lut = lut; - false + return Ok(false) } + // only at the very end do we consider `ConstUnknown` inputs, because if we + // naively try to constify to `ConstUnknown` when the LUT does not reduce, we + // miss cases where other inputs can cause it to have known output or even const + // known output. We only set constify to `ConstUnknown` when all inputs are + // `ConstUnknown`, which we need to recheck here. The `ConstUnknown` inputs + // otherwise unfortunately need to be kept around for correct evaluation + // behavior. + if encountered_const_unknown { + let mut all_const_unknown = true; + let len = inp.len(); + for i in 0..len { + let p_inp = inp[i]; + let equiv = self.backrefs.get_val(p_inp).unwrap(); + match equiv.val { + Value::ConstUnknown => (), + Value::Const(_) | Value::Dynam(_) | Value::Unknown => { + all_const_unknown = false; + break + } + } + } + if all_const_unknown { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::ConstUnknown; + *original_lut = lut; + return Ok(true) + } + } + *original_lut = lut; + false } LNodeKind::DynamicLut(inp, ref mut lut) => { // acquire LUT table inputs, convert to constants for lut_bit in lut.iter_mut() { if let DynamicValue::Dynam(p) = lut_bit { let equiv = self.backrefs.get_val(*p).unwrap(); - if let Value::Const(val) = equiv.val { - // we will be removing the input, mark it to be investigated - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(*p).unwrap(); - *lut_bit = DynamicValue::Const(val); + match equiv.val { + Value::ConstUnknown => { + // we will be removing the input, mark it to be investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(*p).unwrap(); + *lut_bit = DynamicValue::ConstUnknown; + } + Value::Const(val) => { + // we will be removing the input, mark it to be investigated + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(*p).unwrap(); + *lut_bit = DynamicValue::Const(val); + } + Value::Unknown | Value::Dynam(_) => (), } } } @@ -215,22 +259,26 @@ impl Ensemble { for i in (0..len).rev() { let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - len -= 1; - // we will be removing the input, mark it to be investigated - self.optimizer - .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(p_inp).unwrap(); - inp.remove(i); - - let (tmp, removed) = LNode::reduce_dynamic_lut(lut, i, val); - *lut = tmp; - for remove in removed { - let equiv = self.backrefs.get_val(remove).unwrap(); + match equiv.val { + Value::ConstUnknown => (), + Value::Const(val) => { + len -= 1; + // we will be removing the input, mark it to be investigated self.optimizer .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); - self.backrefs.remove_key(remove).unwrap(); + self.backrefs.remove_key(p_inp).unwrap(); + inp.remove(i); + + let (tmp, removed) = LNode::reduce_dynamic_lut(lut, i, val); + *lut = tmp; + for remove in removed { + let equiv = self.backrefs.get_val(remove).unwrap(); + self.optimizer + .insert(Optimization::InvestigateUsed(equiv.p_self_equiv)); + self.backrefs.remove_key(remove).unwrap(); + } } + Value::Unknown | Value::Dynam(_) => (), } } @@ -309,15 +357,10 @@ impl Ensemble { if w.get() == 1 { let bit = lut[0]; match bit { - DynamicValue::Unknown => { - //let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); - //equiv.val = Value::Unknown; - // not sure if `DynamicValue` is something that should map to a stronger - // `Value::ConstUnknown` or `Value::unreachable` - return Err(EvalError::OtherStr( - "encountered a dynamic lookup table that has been reduced down to \ - `DynamicValue::Unknown`", - )); + DynamicValue::ConstUnknown => { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::ConstUnknown; + return Ok(true) } DynamicValue::Const(b) => { let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); @@ -333,19 +376,29 @@ impl Ensemble { } } - // check if all const - let mut all_const = true; + let mut all_const_unknown = true; + let mut all_const_known = true; for lut_bit in lut.iter() { match lut_bit { - DynamicValue::Const(_) => (), - DynamicValue::Unknown | DynamicValue::Dynam(_) => { - all_const = false; - break + DynamicValue::ConstUnknown => { + all_const_known = false; + } + DynamicValue::Const(_) => { + all_const_unknown = false; + } + DynamicValue::Dynam(_) => { + all_const_unknown = false; + all_const_known = false; } } } - if all_const { + if all_const_unknown { + let equiv = self.backrefs.get_val_mut(lnode.p_self).unwrap(); + equiv.val = Value::ConstUnknown; + return Ok(true) + } + if all_const_known { let mut awi_lut = Awi::zero(w); for (i, lut_bit) in lut.iter().enumerate() { if let DynamicValue::Const(b) = lut_bit { @@ -373,8 +426,8 @@ impl Ensemble { let p_self = tnode.p_self; let p_driver = tnode.p_driver; let equiv = self.backrefs.get_val(p_driver).unwrap(); - if let Value::Const(val) = equiv.val { - self.backrefs.get_val_mut(p_self).unwrap().val = Value::Const(val); + if equiv.val.is_const() { + self.backrefs.get_val_mut(p_self).unwrap().val = equiv.val; true } else { false @@ -388,19 +441,22 @@ impl Ensemble { pub fn preinvestigate_equiv(&mut self, p_equiv: PBack) -> Result<(), EvalError> { let mut non_self_rc = 0usize; let equiv = self.backrefs.get_val(p_equiv).unwrap(); - let mut is_const = matches!(equiv.val, Value::Const(_)); + let mut is_const = equiv.val.is_const(); + let mut possible_drivers = false; let mut adv = self.backrefs.advancer_surject(p_equiv); while let Some(p_back) = adv.advance(&self.backrefs) { let referent = *self.backrefs.get_key(p_back).unwrap(); match referent { Referent::ThisEquiv => (), Referent::ThisTNode(p_tnode) => { + possible_drivers = true; // avoid checking more if it was already determined to be constant if !is_const && self.const_eval_tnode(p_tnode) { is_const = true; } } Referent::ThisLNode(p_lnode) => { + possible_drivers = true; // avoid checking more if it was already determined to be constant if !is_const && self.const_eval_lnode(p_lnode)? { is_const = true; @@ -427,12 +483,21 @@ impl Ensemble { // TODO check for const through loop, but there should be a // parameter to enable } - Referent::ThisRNode(_) => non_self_rc += 1, + Referent::ThisRNode(p_rnode) => { + let rnode = self.notary.rnodes().get(p_rnode).unwrap().1; + if !rnode.read_only { + possible_drivers = true; + } + non_self_rc += 1; + } } } + if non_self_rc == 0 { self.optimizer.insert(Optimization::RemoveEquiv(p_equiv)); - } else if is_const { + } else if is_const || (!possible_drivers) { + // if an equivalence has no possible `TNode`, `LNode`, or `RNode` drivers, the + // value is converted to its const version self.optimizer.insert(Optimization::ConstifyEquiv(p_equiv)); } else { self.optimizer diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index a26ced1a..d104a462 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -25,6 +25,7 @@ ptr_struct!( pub struct RNode { pub nzbw: NonZeroUsize, pub bits: SmallVec<[Option; 1]>, + pub read_only: bool, pub associated_state: Option, pub lower_before_pruning: bool, } @@ -41,11 +42,13 @@ impl Recast for RNode { impl RNode { pub fn new( nzbw: NonZeroUsize, + read_only: bool, associated_state: Option, lower_before_pruning: bool, ) -> Self { Self { nzbw, + read_only, bits: smallvec![], associated_state, lower_before_pruning, @@ -117,12 +120,16 @@ impl Ensemble { pub fn make_rnode_for_pstate( &mut self, p_state: PState, + read_only: bool, lower_before_pruning: bool, ) -> Option { let nzbw = self.stator.states[p_state].nzbw; - let (_, p_external) = - self.notary - .insert_rnode(RNode::new(nzbw, Some(p_state), lower_before_pruning)); + let (_, p_external) = self.notary.insert_rnode(RNode::new( + nzbw, + read_only, + Some(p_state), + lower_before_pruning, + )); Some(p_external) } @@ -210,7 +217,11 @@ impl Ensemble { if let Some(p_back) = p_back { let bit = common_value.get(bit_i).unwrap(); let bit = if make_const { - Value::Const(bit.unwrap()) + if let Some(bit) = bit { + Value::Const(bit) + } else { + Value::ConstUnknown + } } else if let Some(bit) = bit { Value::Dynam(bit) } else { diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 4b8953a6..ff1517ff 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -483,7 +483,7 @@ fn lower_elementary_to_lnodes_intermediate( if let Some(p_back) = lut_bits[(i * out_bw) + bit_i] { p_lut_bits.push(DynamicValue::Dynam(p_back)); } else { - p_lut_bits.push(DynamicValue::Unknown); + p_lut_bits.push(DynamicValue::ConstUnknown); } } let p_equiv0 = this diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 5e6355c6..9b1fcf0e 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -518,7 +518,13 @@ impl Ensemble { self.backrefs.insert_with(|p_self_equiv| { ( Referent::ThisEquiv, - Equiv::new(p_self_equiv, Value::from_dag_lit(lit)), + Equiv::new(p_self_equiv, { + if let Some(b) = lit { + Value::Const(b) + } else { + Value::Unknown + } + }), ) }) } @@ -669,9 +675,9 @@ impl Ensemble { equiv0.change_visit = equiv1.change_visit; equiv0.val = equiv1.val; } else if equiv0.val != equiv1.val { - if equiv0.val.is_unknown() { + if !equiv0.val.is_known() { equiv0.val = equiv1.val; - } else if equiv1.val.is_unknown() { + } else if !equiv1.val.is_known() { equiv1.val = equiv0.val; } else { return Err(EvalError::OtherString(format!( diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 38c0070f..132c0c38 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -87,6 +87,8 @@ impl<'a> CommonValue<'a> { /// The value of a multistate boolean #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { + /// The value is permanently unknown + ConstUnknown, /// The value is simply unknown, or a circuit is undriven Unknown, /// The value is a known constant that is guaranteed to not change under any @@ -97,35 +99,36 @@ pub enum Value { } impl Value { - pub fn from_dag_lit(lit: Option) -> Self { - if let Some(lit) = lit { - Value::Const(lit) - } else { - Value::Unknown - } - } - pub fn known_value(self) -> Option { match self { + Value::ConstUnknown => None, Value::Unknown => None, Value::Const(b) => Some(b), Value::Dynam(b) => Some(b), } } - pub fn is_const(self) -> bool { - matches!(self, Value::Const(_)) - } - pub fn is_known(self) -> bool { match self { - Value::Unknown => false, + Value::ConstUnknown | Value::Unknown => false, Value::Const(_) | Value::Dynam(_) => true, } } - pub fn is_unknown(self) -> bool { - !self.is_known() + pub fn is_const(self) -> bool { + match self { + Value::Unknown | Value::Dynam(_) => false, + Value::ConstUnknown | Value::Const(_) => true, + } + } + + pub fn constified(self) -> Self { + match self { + Value::ConstUnknown => self, + Value::Unknown => Value::ConstUnknown, + Value::Const(_) => self, + Value::Dynam(b) => Value::Const(b), + } } } @@ -133,22 +136,12 @@ impl Value { #[derive(Debug, Clone, Copy)] pub enum DynamicValue { /// Corresponds with `Value::Unknown` - Unknown, + ConstUnknown, /// Corresponds with `Value::Const` Const(bool), Dynam(PBack), } -impl DynamicValue { - pub fn is_known(&self) -> bool { - match self { - DynamicValue::Unknown => false, - DynamicValue::Const(_) => true, - DynamicValue::Dynam(_) => true, - } - } -} - /* Consider a request front where we want to know if the output of a LUT is unable to change and thus that part of the front can be eliminated @@ -276,14 +269,8 @@ impl Ensemble { match &lnode.kind { LNodeKind::Copy(p_inp) => { let equiv = self.backrefs.get_val(*p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Const(val), - })); - } else if equiv.change_visit == self.evaluator.change_visit_gen() { - // fixed + if equiv.val.is_const() || (equiv.change_visit == self.evaluator.change_visit_gen()) + { self.evaluator.insert(Eval::Change(Change { depth, p_equiv, @@ -310,16 +297,27 @@ impl Ensemble { for i in 0..len { let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - fixed.set(i, true).unwrap(); - inp_val.set(i, val).unwrap(); - } else if equiv.change_visit == self.evaluator.change_visit_gen() { - fixed.set(i, true).unwrap(); - if let Some(val) = equiv.val.known_value() { - inp_val.set(i, val).unwrap() - } else { + match equiv.val { + Value::ConstUnknown => { + fixed.set(i, true).unwrap(); unknown.set(i, true).unwrap(); } + Value::Const(val) => { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap(); + } + Value::Unknown => { + if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + unknown.set(i, true).unwrap(); + } + } + Value::Dynam(val) => { + if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap() + } + } } } let mut lut = original_lut.clone(); @@ -395,16 +393,27 @@ impl Ensemble { for i in 0..len { let p_inp = inp[i]; let equiv = self.backrefs.get_val(p_inp).unwrap(); - if let Value::Const(val) = equiv.val { - fixed.set(i, true).unwrap(); - inp_val.set(i, val).unwrap(); - } else if equiv.change_visit == self.evaluator.change_visit_gen() { - fixed.set(i, true).unwrap(); - if let Some(val) = equiv.val.known_value() { - inp_val.set(i, val).unwrap() - } else { + match equiv.val { + Value::ConstUnknown => { + fixed.set(i, true).unwrap(); unknown.set(i, true).unwrap(); } + Value::Const(val) => { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap(); + } + Value::Unknown => { + if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + unknown.set(i, true).unwrap(); + } + } + Value::Dynam(val) => { + if equiv.change_visit == self.evaluator.change_visit_gen() { + fixed.set(i, true).unwrap(); + inp_val.set(i, val).unwrap() + } + } } } let lut_w = NonZeroUsize::new(original_lut.len()).unwrap(); @@ -414,7 +423,7 @@ impl Ensemble { let mut lut_unknown = Awi::zero(lut_w); for (i, value) in original_lut.iter().enumerate() { match value { - DynamicValue::Unknown => { + DynamicValue::ConstUnknown => { lut_fixed.set(i, true).unwrap(); lut_unknown.set(i, true).unwrap(); } @@ -425,6 +434,10 @@ impl Ensemble { DynamicValue::Dynam(p) => { let equiv = self.backrefs.get_val(*p).unwrap(); match equiv.val { + Value::ConstUnknown => { + lut_fixed.set(i, true).unwrap(); + lut_unknown.set(i, true).unwrap(); + } Value::Unknown => { lut_unknown.set(i, true).unwrap(); if equiv.change_visit == self.evaluator.change_visit_gen() { @@ -480,9 +493,7 @@ impl Ensemble { } else { let lut_bit = reduced_lut[0]; match lut_bit { - DynamicValue::Unknown | DynamicValue::Const(_) => { - unreachable!() - } + DynamicValue::ConstUnknown | DynamicValue::Const(_) => (), DynamicValue::Dynam(p) => { res.push(RequestLNode { depth: depth - 1, @@ -559,15 +570,7 @@ impl Ensemble { let p_equiv = self.backrefs.get_val(tnode.p_self).unwrap().p_self_equiv; let p_driver = tnode.p_driver; let equiv = self.backrefs.get_val(p_driver).unwrap(); - if let Value::Const(val) = equiv.val { - self.evaluator.insert(Eval::Change(Change { - depth, - p_equiv, - value: Value::Const(val), - })); - None - } else if equiv.change_visit == self.evaluator.change_visit_gen() { - // fixed + if equiv.val.is_const() || (equiv.change_visit == self.evaluator.change_visit_gen()) { self.evaluator.insert(Eval::Change(Change { depth, p_equiv, @@ -813,9 +816,7 @@ impl Ensemble { fn eval_investigate0(&mut self, p_equiv: PBack, depth: i64) { let equiv = self.backrefs.get_val_mut(p_equiv).unwrap(); equiv.request_visit = self.evaluator.request_visit_gen(); - if matches!(equiv.val, Value::Const(_)) - || (equiv.change_visit == self.evaluator.change_visit_gen()) - { + if equiv.val.is_const() || (equiv.change_visit == self.evaluator.change_visit_gen()) { // no need to do anything return } diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index c8406cda..027e4931 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -178,7 +178,7 @@ impl Channeler { } for lut_bit in lut.iter() { match lut_bit { - DynamicValue::Unknown => todo!(), + DynamicValue::ConstUnknown => todo!(), DynamicValue::Const(_) => todo!(), DynamicValue::Dynam(_) => todo!(), } From cd5c71d5e6eaddeedac6f092fb2152152d9b5337 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 8 Jan 2024 19:47:12 -0600 Subject: [PATCH 095/119] begin working on routing again --- starlight/Cargo.toml | 6 +- starlight/src/awi_structs/epoch.rs | 4 + starlight/src/route/cedge.rs | 9 +- starlight/src/route/region_adv.rs | 2 +- starlight/src/route/router.rs | 141 +++++++++++++++++++++++++---- 5 files changed, 139 insertions(+), 23 deletions(-) diff --git a/starlight/Cargo.toml b/starlight/Cargo.toml index ea5ec370..a8d0b346 100644 --- a/starlight/Cargo.toml +++ b/starlight/Cargo.toml @@ -12,10 +12,10 @@ keywords = ["dag", "rtl", "hdl"] categories = ["algorithms"] [dependencies] -#awint = { path = "../../awint/awint", default-features = false, features = ["rand_support", "dag"] } -awint = { version = "0.15", default-features = false, features = ["rand_support", "dag"] } -rand_xoshiro = { version = "0.6", default-features = false } +awint = { path = "../../awint/awint", default-features = false, features = ["rand_support", "dag"] } +#awint = { version = "0.15", default-features = false, features = ["rand_support", "dag"] } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } +rand_xoshiro = { version = "0.6", default-features = false } [features] # note: "dag", "rand_support", and "std" are all turned on always diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index c322e280..9a17ab46 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -761,6 +761,10 @@ impl Epoch { self.shared().ensemble(f) } + pub fn clone_ensemble(&self) -> Ensemble { + self.ensemble(|ensemble| ensemble.clone()) + } + pub fn verify_integrity(&self) -> Result<(), EvalError> { self.ensemble(|ensemble| ensemble.verify_integrity()) } diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 027e4931..6cfdee69 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -9,7 +9,7 @@ use crate::{ ensemble::{DynamicValue, Ensemble, LNodeKind}, route::{channel::Referent, Channeler, PBack}, triple_arena::ptr_struct, - Epoch, + Epoch, SuspendedEpoch, }; ptr_struct!(PCEdge); @@ -54,6 +54,9 @@ pub enum Behavior { /// detailed to allow for more close by programs to coexist #[derive(Debug, Clone, Default)] pub struct Instruction { + // The edge behavior is unconditionally added + //Unconditional, + //SetArbitrary(SmallVec<[ensemble::PBack; 4]>), pub set_bits: SmallVec<[(ensemble::PBack, bool); 4]>, } @@ -115,6 +118,10 @@ impl Channeler { }) } + pub fn from_epoch(epoch: &SuspendedEpoch) -> Result { + epoch.ensemble(|ensemble| Self::from_ensemble(ensemble)) + } + /// Assumes that the ensemble has been optimized pub fn from_ensemble(ensemble: &Ensemble) -> Result { let mut channeler = Self::new(); diff --git a/starlight/src/route/region_adv.rs b/starlight/src/route/region_adv.rs index 6ccc9338..2f31945a 100644 --- a/starlight/src/route/region_adv.rs +++ b/starlight/src/route/region_adv.rs @@ -3,7 +3,7 @@ use std::{cmp::Ordering, marker::PhantomData}; use awint::awint_dag::triple_arena::{Advancer, OrdArena, Ptr}; // TODO may want to add to `triple_arena`, also add a `find_similar_with` to fix -// the issue with needing to rewind +// the issue with needing to rewind, also need `insert_with` pub struct RegionAdvancer Ordering, P: Ptr, K, V> { p: Option, diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index bc732494..2b28d8ff 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -1,12 +1,36 @@ -use awint::awint_dag::EvalError; +use awint::awint_dag::{ + triple_arena::{ptr_struct, ArenaTrait, OrdArena, Ptr}, + EvalError, +}; use crate::{ - ensemble::{self, Ensemble}, + ensemble::{self, Ensemble, PExternal, PRNode, Value}, route::{Channeler, HyperPath, PHyperPath}, triple_arena::Arena, Epoch, EvalAwi, LazyAwi, SuspendedEpoch, }; +ptr_struct!(PConfig; PMapping); + +#[derive(Debug, Clone)] +pub struct Configuration { + /// stable `Ptr` for the target + p_external: PExternal, + /// The index in the `RNode` + bit_i: usize, + /// The bit value the configuration wants. `None` is for not yet determined + /// or for if the value can be set to `Value::Unknown`. + value: Option, +} + +#[derive(Debug, Clone)] +pub struct Mapping { + program_p_external: PExternal, + target_p_external: PExternal, + target_p_equiv: ensemble::PBack, + bit_i: usize, +} + #[derive(Debug, Clone)] pub struct Router { target_ensemble: Ensemble, @@ -14,6 +38,10 @@ pub struct Router { program_ensemble: Ensemble, program_channeler: Channeler, hyperpaths: Arena, + // `ThisEquiv` `PBack` to `PExternal` mapping for bits we are allowed to configure + configurations: OrdArena, + // `ThisEquiv` `PBack` mapping from program to target + mappings: OrdArena, } impl Router { @@ -31,34 +59,111 @@ impl Router { program_ensemble: program_epoch.ensemble(|ensemble| ensemble.clone()), program_channeler, hyperpaths: Arena::new(), + configurations: OrdArena::new(), + mappings: OrdArena::new(), } } - /* - // TODO current plan is to have a corresponding function on the target `Epoch` - // that calls this. May want some kind of `Epoch` restating system (or use - // shared `Epoch`s?). The routing info is generated, then one or more other - // `Epoch`s that have the programs can each have their programs routed. - pub fn from_epoch(epoch: &Epoch) -> Self { - let mut res = Self::new(); - res - } - */ - /// Tell the router what bits it can use for programming the target pub fn map_config(&mut self, config: &LazyAwi) -> Result<(), EvalError> { - Ok(()) + let p_external = config.p_external(); + if let Some((p_rnode, rnode)) = self.target_ensemble.notary.get_rnode(p_external) { + for (bit_i, bit) in rnode.bits.iter().enumerate() { + if let Some(bit) = bit { + let (_, replaced) = self.configurations.insert(*bit, Configuration { + p_external, + bit_i, + value: None, + }); + // we may want to allow this, have some mechanism to be able to configure + // multiple to the same thing + if replaced.is_some() { + return Err(EvalError::OtherString(format!( + "when mapping {config:?} as configurable, found that the same bit as \ + a previous one is mapped, this may be because it was mapped twice or \ + the bit is equivalent to another somehow" + ))); + } + } + } + Ok(()) + } else { + Err(EvalError::OtherString(format!( + "when mapping configurable bits, could not find {config:?} in the target \ + `Ensemble`" + ))) + } + } + + /// Tell the router what program input bits we want to map to what target + /// input bits + pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), EvalError> { + if let Some((program_p_rnode, program_rnode)) = + self.program_ensemble.notary.get_rnode(program) + { + if let Some((target_p_rnode, target_rnode)) = + self.target_ensemble.notary.get_rnode(target) + { + let len0 = program_rnode.bits.len(); + let len1 = target_rnode.bits.len(); + if len0 != len1 { + return Err(EvalError::OtherString(format!( + "when mapping bits, found that the bitwidths of {program:?} ({len0}) and \ + {target:?} ({len1}) differ" + ))); + } + for (bit_i, the_two) in program_rnode + .bits + .iter() + .zip(target_rnode.bits.iter()) + .enumerate() + { + match the_two { + (Some(program_bit), Some(target_bit)) => { + let (_, replaced) = self.mappings.insert(*program_bit, Mapping { + program_p_external: program, + target_p_external: target, + target_p_equiv: *target_bit, + bit_i, + }); + // we may want to allow this, have some mechanism to be able to + // configure multiple to the same thing + if replaced.is_some() { + todo!() + } + } + _ => { + // maybe it should just be a no-op? haven't encountered a case yet + return Err(EvalError::OtherString(format!( + "when mapping bits, one or the other bits were optimized away \ + inconsistently" + ))); + } + (None, None) => (), + } + } + Ok(()) + } else { + Err(EvalError::OtherString(format!( + "when mapping bits, could not find {target:?} in the target `Ensemble`" + ))) + } + } else { + Err(EvalError::OtherString(format!( + "when mapping bits, could not find {program:?} in the program `Ensemble`" + ))) + } } /// Tell the router what program input bits we want to map to what target /// input bits - pub fn map_lazy(&mut self, target: &LazyAwi, program: &LazyAwi) -> Result<(), EvalError> { - Ok(()) + pub fn map_lazy(&mut self, program: &LazyAwi, target: &LazyAwi) -> Result<(), EvalError> { + self.map_rnodes(program.p_external(), target.p_external()) } /// Tell the router what program output bits we want to map to what target /// output bits - pub fn map_eval(&mut self, target: &EvalAwi, program: &EvalAwi) -> Result<(), EvalError> { - Ok(()) + pub fn map_eval(&mut self, program: &EvalAwi, target: &EvalAwi) -> Result<(), EvalError> { + self.map_rnodes(program.p_external(), target.p_external()) } } From ff89b8eed94973048f991a681bd0d9dd4df9e48f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 9 Jan 2024 19:28:32 -0600 Subject: [PATCH 096/119] router configuration --- starlight/src/route.rs | 4 +- starlight/src/route/cedge.rs | 290 ++++++++++++++++++++++++--------- starlight/src/route/channel.rs | 82 +++++++--- starlight/src/route/config.rs | 85 ++++++++++ starlight/src/route/router.rs | 94 ++++------- 5 files changed, 385 insertions(+), 170 deletions(-) create mode 100644 starlight/src/route/config.rs diff --git a/starlight/src/route.rs b/starlight/src/route.rs index 952727da..ed65f9b2 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -3,13 +3,15 @@ mod cedge; mod channel; mod cnode; +mod config; mod path; mod region_adv; mod router; -pub use cedge::{Behavior, BulkBehavior, CEdge, Instruction, PCEdge, Programmability}; +pub use cedge::{BulkBehavior, CEdge, PCEdge, Programmability, SelectorLut, SelectorValue}; pub use channel::{Channeler, PBack}; pub use cnode::CNode; +pub use config::{Config, Configurator, PConfig}; pub use path::{HyperPath, PHyperPath, Path}; pub use region_adv::RegionAdvancer; pub use router::Router; diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 6cfdee69..d1bada04 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,19 +1,74 @@ -use awint::{ - awint_dag::{smallvec::smallvec, EvalError, EvalResult}, - Awi, -}; +use std::num::NonZeroUsize; + +use awint::{awint_dag::EvalError, Awi}; use crate::{ awint_dag::smallvec::SmallVec, ensemble, ensemble::{DynamicValue, Ensemble, LNodeKind}, - route::{channel::Referent, Channeler, PBack}, + route::{channel::Referent, Channeler, Configurator, PBack}, triple_arena::ptr_struct, - Epoch, SuspendedEpoch, + SuspendedEpoch, }; ptr_struct!(PCEdge); +#[derive(Debug, Clone, Copy)] +pub enum SelectorValue { + Dynam, + ConstUnknown, + Const(bool), +} + +/// The `Vec` has the configuration indexes, the two `Awi`s +/// have bitwidths equal to `1 << len` where `len` is the number of indexes +/// +/// Logically, the selector selects from the power-of-two array which may have +/// constants and unused `ConstUnknown`s in addition to the routes for dynamics. +/// The incidents only include the dynamics, and thus we need to know where the +/// gaps are. The `Awi` is broken up into pairs of bits used to indicate the +/// following states in incrementing order: dynamic, const unknown, const zero, +/// const one +#[derive(Debug, Clone)] +pub struct SelectorLut { + awi: Awi, + v: Vec, +} + +impl SelectorLut { + pub fn get_selector_value(&self, bit_i: usize) -> SelectorValue { + debug_assert!(bit_i < (isize::MAX as usize)); + let start = bit_i << 1; + debug_assert!((bit_i << 1) < self.awi.bw()); + match ( + self.awi.get(start).unwrap(), + self.awi.get(start.wrapping_add(1)).unwrap(), + ) { + (false, false) => SelectorValue::Dynam, + (true, false) => SelectorValue::ConstUnknown, + (b, true) => SelectorValue::Const(b), + } + } + + pub fn verify_integrity(&self, sources_len: usize, sinks_len: usize) -> Result<(), EvalError> { + // TODO + let pow_len = 1usize << self.v.len(); + if (pow_len.checked_mul(2).unwrap() != self.awi.bw()) || (sinks_len != 1) { + return Err(EvalError::OtherStr("problem with `SelectorLut` validation")); + } + let mut dynam_len = 0; + for i in 0..pow_len { + if let SelectorValue::Dynam = self.get_selector_value(i) { + dynam_len += 1; + } + } + if dynam_len != sources_len { + return Err(EvalError::OtherStr("problem with `SelectorLut` validation")); + } + Ok(()) + } +} + /// Used by higher order edges to tell what it is capable of overall #[derive(Debug, Clone)] pub struct BulkBehavior { @@ -26,7 +81,7 @@ pub struct BulkBehavior { } #[derive(Debug, Clone)] -pub enum Behavior { +pub enum Programmability { /// Nothing can happen between nodes, used for connecting top level nodes /// that have no connection to each other Noop, @@ -40,52 +95,22 @@ pub enum Behavior { // necessary. /// Can behave as an arbitrary lookup table outputting a bit and taking the /// input bits. - ArbitraryLut(usize), + ArbitraryLut(Vec), /// Can behave as an arbitrary selector that multiplexes one of the input /// bits to the output - SelectorLut(usize), + SelectorLut(SelectorLut), /// Bulk behavior Bulk(BulkBehavior), } -/// A description of bits to set in order to achieve some desired edge behavior. -/// For now we unconditionally specify bits, in the future it should be more -/// detailed to allow for more close by programs to coexist -#[derive(Debug, Clone, Default)] -pub struct Instruction { - // The edge behavior is unconditionally added - //Unconditional, - //SetArbitrary(SmallVec<[ensemble::PBack; 4]>), - pub set_bits: SmallVec<[(ensemble::PBack, bool); 4]>, -} - -impl Instruction { - /// A new instruction that requires nothing - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Debug, Clone)] -pub struct Programmability { - /// The behavior that can be programmed into this edge - pub behavior: Behavior, - /// The instruction required to get the desired behavior - pub instruction: Instruction, -} - /// An edge between channels #[derive(Debug, Clone)] pub struct CEdge { - /// The sources and sinks - pub incidences: SmallVec<[PBack; 4]>, - - // the variables above should uniquely determine a `CEdge`, we define `Eq` and `Ord` to only - // respect the above and any insertion needs to check for duplicates - /// Describes the required program to route a value (could be the `p_equiv` - /// in a unit `CNode` or bulk routing through higher level `CNode`s) from - /// the source to the sink. + // sources and sinks incident to nodes + sources: Vec, + sinks: Vec, + programmability: Programmability, // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for // wide edges delay_weight: u64, @@ -96,35 +121,80 @@ impl CEdge { pub fn programmability(&self) -> &Programmability { &self.programmability } + + pub fn sources(&self) -> &[PBack] { + &self.sources + } + + pub fn sinks(&self) -> &[PBack] { + &self.sinks + } + + pub fn incidents(&self, mut f: F) { + for source in self.sources() { + f(*source) + } + for sink in self.sinks() { + f(*sink) + } + } + + pub fn incidents_len(&self) -> usize { + self.sources() + .len() + .checked_add(self.sinks().len()) + .unwrap() + } } impl Channeler { - /// Given the `incidences` (which should point to unique `ThisCNode`s), this - /// will manage the backrefs - pub fn make_cedge(&mut self, incidences: &[PBack], programmability: Programmability) -> PCEdge { + /// Given the source and sink incidences (which should point to unique + /// `ThisCNode`s), this will manage the backrefs + fn make_cedge( + &mut self, + sources: &[PBack], + sink: &[PBack], + programmability: Programmability, + ) -> PCEdge { self.cedges.insert_with(|p_self| { - let mut actual_incidences = smallvec![]; - for (i, incidence) in incidences.iter().enumerate() { - actual_incidences.push( + let mut fixed_sources = vec![]; + let mut fixed_sinks = vec![]; + for (i, source) in sources.iter().enumerate() { + fixed_sources.push( + self.cnodes + .insert_key(*source, Referent::CEdgeIncidence(p_self, i, false)) + .unwrap(), + ); + } + for (i, sink) in sink.iter().enumerate() { + fixed_sinks.push( self.cnodes - .insert_key(*incidence, Referent::CEdgeIncidence(p_self, i)) + .insert_key(*sink, Referent::CEdgeIncidence(p_self, i, true)) .unwrap(), ); } CEdge { - incidences: actual_incidences, + sources: fixed_sources, + sinks: fixed_sinks, programmability, } }) } - pub fn from_epoch(epoch: &SuspendedEpoch) -> Result { - epoch.ensemble(|ensemble| Self::from_ensemble(ensemble)) + pub fn from_target( + target_epoch: &SuspendedEpoch, + configurator: &Configurator, + ) -> Result { + target_epoch.ensemble(|ensemble| Self::new(ensemble, configurator)) + } + + pub fn from_program(target_epoch: &SuspendedEpoch) -> Result { + target_epoch.ensemble(|ensemble| Self::new(ensemble, &Configurator::new())) } /// Assumes that the ensemble has been optimized - pub fn from_ensemble(ensemble: &Ensemble) -> Result { - let mut channeler = Self::new(); + pub fn new(ensemble: &Ensemble, configurator: &Configurator) -> Result { + let mut channeler = Self::empty(); // for each equivalence make a `CNode` with associated `EnsembleBackref` for equiv in ensemble.backrefs.vals() { @@ -133,9 +203,11 @@ impl Channeler { .cnodes .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) .unwrap(); - channeler + let replaced = channeler .ensemble_backref_to_channeler_backref - .insert(equiv.p_self_equiv, channeler_backref); + .insert(equiv.p_self_equiv, channeler_backref) + .1; + assert!(replaced.is_none()); } // translate from any ensemble backref to the equivalence backref to the @@ -144,7 +216,7 @@ impl Channeler { ensemble: &Ensemble, channeler: &Channeler, ensemble_backref: ensemble::PBack, - ) -> PBack { + ) -> (ensemble::PBack, PBack) { let p_equiv = ensemble .backrefs .get_val(ensemble_backref) @@ -154,50 +226,106 @@ impl Channeler { .ensemble_backref_to_channeler_backref .find_key(&p_equiv) .unwrap(); - *channeler + let channeler_p_back = *channeler .ensemble_backref_to_channeler_backref .get_val(p0) - .unwrap() + .unwrap(); + (p_equiv, channeler_p_back) } // add `CEdge`s according to `LNode`s for lnode in ensemble.lnodes.vals() { + let p_self = translate(ensemble, &channeler, lnode.p_self).1; match &lnode.kind { LNodeKind::Copy(_) => { return Err(EvalError::OtherStr("the epoch was not optimized")) } LNodeKind::Lut(inp, awi) => { - let mut v: SmallVec<[PBack; 8]> = - smallvec![translate(ensemble, &channeler, lnode.p_self)]; + let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); for input in inp { - v.push(translate(ensemble, &channeler, *input)); + v.push(translate(ensemble, &channeler, *input).1); } - channeler.make_cedge(&v, Programmability { - behavior: Behavior::StaticLut(awi.clone()), - instruction: Instruction::new(), - }); + channeler.make_cedge(&v, &[p_self], Programmability::StaticLut(awi.clone())); } LNodeKind::DynamicLut(inp, lut) => { - let mut v: SmallVec<[PBack; 8]> = - smallvec![translate(ensemble, &channeler, lnode.p_self)]; + //let p_self = translate(ensemble, &channeler, lnode.p_self).1; + let mut is_full_selector = true; for input in inp { - v.push(translate(ensemble, &channeler, *input)); + let p_equiv = translate(ensemble, &channeler, *input).0; + if configurator.find(p_equiv).is_none() { + is_full_selector = false; + } } + let mut is_full_arbitrary = true; for lut_bit in lut.iter() { match lut_bit { - DynamicValue::ConstUnknown => todo!(), - DynamicValue::Const(_) => todo!(), - DynamicValue::Dynam(_) => todo!(), + DynamicValue::ConstUnknown | DynamicValue::Const(_) => { + // TODO we should handle intermediates inbetween arbitrary and + // static + is_full_arbitrary = false; + } + DynamicValue::Dynam(p) => { + let p_equiv = translate(ensemble, &channeler, *p).0; + if configurator.find(p_equiv).is_none() { + is_full_arbitrary = false; + } + } } - //v.push(translate(ensemble, &channeler, *input)); } - // TODO detect config bit effects - - //SelectorLut - /*channeler.make_cedge(&v, Programmability { - behavior: Behavior::ArbitraryLut(awi.clone()), - instruction: Instruction::new(), - });*/ + match (is_full_selector, is_full_arbitrary) { + (true, false) => { + let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); + let mut config = vec![]; + for input in inp.iter() { + config.push(translate(ensemble, &channeler, *input).0); + } + let mut awi = Awi::zero(NonZeroUsize::new(2 << inp.len()).unwrap()); + for (i, lut_bit) in lut.iter().enumerate() { + let i = i << 1; + match lut_bit { + DynamicValue::ConstUnknown => { + awi.set(i, true).unwrap(); + } + DynamicValue::Const(b) => { + awi.set(i.wrapping_add(1), true).unwrap(); + if *b { + awi.set(i, true).unwrap(); + } + } + DynamicValue::Dynam(p) => { + v.push(translate(ensemble, &channeler, *p).1); + } + } + } + channeler.make_cedge( + &v, + &[p_self], + Programmability::SelectorLut(SelectorLut { awi, v: config }), + ); + } + (false, true) => { + let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); + for input in inp { + v.push(translate(ensemble, &channeler, *input).1); + } + let mut config = vec![]; + for lut_bit in lut.iter() { + if let DynamicValue::Dynam(p) = lut_bit { + let p_equiv = translate(ensemble, &channeler, *p).0; + config.push(p_equiv); + } else { + unreachable!() + } + } + channeler.make_cedge( + &v, + &[p_self], + Programmability::ArbitraryLut(config), + ); + } + // we will need interaction with the `Ensemble` to do `LNode` side lowering + _ => todo!(), + } } } } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 18e2f2f2..de99d402 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -7,7 +7,7 @@ use awint::awint_dag::{ use crate::{ awint_dag::smallvec::SmallVec, ensemble, - route::{Behavior, CEdge, CNode, PCEdge}, + route::{CEdge, CNode, PCEdge, Programmability}, triple_arena::ptr_struct, }; @@ -18,7 +18,8 @@ pub enum Referent { ThisCNode, SubNode(PBack), SuperNode(PBack), - CEdgeIncidence(PCEdge, usize), + /// The bool indicates if it is a sink + CEdgeIncidence(PCEdge, usize, bool), EnsembleBackRef(ensemble::PBack), } @@ -35,7 +36,7 @@ pub struct Channeler { } impl Channeler { - pub fn new() -> Self { + pub fn empty() -> Self { Self { cnodes: SurjectArena::new(), cedges: Arena::new(), @@ -115,9 +116,15 @@ impl Channeler { Referent::ThisCNode => false, Referent::SubNode(p_subnode) => !self.cnodes.contains(*p_subnode), Referent::SuperNode(p_supernode) => !self.cnodes.contains(*p_supernode), - Referent::CEdgeIncidence(p_cedge, i) => { + Referent::CEdgeIncidence(p_cedge, i, is_sink) => { if let Some(cedges) = self.cedges.get(*p_cedge) { - if *i > cedges.incidences.len() { + if *is_sink { + if *i > cedges.sinks().len() { + return Err(EvalError::OtherString(format!( + "{referent:?} roundtrip out of bounds" + ))) + } + } else if *i > cedges.sources().len() { return Err(EvalError::OtherString(format!( "{referent:?} roundtrip out of bounds" ))) @@ -135,7 +142,14 @@ impl Channeler { } for p_cedge in self.cedges.ptrs() { let cedge = self.cedges.get(p_cedge).unwrap(); - for p_cnode in &cedge.incidences { + for p_cnode in cedge.sources().iter() { + if !self.cnodes.contains(*p_cnode) { + return Err(EvalError::OtherString(format!( + "{cedge:?}.p_cnodes {p_cnode} is invalid", + ))) + } + } + for p_cnode in cedge.sinks().iter() { if !self.cnodes.contains(*p_cnode) { return Err(EvalError::OtherString(format!( "{cedge:?}.p_cnodes {p_cnode} is invalid", @@ -172,16 +186,22 @@ impl Channeler { true } } - Referent::CEdgeIncidence(p_cedge, i) => { + Referent::CEdgeIncidence(p_cedge, i, is_sink) => { let cedge = self.cedges.get(*p_cedge).unwrap(); - let p_cnode = cedge.incidences[*i]; - if let Referent::CEdgeIncidence(p_cedge1, i1) = - self.cnodes.get_key(p_cnode).unwrap() - { - (*p_cedge != *p_cedge1) || (*i != *i1) - } else { - true - } + let mut res = false; + cedge.incidents(|incident| { + let p_cnode = cedge.sinks()[*i]; + if let Referent::CEdgeIncidence(p_cedge1, i1, is_sink1) = + self.cnodes.get_key(p_cnode).unwrap() + { + if (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) { + res = true; + } + } else { + res = true; + } + }); + res } Referent::EnsembleBackRef(_) => todo!(), }; @@ -194,14 +214,28 @@ impl Channeler { // non `Ptr` validities for p_cedge in self.cedges.ptrs() { let cedge = self.cedges.get(p_cedge).unwrap(); - let ok = match &cedge.programmability().behavior { - Behavior::Noop | Behavior::Bulk(_) => cedge.incidences.len() == 2, - Behavior::StaticLut(lut) => { + let incidents_len = cedge.incidents_len(); + let sources_len = cedge.sources().len(); + let sinks_len = cedge.sinks().len(); + let ok = match cedge.programmability() { + Programmability::Noop => incidents_len == 0, + Programmability::StaticLut(lut) => { + // TODO find every place I did the trailing zeros thing and have a function that + // does the more efficient thing the core `lut_` function does lut.bw().is_power_of_two() - && ((lut.bw().trailing_zeros() as usize + 1) == cedge.incidences.len()) + && (lut.bw().trailing_zeros() as usize == sources_len) + && (sinks_len == 1) + } + Programmability::ArbitraryLut(lut) => { + lut.len().is_power_of_two() + && ((lut.len().trailing_zeros() as usize) == sources_len) + && (sinks_len == 1) + } + Programmability::SelectorLut(selector_lut) => { + selector_lut.verify_integrity(sources_len, sinks_len)?; + true } - Behavior::SelectorLut(_) => todo!(), - Behavior::ArbitraryLut(input_len) => *input_len == cedge.incidences.len(), + Programmability::Bulk(_) => todo!(), }; if !ok { return Err(EvalError::OtherString(format!( @@ -212,9 +246,3 @@ impl Channeler { Ok(()) } } - -impl Default for Channeler { - fn default() -> Self { - Self::new() - } -} diff --git a/starlight/src/route/config.rs b/starlight/src/route/config.rs new file mode 100644 index 00000000..82608387 --- /dev/null +++ b/starlight/src/route/config.rs @@ -0,0 +1,85 @@ +use awint::awint_dag::{ + triple_arena::{ptr_struct, OrdArena}, + EvalError, +}; + +use crate::{ + ensemble::{self, Ensemble, PExternal}, + LazyAwi, +}; + +ptr_struct!(PConfig); + +#[derive(Debug, Clone)] +pub struct Config { + /// stable `Ptr` for the target + p_external: PExternal, + /// The index in the `RNode` + bit_i: usize, + /// The bit value the configuration wants. `None` is for not yet determined + /// or for if the value can be set to `Value::Unknown`. + value: Option, +} + +/// The channeler for the target needs to know which bits the router can use to +/// configure different behaviors. +#[derive(Debug, Clone)] +pub struct Configurator { + // `ThisEquiv` `PBack` to `PExternal` mapping for bits we are allowed to configure + pub configurations: OrdArena, +} + +impl Configurator { + pub fn new() -> Self { + Self { + configurations: OrdArena::new(), + } + } + + pub fn find(&self, p_equiv: ensemble::PBack) -> Option { + self.configurations.find_key(&p_equiv) + } + + /// Tell the router what bits it can use for programming the target + pub fn make_configurable( + &mut self, + ensemble: &Ensemble, + config: &LazyAwi, + ) -> Result<(), EvalError> { + let p_external = config.p_external(); + if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { + for (bit_i, bit) in rnode.bits.iter().enumerate() { + if let Some(bit) = bit { + let p_equiv = ensemble.backrefs.get_val(*bit).unwrap().p_self_equiv; + let (_, replaced) = self.configurations.insert(p_equiv, Config { + p_external, + bit_i, + value: None, + }); + // we may want to allow this, if we have a mechanism to make sure they are set + // to the same thing + if replaced.is_some() { + return Err(EvalError::OtherString(format!( + "`make_configurable(.., {config:?})`: found that the same bit as a \ + previous one is configurable, this may be because \ + `make_configurable` was called twice on the same or equivalent bit" + ))); + } + } + } + Ok(()) + } else { + Err(EvalError::OtherString(format!( + "`make_configurable(.., {config:?})`: could not find the `config` in the \ + `Ensemble` (probably, you are using something from the program ensemble instead \ + of the target ensemble)" + ))) + } + } +} + +impl Default for Configurator { + fn default() -> Self { + Self::new() + } +} diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 2b28d8ff..493220af 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -1,27 +1,16 @@ use awint::awint_dag::{ - triple_arena::{ptr_struct, ArenaTrait, OrdArena, Ptr}, + triple_arena::{ptr_struct, OrdArena}, EvalError, }; use crate::{ - ensemble::{self, Ensemble, PExternal, PRNode, Value}, + ensemble::{self, Ensemble, PExternal}, route::{Channeler, HyperPath, PHyperPath}, triple_arena::Arena, - Epoch, EvalAwi, LazyAwi, SuspendedEpoch, + EvalAwi, LazyAwi, SuspendedEpoch, }; -ptr_struct!(PConfig; PMapping); - -#[derive(Debug, Clone)] -pub struct Configuration { - /// stable `Ptr` for the target - p_external: PExternal, - /// The index in the `RNode` - bit_i: usize, - /// The bit value the configuration wants. `None` is for not yet determined - /// or for if the value can be set to `Value::Unknown`. - value: Option, -} +ptr_struct!(PMapping); #[derive(Debug, Clone)] pub struct Mapping { @@ -38,8 +27,6 @@ pub struct Router { program_ensemble: Ensemble, program_channeler: Channeler, hyperpaths: Arena, - // `ThisEquiv` `PBack` to `PExternal` mapping for bits we are allowed to configure - configurations: OrdArena, // `ThisEquiv` `PBack` mapping from program to target mappings: OrdArena, } @@ -59,51 +46,15 @@ impl Router { program_ensemble: program_epoch.ensemble(|ensemble| ensemble.clone()), program_channeler, hyperpaths: Arena::new(), - configurations: OrdArena::new(), mappings: OrdArena::new(), } } - /// Tell the router what bits it can use for programming the target - pub fn map_config(&mut self, config: &LazyAwi) -> Result<(), EvalError> { - let p_external = config.p_external(); - if let Some((p_rnode, rnode)) = self.target_ensemble.notary.get_rnode(p_external) { - for (bit_i, bit) in rnode.bits.iter().enumerate() { - if let Some(bit) = bit { - let (_, replaced) = self.configurations.insert(*bit, Configuration { - p_external, - bit_i, - value: None, - }); - // we may want to allow this, have some mechanism to be able to configure - // multiple to the same thing - if replaced.is_some() { - return Err(EvalError::OtherString(format!( - "when mapping {config:?} as configurable, found that the same bit as \ - a previous one is mapped, this may be because it was mapped twice or \ - the bit is equivalent to another somehow" - ))); - } - } - } - Ok(()) - } else { - Err(EvalError::OtherString(format!( - "when mapping configurable bits, could not find {config:?} in the target \ - `Ensemble`" - ))) - } - } - /// Tell the router what program input bits we want to map to what target /// input bits pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), EvalError> { - if let Some((program_p_rnode, program_rnode)) = - self.program_ensemble.notary.get_rnode(program) - { - if let Some((target_p_rnode, target_rnode)) = - self.target_ensemble.notary.get_rnode(target) - { + if let Some((_, program_rnode)) = self.program_ensemble.notary.get_rnode(program) { + if let Some((_, target_rnode)) = self.target_ensemble.notary.get_rnode(target) { let len0 = program_rnode.bits.len(); let len1 = target_rnode.bits.len(); if len0 != len1 { @@ -120,26 +71,39 @@ impl Router { { match the_two { (Some(program_bit), Some(target_bit)) => { - let (_, replaced) = self.mappings.insert(*program_bit, Mapping { + let program_p_equiv = self + .program_ensemble + .backrefs + .get_val(*program_bit) + .unwrap() + .p_self_equiv; + let target_p_equiv = self + .target_ensemble + .backrefs + .get_val(*target_bit) + .unwrap() + .p_self_equiv; + let (_, replaced) = self.mappings.insert(program_p_equiv, Mapping { program_p_external: program, target_p_external: target, - target_p_equiv: *target_bit, + target_p_equiv, bit_i, }); // we may want to allow this, have some mechanism to be able to - // configure multiple to the same thing + // configure multiple to the same thing as long as constraints are + // satisfied if replaced.is_some() { todo!() } } + (None, None) => (), _ => { // maybe it should just be a no-op? haven't encountered a case yet return Err(EvalError::OtherString(format!( - "when mapping bits, one or the other bits were optimized away \ - inconsistently" + "when mapping bits {program:?} and {target:?}, one or the other \ + bits were optimized away inconsistently" ))); } - (None, None) => (), } } Ok(()) @@ -166,4 +130,12 @@ impl Router { pub fn map_eval(&mut self, program: &EvalAwi, target: &EvalAwi) -> Result<(), EvalError> { self.map_rnodes(program.p_external(), target.p_external()) } + + pub fn verify_integrity(&self) -> Result<(), EvalError> { + self.target_ensemble.verify_integrity()?; + self.target_channeler.verify_integrity()?; + self.program_ensemble.verify_integrity()?; + self.program_channeler.verify_integrity()?; + Ok(()) + } } From f3f03f21053883a2b586be57c7c6e9cd2de70a82 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 9 Jan 2024 19:59:47 -0600 Subject: [PATCH 097/119] migrate `EvalError` to `starlight` --- starlight/Cargo.toml | 1 + starlight/src/awi_structs/epoch.rs | 4 +- starlight/src/awi_structs/eval_awi.rs | 3 +- starlight/src/awi_structs/lazy_awi.rs | 3 +- starlight/src/ensemble/debug.rs | 4 +- starlight/src/ensemble/optimize.rs | 4 +- starlight/src/ensemble/rnode.rs | 3 +- starlight/src/ensemble/state.rs | 3 +- starlight/src/ensemble/together.rs | 3 +- starlight/src/ensemble/value.rs | 6 +-- starlight/src/lib.rs | 2 +- starlight/src/lower/lower_op.rs | 4 +- starlight/src/lower/lower_state.rs | 3 +- starlight/src/misc.rs | 2 + starlight/src/misc/error.rs | 76 +++++++++++++++++++++++++++ starlight/src/route/cedge.rs | 4 +- starlight/src/route/channel.rs | 2 +- starlight/src/route/config.rs | 7 +-- starlight/src/route/router.rs | 7 +-- testcrate/src/lib.rs | 2 +- testcrate/tests/fuzz_elementary.rs | 11 ++-- testcrate/tests/fuzz_lower.rs | 6 +-- 22 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 starlight/src/misc/error.rs diff --git a/starlight/Cargo.toml b/starlight/Cargo.toml index a8d0b346..637fd32e 100644 --- a/starlight/Cargo.toml +++ b/starlight/Cargo.toml @@ -16,6 +16,7 @@ awint = { path = "../../awint/awint", default-features = false, features = ["ran #awint = { version = "0.15", default-features = false, features = ["rand_support", "dag"] } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rand_xoshiro = { version = "0.6", default-features = false } +thiserror = "1.0" [features] # note: "dag", "rand_support", and "std" are all turned on always diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 9a17ab46..8707c737 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -16,14 +16,14 @@ use awint::{ awint_dag::{ epoch::{EpochCallback, EpochKey}, triple_arena::{ptr_struct, Advancer, Arena}, - EvalError, Lineage, Location, Op, PState, + Lineage, Location, Op, PState, }, bw, dag, }; use crate::{ ensemble::{Ensemble, Value}, - EvalAwi, + EvalAwi, EvalError, }; /// A list of single bit `EvalAwi`s for assertions diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index 6095a951..ab242770 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -1,7 +1,7 @@ use std::{fmt, num::NonZeroUsize, thread::panicking}; use awint::{ - awint_dag::{dag, EvalError, Lineage, PState}, + awint_dag::{dag, Lineage, PState}, awint_internals::{forward_debug_fmt, BITS}, }; @@ -9,6 +9,7 @@ use crate::{ awi, ensemble::{Ensemble, PExternal}, epoch::get_current_epoch, + EvalError, }; // Note: `mem::forget` can be used on `EvalAwi`s, but in this crate it should diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index e65e5c15..48ef598c 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -7,7 +7,7 @@ use std::{ }; use awint::{ - awint_dag::{dag, EvalError, Lineage, PState}, + awint_dag::{dag, Lineage, PState}, awint_internals::forward_debug_fmt, }; @@ -15,6 +15,7 @@ use crate::{ awi, ensemble::{BasicValue, BasicValueKind, CommonValue, Ensemble, PExternal}, epoch::get_current_epoch, + EvalError, }; // do not implement `Clone` for this, we would need a separate `LazyCellAwi` diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index ca50b56d..d2fd37da 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use awint::{ - awint_dag::{EvalError, Op, PState}, + awint_dag::{Op, PState}, awint_macro_internals::triple_arena::Arena, }; @@ -11,7 +11,7 @@ use crate::{ }, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, - Epoch, + Epoch, EvalError, }; impl DebugNodeTrait for State { diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index d654c020..b10730a3 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -4,7 +4,7 @@ use awint::{ awint_dag::{ smallvec::SmallVec, triple_arena::{Advancer, Ptr}, - EvalError, PState, + PState, }, Awi, InlAwi, }; @@ -12,7 +12,7 @@ use awint::{ use crate::{ ensemble::{DynamicValue, Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, - SmallMap, + EvalError, SmallMap, }; ptr_struct!(POpt); diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index d104a462..3153b874 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -3,13 +3,14 @@ use std::num::{NonZeroU128, NonZeroUsize}; use awint::awint_dag::{ smallvec::{smallvec, SmallVec}, triple_arena::{ptr_struct, Arena, OrdArena, Ptr, Recast, Recaster}, - EvalError, PState, + PState, }; use crate::{ awi::*, ensemble::{CommonValue, Ensemble, PBack, Referent, Value}, epoch::get_current_epoch, + EvalError, }; ptr_struct!(PRNode); diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index ff1517ff..b5f9ede0 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -3,7 +3,7 @@ use std::{fmt::Write, num::NonZeroUsize}; use awint::awint_dag::{ smallvec::{smallvec, SmallVec}, triple_arena::{Advancer, Arena}, - EAwi, EvalError, EvalResult, Location, + EAwi, EvalResult, Location, Op::{self, *}, PState, }; @@ -15,6 +15,7 @@ use crate::{ DynamicValue, Ensemble, PBack, Value, }, epoch::EpochShared, + EvalError, }; /// Represents a single state that `awint_dag::mimick::Bits` is in at one point diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 9b1fcf0e..761b507d 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -4,7 +4,7 @@ use awint::{ awint_dag::{ smallvec::{smallvec, SmallVec}, triple_arena::{Recast, Recaster}, - EvalError, Location, Op, PState, + Location, Op, PState, }, Awi, Bits, }; @@ -15,6 +15,7 @@ use crate::{ PTNode, State, Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, + EvalError, }; ptr_struct!(PBack); diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 132c0c38..8962d53a 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -2,15 +2,13 @@ use std::num::{NonZeroU64, NonZeroUsize}; use awint::{ awi::*, - awint_dag::{ - triple_arena::{ptr_struct, Advancer, OrdArena}, - EvalError, - }, + awint_dag::triple_arena::{ptr_struct, Advancer, OrdArena}, }; use crate::{ ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent}, epoch::EpochShared, + EvalError, }; #[derive(Debug, Clone, Copy)] diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 4fb03bdb..25ae1316 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -179,7 +179,7 @@ pub use awi_structs::{ #[cfg(feature = "debug")] pub use awint::awint_dag::triple_arena_render; pub use awint::{self, awint_dag, awint_dag::triple_arena}; -pub use misc::{SmallMap, StarRng}; +pub use misc::{EvalError, SmallMap, StarRng}; /// Reexports all the regular arbitrary width integer structs, macros, common /// enums, and most of `core::primitive::*`. This is useful for glob importing diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 749d996c..c6b3a2a5 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -8,7 +8,7 @@ use std::{cmp::min, num::NonZeroUsize}; use awint::{ awint_dag::{ triple_arena::Ptr, - DummyDefault, EvalError, Lineage, + DummyDefault, Lineage, Op::{self, *}, PState, }, @@ -17,7 +17,7 @@ use awint::{ }; use super::meta::*; -use crate::awi; +use crate::{awi, EvalError}; pub trait LowerManagement { fn graft(&mut self, output_and_operands: &[PState]); diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index 66c5ff17..f1351ad7 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use awint::{ - awint_dag::{smallvec::smallvec, ConcatFieldsType, ConcatType, EvalError, Op::*, PState}, + awint_dag::{smallvec::smallvec, ConcatFieldsType, ConcatType, Op::*, PState}, bw, }; @@ -9,6 +9,7 @@ use crate::{ ensemble::Ensemble, epoch::EpochShared, lower::{lower_op, LowerManagement}, + EvalError, }; impl Ensemble { diff --git a/starlight/src/misc.rs b/starlight/src/misc.rs index a6fd9598..dc9e01f6 100644 --- a/starlight/src/misc.rs +++ b/starlight/src/misc.rs @@ -1,5 +1,7 @@ +mod error; mod rng; mod small_map; +pub use error::EvalError; pub use rng::StarRng; pub use small_map::SmallMap; diff --git a/starlight/src/misc/error.rs b/starlight/src/misc/error.rs new file mode 100644 index 00000000..07e6c006 --- /dev/null +++ b/starlight/src/misc/error.rs @@ -0,0 +1,76 @@ +use core::fmt; +use std::fmt::Debug; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)] +pub enum EvalError { + // An operand points nowhere, so the DAG is broken + #[error("InvalidPtr")] + InvalidPtr, + // Thrown if a `Literal`, `Invalid`, or `Opaque` node is attempted to be evaluated + #[error("Unevaluatable")] + Unevaluatable, + #[error("WrongNumberOfOperands")] + WrongNumberOfOperands, + // An `Opaque` node was expected + #[error("ExpectedOpaque")] + ExpectedOpaque, + // an operand is not a `Literal` + #[error("NonliteralOperand")] + NonliteralOperand, + // wrong bitwidths of operands + #[error("WrongBitwidth")] + WrongBitwidth, + // Something needs a statically known bitwidth + #[error("NonStaticBitwidth")] + NonStaticBitwidth, + // wrong integer value of an operand, such as overshifting from a shift operation or going out + // of bounds in a field operation + #[error("InvalidOperandValue")] + InvalidOperandValue, + // A typical `Bits` operation failed + #[error("EvalFailure")] + EvalFailure, + // An operation was unimplemented + #[error("Unimplemented")] + Unimplemented, + // Some other kind of brokenness, such as dependency edges not agreeing with operand edges + #[error("{0}")] + OtherStr(&'static str), + #[error("{0}")] + OtherString(String), + #[error("AssertionFailure({0})")] + AssertionFailure(String), +} + +struct DisplayStr<'a>(pub &'a str); +impl<'a> Debug for DisplayStr<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + +impl Debug for EvalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidPtr => write!(f, "InvalidPtr"), + Self::Unevaluatable => write!(f, "Unevaluatable"), + Self::WrongNumberOfOperands => write!(f, "WrongNumberOfOperands"), + Self::ExpectedOpaque => write!(f, "ExpectedOpaque"), + Self::NonliteralOperand => write!(f, "NonliteralOperand"), + Self::WrongBitwidth => write!(f, "WrongBitwidth"), + Self::NonStaticBitwidth => write!(f, "NonStaticBitwidth"), + Self::InvalidOperandValue => write!(f, "InvalidOperandValue"), + Self::EvalFailure => write!(f, "EvalFailure"), + Self::Unimplemented => write!(f, "Unimplemented"), + Self::OtherStr(arg0) => f.debug_tuple("OtherStr").field(&DisplayStr(arg0)).finish(), + Self::OtherString(arg0) => f + .debug_tuple("OtherString") + .field(&DisplayStr(arg0)) + .finish(), + Self::AssertionFailure(arg0) => f + .debug_tuple("AssertionFailure") + .field(&DisplayStr(arg0)) + .finish(), + } + } +} diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index d1bada04..2ca4de99 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,6 +1,6 @@ use std::num::NonZeroUsize; -use awint::{awint_dag::EvalError, Awi}; +use awint::Awi; use crate::{ awint_dag::smallvec::SmallVec, @@ -8,7 +8,7 @@ use crate::{ ensemble::{DynamicValue, Ensemble, LNodeKind}, route::{channel::Referent, Channeler, Configurator, PBack}, triple_arena::ptr_struct, - SuspendedEpoch, + EvalError, SuspendedEpoch, }; ptr_struct!(PCEdge); diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index de99d402..9da39e9d 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,7 +1,6 @@ use awint::awint_dag::{ smallvec::smallvec, triple_arena::{Arena, OrdArena, SurjectArena}, - EvalError, }; use crate::{ @@ -9,6 +8,7 @@ use crate::{ ensemble, route::{CEdge, CNode, PCEdge, Programmability}, triple_arena::ptr_struct, + EvalError, }; ptr_struct!(P0; PBack); diff --git a/starlight/src/route/config.rs b/starlight/src/route/config.rs index 82608387..45258deb 100644 --- a/starlight/src/route/config.rs +++ b/starlight/src/route/config.rs @@ -1,11 +1,8 @@ -use awint::awint_dag::{ - triple_arena::{ptr_struct, OrdArena}, - EvalError, -}; +use awint::awint_dag::triple_arena::{ptr_struct, OrdArena}; use crate::{ ensemble::{self, Ensemble, PExternal}, - LazyAwi, + EvalError, LazyAwi, }; ptr_struct!(PConfig); diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 493220af..b7f3010f 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -1,13 +1,10 @@ -use awint::awint_dag::{ - triple_arena::{ptr_struct, OrdArena}, - EvalError, -}; +use awint::awint_dag::triple_arena::{ptr_struct, OrdArena}; use crate::{ ensemble::{self, Ensemble, PExternal}, route::{Channeler, HyperPath, PHyperPath}, triple_arena::Arena, - EvalAwi, LazyAwi, SuspendedEpoch, + EvalAwi, EvalError, LazyAwi, SuspendedEpoch, }; ptr_struct!(PMapping); diff --git a/testcrate/src/lib.rs b/testcrate/src/lib.rs index 806fa525..f167a9f7 100644 --- a/testcrate/src/lib.rs +++ b/testcrate/src/lib.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use starlight::{awint_dag::EvalError, Epoch}; +use starlight::{Epoch, EvalError}; pub fn _render(epoch: &Epoch) -> Result<(), EvalError> { epoch.render_to_svgs_in_dir(PathBuf::from("./".to_owned())) diff --git a/testcrate/tests/fuzz_elementary.rs b/testcrate/tests/fuzz_elementary.rs index 2d786307..1b6206de 100644 --- a/testcrate/tests/fuzz_elementary.rs +++ b/testcrate/tests/fuzz_elementary.rs @@ -1,7 +1,7 @@ use std::{cmp::min, num::NonZeroUsize}; use starlight::{ - awint::{awi, awint_dag::EvalError, dag}, + awint::{awi, dag}, triple_arena::{ptr_struct, Arena}, Epoch, EvalAwi, LazyAwi, StarRng, }; @@ -131,7 +131,7 @@ impl Mem { epoch.lower_and_prune().unwrap(); } - pub fn verify_equivalence(&mut self, epoch: &Epoch) -> Result<(), EvalError> { + pub fn verify_equivalence(&mut self, epoch: &Epoch) { // set all lazy roots for (lazy, lit) in &mut self.roots { lazy.retro_(lit).unwrap(); @@ -142,7 +142,6 @@ impl Mem { for pair in self.a.vals() { assert_eq!(pair.eval.as_ref().unwrap().eval().unwrap(), pair.awi); } - Ok(()) } } @@ -232,11 +231,9 @@ fn fuzz_elementary() { } m.finish(&epoch); epoch.verify_integrity().unwrap(); - let res = m.verify_equivalence(&epoch); - res.unwrap(); + m.verify_equivalence(&epoch); epoch.optimize().unwrap(); - let res = m.verify_equivalence(&epoch); - res.unwrap(); + m.verify_equivalence(&epoch); // TODO verify stable optimization drop(epoch); m.clear(); diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index c1fa2c2d..5ebdc639 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -7,7 +7,6 @@ use std::{ use starlight::{ awi, - awint_dag::EvalError, dag::{self}, triple_arena::{ptr_struct, Arena}, Epoch, EvalAwi, LazyAwi, StarRng, @@ -154,7 +153,7 @@ impl Mem { epoch.lower_and_prune().unwrap(); } - pub fn eval_and_verify_equal(&mut self, epoch: &Epoch) -> Result<(), EvalError> { + pub fn eval_and_verify_equal(&mut self, epoch: &Epoch) { // set half of the roots randomly let len = self.roots.len(); for _ in 0..(len / 2) { @@ -176,7 +175,6 @@ impl Mem { for pair in self.a.vals() { assert_eq!(pair.eval.as_ref().unwrap().eval().unwrap(), pair.awi); } - Ok(()) } } @@ -765,7 +763,7 @@ fn fuzz_lower() { num_dag_duo(&mut rng, &mut m) } m.finish(&epoch); - m.eval_and_verify_equal(&epoch).unwrap(); + m.eval_and_verify_equal(&epoch); m.clear(); drop(epoch); } From 16fb6b356e89b5ebb37b6ab0dbfa4d17e78ad504 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 9 Jan 2024 20:09:14 -0600 Subject: [PATCH 098/119] remove outdated errors --- starlight/src/lower/lower_state.rs | 20 ++++++------------ starlight/src/misc/error.rs | 34 ------------------------------ 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index f1351ad7..fb006562 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -19,7 +19,9 @@ impl Ensemble { #[cfg(debug_assertions)] { if (self.stator.states[p_state].op.operands_len() + 1) != operands.len() { - return Err(EvalError::WrongNumberOfOperands) + return Err(EvalError::OtherStr( + "wrong number of operands for the `graft` function", + )) } for (i, op) in self.stator.states[p_state].op.operands().iter().enumerate() { let current_nzbw = self.stator.states[operands[i + 1]].nzbw; @@ -32,7 +34,9 @@ impl Ensemble { ))) } if !current_is_opaque { - return Err(EvalError::ExpectedOpaque) + return Err(EvalError::OtherStr( + "expected an `Opaque` for the `graft` function", + )) } } if self.stator.states[p_state].nzbw != self.stator.states[operands[0]].nzbw { @@ -170,7 +174,6 @@ impl Ensemble { epoch_shared: &EpochShared, p_state: PState, ) -> Result<(), EvalError> { - let mut unimplemented = false; let mut lock = epoch_shared.epoch_data.borrow_mut(); if let Some(state) = lock.ensemble.stator.states.get(p_state) { if state.lowered_to_elementary { @@ -321,11 +324,6 @@ impl Ensemble { temporary.set_as_current(); let lowering_done = match Ensemble::lower_op(&temporary, p_state) { Ok(lowering_done) => lowering_done, - Err(EvalError::Unimplemented) => { - // finish lowering as much as possible - unimplemented = true; - true - } Err(e) => { temporary.remove_as_current().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -379,10 +377,6 @@ impl Ensemble { } } - if unimplemented { - Err(EvalError::Unimplemented) - } else { - Ok(()) - } + Ok(()) } } diff --git a/starlight/src/misc/error.rs b/starlight/src/misc/error.rs index 07e6c006..4da713aa 100644 --- a/starlight/src/misc/error.rs +++ b/starlight/src/misc/error.rs @@ -9,37 +9,14 @@ pub enum EvalError { // Thrown if a `Literal`, `Invalid`, or `Opaque` node is attempted to be evaluated #[error("Unevaluatable")] Unevaluatable, - #[error("WrongNumberOfOperands")] - WrongNumberOfOperands, - // An `Opaque` node was expected - #[error("ExpectedOpaque")] - ExpectedOpaque, - // an operand is not a `Literal` - #[error("NonliteralOperand")] - NonliteralOperand, // wrong bitwidths of operands #[error("WrongBitwidth")] WrongBitwidth, - // Something needs a statically known bitwidth - #[error("NonStaticBitwidth")] - NonStaticBitwidth, - // wrong integer value of an operand, such as overshifting from a shift operation or going out - // of bounds in a field operation - #[error("InvalidOperandValue")] - InvalidOperandValue, - // A typical `Bits` operation failed - #[error("EvalFailure")] - EvalFailure, - // An operation was unimplemented - #[error("Unimplemented")] - Unimplemented, // Some other kind of brokenness, such as dependency edges not agreeing with operand edges #[error("{0}")] OtherStr(&'static str), #[error("{0}")] OtherString(String), - #[error("AssertionFailure({0})")] - AssertionFailure(String), } struct DisplayStr<'a>(pub &'a str); @@ -54,23 +31,12 @@ impl Debug for EvalError { match self { Self::InvalidPtr => write!(f, "InvalidPtr"), Self::Unevaluatable => write!(f, "Unevaluatable"), - Self::WrongNumberOfOperands => write!(f, "WrongNumberOfOperands"), - Self::ExpectedOpaque => write!(f, "ExpectedOpaque"), - Self::NonliteralOperand => write!(f, "NonliteralOperand"), Self::WrongBitwidth => write!(f, "WrongBitwidth"), - Self::NonStaticBitwidth => write!(f, "NonStaticBitwidth"), - Self::InvalidOperandValue => write!(f, "InvalidOperandValue"), - Self::EvalFailure => write!(f, "EvalFailure"), - Self::Unimplemented => write!(f, "Unimplemented"), Self::OtherStr(arg0) => f.debug_tuple("OtherStr").field(&DisplayStr(arg0)).finish(), Self::OtherString(arg0) => f .debug_tuple("OtherString") .field(&DisplayStr(arg0)) .finish(), - Self::AssertionFailure(arg0) => f - .debug_tuple("AssertionFailure") - .field(&DisplayStr(arg0)) - .finish(), } } } From f7a32d36262cb79c632b2f013deaab5e62b78798 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Tue, 9 Jan 2024 21:08:01 -0600 Subject: [PATCH 099/119] rename `EvalError` to `Error` --- CHANGELOG.md | 1 + starlight/src/awi_structs/epoch.rs | 44 +++++------ starlight/src/awi_structs/eval_awi.rs | 12 +-- starlight/src/awi_structs/lazy_awi.rs | 18 ++--- starlight/src/ensemble/debug.rs | 10 +-- starlight/src/ensemble/optimize.rs | 14 ++-- starlight/src/ensemble/rnode.rs | 30 ++++---- starlight/src/ensemble/state.rs | 54 +++++++------- starlight/src/ensemble/together.rs | 102 ++++++++++++-------------- starlight/src/ensemble/value.rs | 24 +++--- starlight/src/lib.rs | 4 +- starlight/src/lower/lower_op.rs | 12 +-- starlight/src/lower/lower_state.rs | 20 ++--- starlight/src/misc.rs | 2 +- starlight/src/misc/error.rs | 10 ++- starlight/src/route/cedge.rs | 18 ++--- starlight/src/route/channel.rs | 28 ++++--- starlight/src/route/config.rs | 8 +- starlight/src/route/router.rs | 18 ++--- testcrate/src/lib.rs | 4 +- 20 files changed, 211 insertions(+), 222 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e67fbb..1eb0432c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - many fixes for `Epoch` behavior - `LNode`s now have a `LNodeKind` - `StarRng::index` was renamed to `index_slice`, and a `index_slice_mut` and new `index` function were added +- Redid the error system ### Additions - Added `Epoch::suspend` diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index 8707c737..e7de221e 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -23,7 +23,7 @@ use awint::{ use crate::{ ensemble::{Ensemble, Value}, - EvalAwi, EvalError, + Error, EvalAwi, }; /// A list of single bit `EvalAwi`s for assertions @@ -213,7 +213,7 @@ impl EpochShared { /// `PerEpochShared` in the `EpochData`. /// /// This function should not be called more than once per `self.p_self`. - pub fn drop_associated(&self) -> Result<(), EvalError> { + pub fn drop_associated(&self) -> Result<(), Error> { let mut lock = self.epoch_data.borrow_mut(); if let Some(ours) = lock.responsible_for.remove(self.p_self) { for p_state in &ours.states_inserted { @@ -229,7 +229,7 @@ impl EpochShared { match lock.epoch_key.take().unwrap().pop_off_epoch_stack() { Ok(()) => (), Err((self_gen, top_gen)) => { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "The last `starlight::Epoch` or `starlight::SuspendedEpoch` of a \ group of one or more shared `Epoch`s was dropped out of stacklike \ order, such that an `awint_dag::epoch::EpochKey` with generation {} \ @@ -243,7 +243,7 @@ impl EpochShared { } Ok(()) } else { - Err(EvalError::OtherStr( + Err(Error::OtherStr( "should be unreachable: called `EpochShared::drop_associated` on the same \ `EpochShared`", )) @@ -293,7 +293,7 @@ impl EpochShared { /// (returning an error if any are false, and returning an error on /// unevaluatable assertions if `strict`), and eliminates assertions /// that evaluate to a constant true. - pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { + pub fn assert_assertions(&self, strict: bool) -> Result<(), Error> { let p_self = self.p_self; let epoch_data = self.epoch_data.borrow(); let mut len = epoch_data @@ -326,12 +326,12 @@ impl EpochShared { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "an assertion bit evaluated to false, failed on {p_external} {:?}", s ))) } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "an assertion bit evaluated to false, failed on {p_external} {p_state}" ))) } @@ -366,13 +366,13 @@ impl EpochShared { let epoch_data = self.epoch_data.borrow(); let s = epoch_data.ensemble.get_state_debug(p_state); if let Some(s) = s { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ {p_external} {}", s ))) } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "an assertion bit could not be evaluated to a known value, failed on \ {p_external} {p_state}" ))) @@ -382,7 +382,7 @@ impl EpochShared { Ok(()) } - fn internal_drive_loops_with_lower_capability(&self) -> Result<(), EvalError> { + fn internal_drive_loops_with_lower_capability(&self) -> Result<(), Error> { // `Loop`s register states to lower so that the below loops can find them Ensemble::handle_requests_with_lower_capability(self)?; // first evaluate all loop drivers @@ -412,7 +412,7 @@ impl EpochShared { Ok(()) } - fn internal_drive_loops(&self) -> Result<(), EvalError> { + fn internal_drive_loops(&self) -> Result<(), Error> { // first evaluate all loop drivers let mut lock = self.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; @@ -737,19 +737,19 @@ impl Epoch { /// Checks if `self.shared()` is the same as the current epoch, and returns /// the `EpochShared` if so - fn check_current(&self) -> Result { + fn check_current(&self) -> Result { let epoch_shared = get_current_epoch().unwrap(); if Rc::ptr_eq(&epoch_shared.epoch_data, &self.shared().epoch_data) { Ok(self.shared().clone()) } else { - Err(EvalError::OtherStr("epoch is not the current epoch")) + Err(Error::OtherStr("epoch is not the current epoch")) } } /// Suspends the `Epoch` from being the current epoch temporarily. Returns /// an error if `self` is not the current `Epoch`. - pub fn suspend(mut self) -> Result { - // TODO in the `EvalError` redo (probably needs a `starlight` side `EvalError`), + pub fn suspend(mut self) -> Result { + // TODO in the `Error` redo (probably needs a `starlight` side `Error`), // there should be a variant that returns the `Epoch` to prevent it from being // dropped and causing another error self.inner.epoch_shared.remove_as_current().unwrap(); @@ -765,7 +765,7 @@ impl Epoch { self.ensemble(|ensemble| ensemble.clone()) } - pub fn verify_integrity(&self) -> Result<(), EvalError> { + pub fn verify_integrity(&self) -> Result<(), Error> { self.ensemble(|ensemble| ensemble.verify_integrity()) } @@ -779,13 +779,13 @@ impl Epoch { /// If any assertion bit evaluates to false, this returns an error. If /// `strict` and an assertion could not be evaluated to a known value, this /// also returns an error. Prunes assertions evaluated to a constant true. - pub fn assert_assertions(&self, strict: bool) -> Result<(), EvalError> { + pub fn assert_assertions(&self, strict: bool) -> Result<(), Error> { self.shared().assert_assertions(strict) } /// Removes all states that do not lead to a live `EvalAwi`, and loosely /// evaluates assertions. - pub fn prune_unused_states(&self) -> Result<(), EvalError> { + pub fn prune_unused_states(&self) -> Result<(), Error> { let epoch_shared = self.shared(); // get rid of constant assertions let _ = epoch_shared.assert_assertions(false); @@ -797,7 +797,7 @@ impl Epoch { /// `RNode`s that need it. This is not needed in most circumstances, /// `EvalAwi` and optimization functions do this on demand. Requires /// that `self` be the current `Epoch`. - pub fn lower(&self) -> Result<(), EvalError> { + pub fn lower(&self) -> Result<(), Error> { let epoch_shared = self.check_current()?; Ensemble::lower_for_rnodes(&epoch_shared)?; let _ = epoch_shared.assert_assertions(false); @@ -807,7 +807,7 @@ impl Epoch { /// Aggressively prunes all states, lowering `RNode`s for `EvalAwi`s and /// `LazyAwi`s if necessary and evaluating assertions. Requires that `self` /// be the current `Epoch`. - pub fn lower_and_prune(&self) -> Result<(), EvalError> { + pub fn lower_and_prune(&self) -> Result<(), Error> { let epoch_shared = self.check_current()?; Ensemble::lower_for_rnodes(&epoch_shared)?; // get rid of constant assertions @@ -818,7 +818,7 @@ impl Epoch { /// Runs optimization including lowering then pruning all states. Requires /// that `self` be the current `Epoch`. - pub fn optimize(&self) -> Result<(), EvalError> { + pub fn optimize(&self) -> Result<(), Error> { let epoch_shared = self.check_current()?; Ensemble::lower_for_rnodes(&epoch_shared).unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); @@ -833,7 +833,7 @@ impl Epoch { /// This evaluates all loop drivers, and then registers loopback changes. /// Requires that `self` be the current `Epoch`. - pub fn drive_loops(&self) -> Result<(), EvalError> { + pub fn drive_loops(&self) -> Result<(), Error> { let epoch_shared = self.check_current()?; if epoch_shared .epoch_data diff --git a/starlight/src/awi_structs/eval_awi.rs b/starlight/src/awi_structs/eval_awi.rs index ab242770..588b472f 100644 --- a/starlight/src/awi_structs/eval_awi.rs +++ b/starlight/src/awi_structs/eval_awi.rs @@ -9,7 +9,7 @@ use crate::{ awi, ensemble::{Ensemble, PExternal}, epoch::get_current_epoch, - EvalError, + Error, }; // Note: `mem::forget` can be used on `EvalAwi`s, but in this crate it should @@ -83,12 +83,12 @@ macro_rules! eval_primitives { /// The same as [EvalAwi::eval], except that it returns a primitive /// and returns an error if the bitwidth of the evaluation does not /// match the bitwidth of the primitive - pub fn $f(&self) -> Result<$x, EvalError> { + pub fn $f(&self) -> Result<$x, Error> { let awi = self.eval()?; if awi.bw() == $w { Ok(awi.$to_x()) } else { - Err(EvalError::WrongBitwidth) + Err(Error::WrongBitwidth) } } )* @@ -132,7 +132,7 @@ impl EvalAwi { self.p_external } - fn try_get_nzbw(&self) -> Result { + fn try_get_nzbw(&self) -> Result { Ensemble::get_thread_local_rnode_nzbw(self.p_external) } @@ -190,7 +190,7 @@ impl EvalAwi { /// it may be possible to evaluate to a known value even if some inputs are /// `opaque`, but in general this will return an error that a bit could not /// be evaluated to a known value, if any upstream inputs are `opaque`. - pub fn eval(&self) -> Result { + pub fn eval(&self) -> Result { let nzbw = self.try_get_nzbw()?; let mut res = awi::Awi::zero(nzbw); for bit_i in 0..res.bw() { @@ -198,7 +198,7 @@ impl EvalAwi { if let Some(val) = val.known_value() { res.set(bit_i, val).unwrap(); } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "could not eval bit {bit_i} to known value, the node is {}", self.p_external() ))) diff --git a/starlight/src/awi_structs/lazy_awi.rs b/starlight/src/awi_structs/lazy_awi.rs index 48ef598c..324f0760 100644 --- a/starlight/src/awi_structs/lazy_awi.rs +++ b/starlight/src/awi_structs/lazy_awi.rs @@ -15,7 +15,7 @@ use crate::{ awi, ensemble::{BasicValue, BasicValueKind, CommonValue, Ensemble, PExternal}, epoch::get_current_epoch, - EvalError, + Error, }; // do not implement `Clone` for this, we would need a separate `LazyCellAwi` @@ -68,7 +68,7 @@ macro_rules! retro_primitives { ($($f:ident $x:ident);*;) => { $( /// Retroactively-assigns by `rhs` - pub fn $f(&self, rhs: $x) -> Result<(), EvalError> { + pub fn $f(&self, rhs: $x) -> Result<(), Error> { self.retro_(&awi::InlAwi::from(rhs)) } )* @@ -106,7 +106,7 @@ macro_rules! retro { $( /// Retroactively-assigns by `rhs`. Returns an error if this /// is being called after the corresponding Epoch is dropped. - pub fn $f(&self) -> Result<(), EvalError> { + pub fn $f(&self) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value( self.p_external, CommonValue::Basic(BasicValue { @@ -184,13 +184,13 @@ impl LazyAwi { /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch /// or if this is being called after the corresponding Epoch is dropped. - pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), false) } /// Retroactively-unknown-assigns, the same as `retro_` except it sets the /// bits to a dynamically unknown value - pub fn retro_unknown_(&self) -> Result<(), EvalError> { + pub fn retro_unknown_(&self) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value( self.p_external, CommonValue::Basic(BasicValue { @@ -205,7 +205,7 @@ impl LazyAwi { /// adds the guarantee that the value will never be changed again (or else /// it will result in errors if you try another `retro_*` function on /// `self`) - pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), true) } } @@ -355,13 +355,13 @@ impl LazyInlAwi { /// Retroactively-assigns by `rhs`. Returns an error if bitwidths mismatch /// or if this is being called after the corresponding Epoch is dropped. - pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + pub fn retro_(&self, rhs: &awi::Bits) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), false) } /// Retroactively-unknown-assigns, the same as `retro_` except it sets the /// bits to a dynamically unknown value - pub fn retro_unknown_(&self) -> Result<(), EvalError> { + pub fn retro_unknown_(&self) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value( self.p_external, CommonValue::Basic(BasicValue { @@ -374,7 +374,7 @@ impl LazyInlAwi { /// Retroactively-constant-assigns by `rhs`, the same as `retro_` except it /// adds the guarantee that the value will never be changed again - pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), EvalError> { + pub fn retro_const_(&self, rhs: &awi::Bits) -> Result<(), Error> { Ensemble::change_thread_local_rnode_value(self.p_external, CommonValue::Bits(rhs), true) } } diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index d2fd37da..aacacfd2 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -11,7 +11,7 @@ use crate::{ }, triple_arena::{Advancer, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, - Epoch, EvalError, + Epoch, Error, }; impl DebugNodeTrait for State { @@ -286,16 +286,16 @@ impl Ensemble { arena } - pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), EvalError> { + pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), Error> { let dir = match out_file.canonicalize() { Ok(o) => { if !o.is_dir() { - return Err(EvalError::OtherStr("need a directory not a file")); + return Err(Error::OtherStr("need a directory not a file")); } o } Err(e) => { - return Err(EvalError::OtherString(format!("{e:?}"))); + return Err(Error::OtherString(format!("{e:?}"))); } }; let mut ensemble_file = dir.clone(); @@ -321,7 +321,7 @@ impl Epoch { }); } - pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), EvalError> { + pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), Error> { let tmp = &out_file; self.ensemble(|ensemble| { let out_file = tmp.to_owned(); diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index b10730a3..9c72f553 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -12,7 +12,7 @@ use awint::{ use crate::{ ensemble::{DynamicValue, Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent, Value}, triple_arena::{ptr_struct, OrdArena}, - EvalError, SmallMap, + Error, SmallMap, }; ptr_struct!(POpt); @@ -75,9 +75,9 @@ impl Optimizer { /// Checks that there are no remaining optimizations, then shrinks /// allocations - pub fn check_clear(&mut self) -> Result<(), EvalError> { + pub fn check_clear(&mut self) -> Result<(), Error> { if !self.optimizations.is_empty() { - return Err(EvalError::OtherStr("optimizations need to be empty")); + return Err(Error::OtherStr("optimizations need to be empty")); } self.optimizations.clear_and_shrink(); Ok(()) @@ -92,7 +92,7 @@ impl Ensemble { /// Removes all `Const` inputs and assigns `Const` result if possible. /// Returns if a `Const` result was assigned (`Optimization::ConstifyEquiv` /// needs to be run by the caller). - pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> Result { + pub fn const_eval_lnode(&mut self, p_lnode: PLNode) -> Result { let lnode = self.lnodes.get_mut(p_lnode).unwrap(); Ok(match &mut lnode.kind { LNodeKind::Copy(inp) => { @@ -438,7 +438,7 @@ impl Ensemble { /// always be applied before any further optimizations are applied, so that /// `RemoveUnused` and `ConstPropogate` can be handled before any other /// optimization - pub fn preinvestigate_equiv(&mut self, p_equiv: PBack) -> Result<(), EvalError> { + pub fn preinvestigate_equiv(&mut self, p_equiv: PBack) -> Result<(), Error> { let mut non_self_rc = 0usize; let equiv = self.backrefs.get_val(p_equiv).unwrap(); let mut is_const = equiv.val.is_const(); @@ -550,7 +550,7 @@ impl Ensemble { } /// Removes all states, optimizes, and shrinks allocations - pub fn optimize_all(&mut self) -> Result<(), EvalError> { + pub fn optimize_all(&mut self) -> Result<(), Error> { self.force_remove_all_states().unwrap(); // need to preinvestigate everything before starting a priority loop let mut adv = self.backrefs.advancer(); @@ -565,7 +565,7 @@ impl Ensemble { self.recast_all_internal_ptrs() } - pub fn optimize(&mut self, p_optimization: POpt) -> Result<(), EvalError> { + pub fn optimize(&mut self, p_optimization: POpt) -> Result<(), Error> { let optimization = self .optimizer .optimizations diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index 3153b874..feecd9d8 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -10,7 +10,7 @@ use crate::{ awi::*, ensemble::{CommonValue, Ensemble, PBack, Referent, Value}, epoch::get_current_epoch, - EvalError, + Error, }; ptr_struct!(PRNode); @@ -139,7 +139,7 @@ impl Ensemble { &mut self, p_rnode: PRNode, allow_pruned: bool, - ) -> Result<(), EvalError> { + ) -> Result<(), Error> { let rnode = &self.notary.rnodes()[p_rnode]; if rnode.bits.is_empty() { if let Some(p_state) = rnode.associated_state { @@ -162,13 +162,13 @@ impl Ensemble { } } if !allow_pruned { - return Err(EvalError::OtherStr("failed to initialize `RNode`")) + return Err(Error::OtherStr("failed to initialize `RNode`")) } } Ok(()) } - pub fn remove_rnode(&mut self, p_external: PExternal) -> Result<(), EvalError> { + pub fn remove_rnode(&mut self, p_external: PExternal) -> Result<(), Error> { if let Some(p_rnode) = self.notary.rnodes.find_key(&p_external) { let rnode = self.notary.rnodes.remove(p_rnode).unwrap().1; for p_back in rnode.bits { @@ -179,18 +179,18 @@ impl Ensemble { } Ok(()) } else { - Err(EvalError::InvalidPtr) + Err(Error::InvalidPtr) } } - pub fn get_thread_local_rnode_nzbw(p_external: PExternal) -> Result { + pub fn get_thread_local_rnode_nzbw(p_external: PExternal) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { Ok(rnode.nzbw) } else { - Err(EvalError::OtherStr( + Err(Error::OtherStr( "could not find thread local `RNode`, probably an `EvalAwi` or `LazyAwi` was used \ outside of the `Epoch` it was created in", )) @@ -203,7 +203,7 @@ impl Ensemble { p_external: PExternal, common_value: CommonValue<'_>, make_const: bool, - ) -> Result<(), EvalError> { + ) -> Result<(), Error> { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; @@ -211,7 +211,7 @@ impl Ensemble { ensemble.initialize_rnode_if_needed(p_rnode, true)?; if !ensemble.notary.rnodes[p_rnode].bits.is_empty() { if ensemble.notary.rnodes[p_rnode].bits.len() != common_value.bw() { - return Err(EvalError::WrongBitwidth); + return Err(Error::WrongBitwidth); } for bit_i in 0..common_value.bw() { let p_back = ensemble.notary.rnodes[p_rnode].bits[bit_i]; @@ -234,7 +234,7 @@ impl Ensemble { } // else the state was pruned } else { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "could not find thread local `RNode`, probably a `LazyAwi` was used outside of \ the `Epoch` it was created in", )) @@ -245,7 +245,7 @@ impl Ensemble { pub fn calculate_thread_local_rnode_value( p_external: PExternal, bit_i: usize, - ) -> Result { + ) -> Result { let epoch_shared = get_current_epoch().unwrap(); let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; @@ -254,19 +254,17 @@ impl Ensemble { } let p_back = if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { if bit_i >= rnode.bits.len() { - return Err(EvalError::OtherStr( - "something went wrong with rnode bitwidth", - )); + return Err(Error::OtherStr("something went wrong with rnode bitwidth")); } if let Some(p_back) = rnode.bits[bit_i] { p_back } else { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "something went wrong, found `RNode` for evaluator but a bit was pruned", )) } } else { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "could not find thread local `RNode`, probably an `EvalAwi` was used outside of \ the `Epoch` it was created in", )) diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index b5f9ede0..7e808830 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -15,7 +15,7 @@ use crate::{ DynamicValue, Ensemble, PBack, Value, }, epoch::EpochShared, - EvalError, + Error, }; /// Represents a single state that `awint_dag::mimick::Bits` is in at one point @@ -32,7 +32,7 @@ pub struct State { pub op: Op, /// Location where this state is derived from pub location: Option, - pub err: Option, + pub err: Option, /// The number of other `State`s, and only other `State`s, that reference /// this one through the `Op`s pub rc: usize, @@ -86,9 +86,9 @@ impl Stator { } /// Checks that there are no remaining states, then shrinks allocations - pub fn check_clear(&mut self) -> Result<(), EvalError> { + pub fn check_clear(&mut self) -> Result<(), Error> { if !self.states.is_empty() { - return Err(EvalError::OtherStr("states need to be empty")); + return Err(Error::OtherStr("states need to be empty")); } self.states.clear_and_shrink(); self.states_to_lower.clear(); @@ -105,24 +105,24 @@ impl Ensemble { .map(|state| format!("{p_state} {state:#?}")) } - pub fn dec_rc(&mut self, p_state: PState) -> Result<(), EvalError> { + pub fn dec_rc(&mut self, p_state: PState) -> Result<(), Error> { if let Some(state) = self.stator.states.get_mut(p_state) { state.rc = if let Some(x) = state.rc.checked_sub(1) { x } else { - return Err(EvalError::OtherStr("tried to subtract a 0 reference count")) + return Err(Error::OtherStr("tried to subtract a 0 reference count")) }; if state.pruning_allowed() { self.remove_state(p_state)?; } Ok(()) } else { - Err(EvalError::InvalidPtr) + Err(Error::InvalidPtr) } } /// Prunes all states with `pruning_allowed()` - pub fn prune_unused_states(&mut self) -> Result<(), EvalError> { + pub fn prune_unused_states(&mut self) -> Result<(), Error> { let mut adv = self.stator.states.advancer(); while let Some(p_state) = adv.advance(&self.stator.states) { let state = &self.stator.states[p_state]; @@ -133,7 +133,7 @@ impl Ensemble { Ok(()) } - pub fn eval_state(&mut self, p_state: PState) -> Result<(), EvalError> { + pub fn eval_state(&mut self, p_state: PState) -> Result<(), Error> { let state = &self.stator.states[p_state]; let self_w = state.nzbw; let lit_op: Op = Op::translate(&state.op, |lhs: &mut [EAwi], rhs: &[PState]| { @@ -179,14 +179,12 @@ impl Ensemble { for op in operands { writeln!(s, "{:#?},", self.stator.states[op]).unwrap(); } - Err(EvalError::OtherString(format!( + Err(Error::OtherString(format!( "`EvalResult::Noop` evaluation failure on state {} {:#?}\narguments: (\n{})", p_state, state, s ))) } - EvalResult::Unevaluatable | EvalResult::PassUnevaluatable => { - Err(EvalError::Unevaluatable) - } + EvalResult::Unevaluatable | EvalResult::PassUnevaluatable => Err(Error::Unevaluatable), EvalResult::AssertionSuccess => { if let Assert([_]) = state.op { // this can be done because `Assert` is a sink that should not be used by @@ -199,7 +197,7 @@ impl Ensemble { unreachable!() } } - EvalResult::AssertionFailure => Err(EvalError::OtherString(format!( + EvalResult::AssertionFailure => Err(Error::OtherString(format!( "`EvalResult::AssertionFailure` when evaluating state {} {:?}", p_state, state ))), @@ -209,7 +207,7 @@ impl Ensemble { for op in operands { writeln!(s, "{:?},", self.stator.states[op]).unwrap(); } - Err(EvalError::OtherString(format!( + Err(Error::OtherString(format!( "`EvalResult::Error` evaluation failure (\n{:#?}\n) on state {} \ {:#?}\narguments: (\n{})", e, p_state, state, s @@ -220,13 +218,13 @@ impl Ensemble { /// Assuming that the rootward tree from `p_state` is lowered down to the /// elementary `Op`s, this will create the `LNode` network - pub fn dfs_lower_elementary_to_lnodes(&mut self, p_state: PState) -> Result<(), EvalError> { + pub fn dfs_lower_elementary_to_lnodes(&mut self, p_state: PState) -> Result<(), Error> { if let Some(state) = self.stator.states.get(p_state) { if state.lowered_to_lnodes { return Ok(()) } } else { - return Err(EvalError::InvalidPtr) + return Err(Error::InvalidPtr) } self.stator.states[p_state].lowered_to_lnodes = true; let mut path: Vec<(usize, PState)> = vec![(0, p_state)]; @@ -245,18 +243,18 @@ impl Ensemble { Opaque(_, name) => { if let Some(name) = name { if name == "LoopSource" { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "cannot lower LoopSource opaque with no driver, most likely \ some `Loop` or `Net` has been left undriven", )) } - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "cannot lower root opaque with name {name}" ))) } self.initialize_state_bits_if_needed(p_state).unwrap(); } - ref op => return Err(EvalError::OtherString(format!("cannot lower {op:?}"))), + ref op => return Err(Error::OtherString(format!("cannot lower {op:?}"))), } path.pop().unwrap(); if path.is_empty() { @@ -289,7 +287,7 @@ impl Ensemble { } /// Lowers the rootward tree from `p_state` down to `LNode`s - pub fn dfs_lower(epoch_shared: &EpochShared, p_state: PState) -> Result<(), EvalError> { + pub fn dfs_lower(epoch_shared: &EpochShared, p_state: PState) -> Result<(), Error> { Ensemble::dfs_lower_states_to_elementary(epoch_shared, p_state)?; let mut lock = epoch_shared.epoch_data.borrow_mut(); // the state can get removed by the above step @@ -301,7 +299,7 @@ impl Ensemble { } /// Lowers `RNode`s with the `lower_before_pruning` flag - pub fn lower_for_rnodes(epoch_shared: &EpochShared) -> Result<(), EvalError> { + pub fn lower_for_rnodes(epoch_shared: &EpochShared) -> Result<(), Error> { let lock = epoch_shared.epoch_data.borrow(); let mut adv = lock.ensemble.notary.rnodes().advancer(); drop(lock); @@ -338,7 +336,7 @@ impl Ensemble { fn lower_elementary_to_lnodes_intermediate( this: &mut Ensemble, p_state: PState, -) -> Result<(), EvalError> { +) -> Result<(), Error> { this.initialize_state_bits_if_needed(p_state).unwrap(); match this.stator.states[p_state].op { Assert([x]) => { @@ -519,12 +517,12 @@ fn lower_elementary_to_lnodes_intermediate( Opaque(ref v, name) => { if name == Some("LoopSource") { if v.len() != 1 { - return Err(EvalError::OtherStr("cannot lower an undriven `Loop`")) + return Err(Error::OtherStr("cannot lower an undriven `Loop`")) } let p_driver_state = v[0]; let w = this.stator.states[p_state].p_self_bits.len(); if w != this.stator.states[p_driver_state].p_self_bits.len() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "`Loop` has a bitwidth mismatch of looper and driver", )) } @@ -535,14 +533,14 @@ fn lower_elementary_to_lnodes_intermediate( .unwrap(); } } else if let Some(name) = name { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "cannot lower opaque with name \"{name}\"" ))) } else { - return Err(EvalError::OtherStr("cannot lower opaque with no name")) + return Err(Error::OtherStr("cannot lower opaque with no name")) } } - ref op => return Err(EvalError::OtherString(format!("cannot lower {op:?}"))), + ref op => return Err(Error::OtherString(format!("cannot lower {op:?}"))), } Ok(()) } diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index 761b507d..b0af0e80 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -15,7 +15,7 @@ use crate::{ PTNode, State, Stator, TNode, Value, }, triple_arena::{ptr_struct, Arena, SurjectArena}, - EvalError, + Error, }; ptr_struct!(PBack); @@ -103,7 +103,7 @@ impl Ensemble { /// Compresses and shrinks all internal `Ptr`s. Returns an error if the /// optimizer, evaluator, or stator are not empty. - pub fn recast_all_internal_ptrs(&mut self) -> Result<(), EvalError> { + pub fn recast_all_internal_ptrs(&mut self) -> Result<(), Error> { self.optimizer.check_clear()?; self.evaluator.check_clear()?; self.stator.check_clear()?; @@ -116,14 +116,14 @@ impl Ensemble { Referent::ThisEquiv => (), Referent::ThisLNode(p_lnode) => { if let Err(e) = p_lnode.recast(&p_lnode_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in a `Referent::ThisLNode`" ))); } } Referent::ThisTNode(p_tnode) => { if let Err(e) = p_tnode.recast(&p_tnode_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in a `Referent::ThisTNode`" ))); } @@ -131,21 +131,21 @@ impl Ensemble { Referent::ThisStateBit(..) => unreachable!(), Referent::Input(p_lnode) => { if let Err(e) = p_lnode.recast(&p_lnode_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in a `Referent::Input`" ))); } } Referent::LoopDriver(p_tnode) => { if let Err(e) = p_tnode.recast(&p_tnode_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in a `Referent::LoopDriver`" ))); } } Referent::ThisRNode(p_rnode) => { if let Err(e) = p_rnode.recast(&p_rnode_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in a `Referent::ThisRNode`" ))); } @@ -155,29 +155,29 @@ impl Ensemble { let p_back_recaster = self.backrefs.compress_and_shrink_recaster(); if let Err(e) = self.backrefs.recast(&p_back_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in the backrefs" ))); } if let Err(e) = self.notary.recast(&p_back_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in the notary" ))); } if let Err(e) = self.lnodes.recast(&p_back_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in the lnodes" ))); } if let Err(e) = self.tnodes.recast(&p_back_recaster) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "recast error with {e} in the tnodes" ))); } Ok(()) } - pub fn verify_integrity(&self) -> Result<(), EvalError> { + pub fn verify_integrity(&self) -> Result<(), Error> { // return errors in order of most likely to be root cause // first check that equivalences aren't broken by themselves @@ -189,12 +189,12 @@ impl Ensemble { .in_same_set(p_back, equiv.p_self_equiv) .unwrap() { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{equiv:?}.p_self_equiv roundtrip fail" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{equiv:?}.p_self_equiv is invalid" ))) } @@ -202,7 +202,7 @@ impl Ensemble { // `ThisEquiv` for each equivalence surject if let Some(Referent::ThisEquiv) = self.backrefs.get_key(p_back) { if p_back != equiv.p_self_equiv { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{equiv:?}.p_self_equiv roundtrip fail" ))) } @@ -211,15 +211,13 @@ impl Ensemble { // check other kinds of self refs for (p_state, state) in &self.stator.states { if (!state.p_self_bits.is_empty()) && (state.nzbw.get() != state.p_self_bits.len()) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{state:?}.nzbw mismatch with p_self_bits.len" ))) } for operand in state.op.operands() { if !self.stator.states.contains(*operand) { - return Err(EvalError::OtherString(format!( - "{state:?} operand is missing" - ))) + return Err(Error::OtherString(format!("{state:?} operand is missing"))) } } for (inx, p_self_bit) in state.p_self_bits.iter().enumerate() { @@ -228,12 +226,12 @@ impl Ensemble { self.backrefs.get_key(*p_self_bit) { if (p_state != *p_self) || (inx != *inx_self) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{state:?}.p_self_bits roundtrip fail" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{state:?}.p_self_bits is invalid" ))) } @@ -243,27 +241,23 @@ impl Ensemble { for (p_lnode, lnode) in &self.lnodes { if let Some(Referent::ThisLNode(p_self)) = self.backrefs.get_key(lnode.p_self) { if p_lnode != *p_self { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{lnode:?}.p_self roundtrip fail" ))) } } else { - return Err(EvalError::OtherString(format!( - "{lnode:?}.p_self is invalid" - ))) + return Err(Error::OtherString(format!("{lnode:?}.p_self is invalid"))) } } for (p_tnode, tnode) in &self.tnodes { if let Some(Referent::ThisTNode(p_self)) = self.backrefs.get_key(tnode.p_self) { if p_tnode != *p_self { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{tnode:?}.p_self roundtrip fail" ))) } } else { - return Err(EvalError::OtherString(format!( - "{tnode:?}.p_self is invalid" - ))) + return Err(Error::OtherString(format!("{tnode:?}.p_self is invalid"))) } } // check other referent validities @@ -279,7 +273,7 @@ impl Ensemble { Referent::ThisRNode(p_rnode) => !self.notary.rnodes().contains(*p_rnode), }; if invalid { - return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) + return Err(Error::OtherString(format!("{referent:?} is invalid"))) } } // other kinds of validity @@ -290,18 +284,18 @@ impl Ensemble { if let Some(referent) = self.backrefs.get_key(p_input) { if let Referent::Input(referent) = referent { if !self.lnodes.contains(*referent) { - res = Err(EvalError::OtherString(format!( + res = Err(Error::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} referrent {referent} is \ invalid" ))); } } else { - res = Err(EvalError::OtherString(format!( + res = Err(Error::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} has incorrect referrent" ))); } } else { - res = Err(EvalError::OtherString(format!( + res = Err(Error::OtherString(format!( "{p_lnode}: {lnode:?} input {p_input} is invalid" ))); } @@ -313,17 +307,17 @@ impl Ensemble { if let Some(referent) = self.backrefs.get_key(tnode.p_driver) { if let Referent::LoopDriver(p_driver) = referent { if !self.tnodes.contains(*p_driver) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{p_tnode}: {tnode:?} loop driver referrent {p_driver} is invalid" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{p_tnode}: {tnode:?} loop driver has incorrect referrent" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{p_tnode}: {tnode:?} loop driver {} is invalid", tnode.p_driver ))) @@ -335,17 +329,17 @@ impl Ensemble { if let Some(referent) = self.backrefs.get_key(*p_back) { if let Referent::ThisRNode(p_rnode) = referent { if !self.notary.rnodes().contains(*p_rnode) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{rnode:?} backref {p_rnode} is invalid" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{rnode:?} backref {p_back} has incorrect referrent" ))) } } else { - return Err(EvalError::OtherString(format!("rnode {p_back} is invalid"))) + return Err(Error::OtherString(format!("rnode {p_back} is invalid"))) } } } @@ -400,9 +394,7 @@ impl Ensemble { } }; if fail { - return Err(EvalError::OtherString(format!( - "{referent:?} roundtrip fail" - ))) + return Err(Error::OtherString(format!("{referent:?} roundtrip fail"))) } } // non-pointer invariants @@ -411,30 +403,30 @@ impl Ensemble { LNodeKind::Copy(_) => (), LNodeKind::Lut(inp, lut) => { if inp.is_empty() { - return Err(EvalError::OtherStr("no inputs for lookup table")) + return Err(Error::OtherStr("no inputs for lookup table")) } if !lut.bw().is_power_of_two() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "lookup table is not a power of two in bitwidth", )) } if (lut.bw().trailing_zeros() as usize) != inp.len() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "number of inputs does not correspond to lookup table size", )) } } LNodeKind::DynamicLut(inp, lut) => { if inp.is_empty() { - return Err(EvalError::OtherStr("no inputs for lookup table")) + return Err(Error::OtherStr("no inputs for lookup table")) } if !lut.len().is_power_of_two() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "lookup table is not a power of two in bitwidth", )) } if (lut.len().trailing_zeros() as usize) != inp.len() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "number of inputs does not correspond to lookup table size", )) } @@ -451,7 +443,7 @@ impl Ensemble { } for (p_state, state) in &self.stator.states { if state.rc != counts[p_state] { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "{p_state} {state:?} reference count mismatch", )) } @@ -654,7 +646,7 @@ impl Ensemble { Some(p_tnode) } - pub fn union_equiv(&mut self, p_equiv0: PBack, p_equiv1: PBack) -> Result<(), EvalError> { + pub fn union_equiv(&mut self, p_equiv0: PBack, p_equiv1: PBack) -> Result<(), Error> { let (equiv0, equiv1) = self.backrefs.get2_val_mut(p_equiv0, p_equiv1).unwrap(); if (equiv0.val.is_const() && equiv1.val.is_const()) && (equiv0.val != equiv1.val) { panic!("tried to merge two const equivalences with differing values"); @@ -681,7 +673,7 @@ impl Ensemble { } else if !equiv1.val.is_known() { equiv1.val = equiv0.val; } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "inconsistent value merging:\n{equiv0:?}\n{equiv1:?}" ))); } @@ -696,9 +688,9 @@ impl Ensemble { /// Triggers a cascade of state removals if `pruning_allowed()` and /// their reference counts are zero - pub fn remove_state(&mut self, p_state: PState) -> Result<(), EvalError> { + pub fn remove_state(&mut self, p_state: PState) -> Result<(), Error> { if !self.stator.states.contains(p_state) { - return Err(EvalError::InvalidPtr); + return Err(Error::InvalidPtr); } let mut pstate_stack = vec![p_state]; while let Some(p) = pstate_stack.pop() { @@ -712,7 +704,7 @@ impl Ensemble { for i in 0..self.stator.states[p].op.operands_len() { let op = self.stator.states[p].op.operands()[i]; if self.stator.states[op].dec_rc().is_none() { - return Err(EvalError::OtherStr("tried to subtract a 0 reference count")) + return Err(Error::OtherStr("tried to subtract a 0 reference count")) }; pstate_stack.push(op); } @@ -727,7 +719,7 @@ impl Ensemble { Ok(()) } - pub fn force_remove_all_states(&mut self) -> Result<(), EvalError> { + pub fn force_remove_all_states(&mut self) -> Result<(), Error> { for (_, mut state) in self.stator.states.drain() { for p_self_state in state.p_self_bits.drain(..) { if let Some(p_self_state) = p_self_state { diff --git a/starlight/src/ensemble/value.rs b/starlight/src/ensemble/value.rs index 8962d53a..bbcecbae 100644 --- a/starlight/src/ensemble/value.rs +++ b/starlight/src/ensemble/value.rs @@ -8,7 +8,7 @@ use awint::{ use crate::{ ensemble::{Ensemble, LNode, LNodeKind, PBack, PLNode, PTNode, Referent}, epoch::EpochShared, - EvalError, + Error, }; #[derive(Debug, Clone, Copy)] @@ -223,9 +223,9 @@ impl Evaluator { } /// Checks that there are no remaining evaluations, then shrinks allocations - pub fn check_clear(&mut self) -> Result<(), EvalError> { + pub fn check_clear(&mut self) -> Result<(), Error> { if !self.evaluations.is_empty() { - return Err(EvalError::OtherStr("evaluations need to be empty")); + return Err(Error::OtherStr("evaluations need to be empty")); } self.evaluations.clear_and_shrink(); Ok(()) @@ -584,10 +584,10 @@ impl Ensemble { } } - pub fn change_value(&mut self, p_back: PBack, value: Value) -> Result<(), EvalError> { + pub fn change_value(&mut self, p_back: PBack, value: Value) -> Result<(), Error> { if let Some(equiv) = self.backrefs.get_val_mut(p_back) { if equiv.val.is_const() && (equiv.val != value) { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "tried to change a constant (probably, `retro_const_` was used followed by a \ contradicting `retro_*`", )) @@ -601,14 +601,14 @@ impl Ensemble { equiv.change_visit = self.evaluator.change_visit_gen(); Ok(()) } else { - Err(EvalError::InvalidPtr) + Err(Error::InvalidPtr) } } pub fn calculate_value_with_lower_capability( epoch_shared: &EpochShared, p_back: PBack, - ) -> Result { + ) -> Result { let mut lock = epoch_shared.epoch_data.borrow_mut(); let ensemble = &mut lock.ensemble; if let Some(equiv) = ensemble.backrefs.get_val_mut(p_back) { @@ -640,11 +640,11 @@ impl Ensemble { .unwrap() .val) } else { - Err(EvalError::InvalidPtr) + Err(Error::InvalidPtr) } } - pub fn calculate_value(&mut self, p_back: PBack) -> Result { + pub fn calculate_value(&mut self, p_back: PBack) -> Result { if let Some(equiv) = self.backrefs.get_val_mut(p_back) { if equiv.val.is_const() { return Ok(equiv.val) @@ -663,13 +663,13 @@ impl Ensemble { } Ok(self.backrefs.get_val(p_back).unwrap().val) } else { - Err(EvalError::InvalidPtr) + Err(Error::InvalidPtr) } } pub(crate) fn handle_requests_with_lower_capability( epoch_shared: &EpochShared, - ) -> Result<(), EvalError> { + ) -> Result<(), Error> { // TODO currently, the only way of avoiding N^2 worst case scenarios where // different change cascades lead to large groups of nodes being evaluated // repeatedly, is to use the front strategy. Only a powers of two reduction tree @@ -718,7 +718,7 @@ impl Ensemble { Ok(()) } - pub(crate) fn handle_requests(&mut self) -> Result<(), EvalError> { + pub(crate) fn handle_requests(&mut self) -> Result<(), Error> { while let Some(p_eval) = self.evaluator.evaluations.min() { self.evaluate(p_eval); } diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 25ae1316..3aac6e47 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -179,7 +179,7 @@ pub use awi_structs::{ #[cfg(feature = "debug")] pub use awint::awint_dag::triple_arena_render; pub use awint::{self, awint_dag, awint_dag::triple_arena}; -pub use misc::{EvalError, SmallMap, StarRng}; +pub use misc::{Error, SmallMap, StarRng}; /// Reexports all the regular arbitrary width integer structs, macros, common /// enums, and most of `core::primitive::*`. This is useful for glob importing @@ -202,8 +202,6 @@ pub mod dag { pub use crate::{Loop, Net}; } -// TODO fix the EvalError enum situation - // TODO use modified Lagrangians that appear different to nets with different // requirements on critical path, plus small differencing values to prevent // alternating constraint problems diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index c6b3a2a5..2212b65e 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -17,7 +17,7 @@ use awint::{ }; use super::meta::*; -use crate::{awi, EvalError}; +use crate::{awi, Error}; pub trait LowerManagement { fn graft(&mut self, output_and_operands: &[PState]); @@ -33,14 +33,14 @@ pub fn lower_op( start_op: Op

, out_w: NonZeroUsize, mut m: impl LowerManagement

, -) -> Result { +) -> Result { match start_op { - Invalid => return Err(EvalError::OtherStr("encountered `Invalid` in lowering")), + Invalid => return Err(Error::OtherStr("encountered `Invalid` in lowering")), Opaque(..) | Literal(_) | Assert(_) | Copy(_) | StaticGet(..) | Concat(_) | ConcatFields(_) | Repeat(_) | StaticLut(..) => return Ok(true), Lut([lut, inx]) => { if m.is_literal(lut) { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "this needs to be handled before this function", )); } else { @@ -53,7 +53,7 @@ pub fn lower_op( } Get([bits, inx]) => { if m.is_literal(inx) { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "this needs to be handled before this function", )); } else { @@ -65,7 +65,7 @@ pub fn lower_op( } Set([bits, inx, bit]) => { if m.is_literal(inx) { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "this needs to be handled before this function", )); } else { diff --git a/starlight/src/lower/lower_state.rs b/starlight/src/lower/lower_state.rs index fb006562..3a7d5395 100644 --- a/starlight/src/lower/lower_state.rs +++ b/starlight/src/lower/lower_state.rs @@ -9,17 +9,17 @@ use crate::{ ensemble::Ensemble, epoch::EpochShared, lower::{lower_op, LowerManagement}, - EvalError, + Error, }; impl Ensemble { /// Used for forbidden meta psuedo-DSL techniques in which a single state is /// replaced by more basic states. - pub fn graft(&mut self, p_state: PState, operands: &[PState]) -> Result<(), EvalError> { + pub fn graft(&mut self, p_state: PState, operands: &[PState]) -> Result<(), Error> { #[cfg(debug_assertions)] { if (self.stator.states[p_state].op.operands_len() + 1) != operands.len() { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "wrong number of operands for the `graft` function", )) } @@ -27,20 +27,20 @@ impl Ensemble { let current_nzbw = self.stator.states[operands[i + 1]].nzbw; let current_is_opaque = self.stator.states[operands[i + 1]].op.is_opaque(); if self.stator.states[op].nzbw != current_nzbw { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "operand {}: a bitwidth of {:?} is trying to be grafted to a bitwidth of \ {:?}", i, current_nzbw, self.stator.states[op].nzbw ))) } if !current_is_opaque { - return Err(EvalError::OtherStr( + return Err(Error::OtherStr( "expected an `Opaque` for the `graft` function", )) } } if self.stator.states[p_state].nzbw != self.stator.states[operands[0]].nzbw { - return Err(EvalError::WrongBitwidth) + return Err(Error::WrongBitwidth) } } @@ -67,7 +67,7 @@ impl Ensemble { Ok(()) } - pub fn lower_op(epoch_shared: &EpochShared, p_state: PState) -> Result { + pub fn lower_op(epoch_shared: &EpochShared, p_state: PState) -> Result { struct Tmp<'a> { ptr: PState, epoch_shared: &'a EpochShared, @@ -173,14 +173,14 @@ impl Ensemble { pub fn dfs_lower_states_to_elementary( epoch_shared: &EpochShared, p_state: PState, - ) -> Result<(), EvalError> { + ) -> Result<(), Error> { let mut lock = epoch_shared.epoch_data.borrow_mut(); if let Some(state) = lock.ensemble.stator.states.get(p_state) { if state.lowered_to_elementary { return Ok(()) } } else { - return Err(EvalError::InvalidPtr) + return Err(Error::InvalidPtr) } lock.ensemble.stator.states[p_state].lowered_to_elementary = true; @@ -211,7 +211,7 @@ impl Ensemble { } } // Continue on to lowering - Err(EvalError::Unevaluatable) => (), + Err(Error::Unevaluatable) => (), Err(e) => { lock.ensemble.stator.states[p_state].err = Some(e.clone()); return Err(e) diff --git a/starlight/src/misc.rs b/starlight/src/misc.rs index dc9e01f6..b54fb3d1 100644 --- a/starlight/src/misc.rs +++ b/starlight/src/misc.rs @@ -2,6 +2,6 @@ mod error; mod rng; mod small_map; -pub use error::EvalError; +pub use error::Error; pub use rng::StarRng; pub use small_map::SmallMap; diff --git a/starlight/src/misc/error.rs b/starlight/src/misc/error.rs index 4da713aa..5c75a345 100644 --- a/starlight/src/misc/error.rs +++ b/starlight/src/misc/error.rs @@ -1,8 +1,14 @@ use core::fmt; use std::fmt::Debug; +// TODO in regular cases add errors that lazily produce a formatted output. Keep +// things using `OtherStr` and `OtherString` if they are special cases like +// improper `Epoch` management or internal failures or things like lowering that +// will be changed in the future. Conversely, add special variants for things +// users might match against + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)] -pub enum EvalError { +pub enum Error { // An operand points nowhere, so the DAG is broken #[error("InvalidPtr")] InvalidPtr, @@ -26,7 +32,7 @@ impl<'a> Debug for DisplayStr<'a> { } } -impl Debug for EvalError { +impl Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidPtr => write!(f, "InvalidPtr"), diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 2ca4de99..056f1bd3 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -8,7 +8,7 @@ use crate::{ ensemble::{DynamicValue, Ensemble, LNodeKind}, route::{channel::Referent, Channeler, Configurator, PBack}, triple_arena::ptr_struct, - EvalError, SuspendedEpoch, + Error, SuspendedEpoch, }; ptr_struct!(PCEdge); @@ -50,11 +50,11 @@ impl SelectorLut { } } - pub fn verify_integrity(&self, sources_len: usize, sinks_len: usize) -> Result<(), EvalError> { + pub fn verify_integrity(&self, sources_len: usize, sinks_len: usize) -> Result<(), Error> { // TODO let pow_len = 1usize << self.v.len(); if (pow_len.checked_mul(2).unwrap() != self.awi.bw()) || (sinks_len != 1) { - return Err(EvalError::OtherStr("problem with `SelectorLut` validation")); + return Err(Error::OtherStr("problem with `SelectorLut` validation")); } let mut dynam_len = 0; for i in 0..pow_len { @@ -63,7 +63,7 @@ impl SelectorLut { } } if dynam_len != sources_len { - return Err(EvalError::OtherStr("problem with `SelectorLut` validation")); + return Err(Error::OtherStr("problem with `SelectorLut` validation")); } Ok(()) } @@ -184,16 +184,16 @@ impl Channeler { pub fn from_target( target_epoch: &SuspendedEpoch, configurator: &Configurator, - ) -> Result { + ) -> Result { target_epoch.ensemble(|ensemble| Self::new(ensemble, configurator)) } - pub fn from_program(target_epoch: &SuspendedEpoch) -> Result { + pub fn from_program(target_epoch: &SuspendedEpoch) -> Result { target_epoch.ensemble(|ensemble| Self::new(ensemble, &Configurator::new())) } /// Assumes that the ensemble has been optimized - pub fn new(ensemble: &Ensemble, configurator: &Configurator) -> Result { + pub fn new(ensemble: &Ensemble, configurator: &Configurator) -> Result { let mut channeler = Self::empty(); // for each equivalence make a `CNode` with associated `EnsembleBackref` @@ -237,9 +237,7 @@ impl Channeler { for lnode in ensemble.lnodes.vals() { let p_self = translate(ensemble, &channeler, lnode.p_self).1; match &lnode.kind { - LNodeKind::Copy(_) => { - return Err(EvalError::OtherStr("the epoch was not optimized")) - } + LNodeKind::Copy(_) => return Err(Error::OtherStr("the epoch was not optimized")), LNodeKind::Lut(inp, awi) => { let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); for input in inp { diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 9da39e9d..9cf98794 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -8,7 +8,7 @@ use crate::{ ensemble, route::{CEdge, CNode, PCEdge, Programmability}, triple_arena::ptr_struct, - EvalError, + Error, }; ptr_struct!(P0; PBack); @@ -82,7 +82,7 @@ impl Channeler { } }*/ - pub fn verify_integrity(&self) -> Result<(), EvalError> { + pub fn verify_integrity(&self) -> Result<(), Error> { // return errors in order of most likely to be root cause // first check that surjects self refs aren't broken by themselves @@ -90,12 +90,12 @@ impl Channeler { let cnode = self.cnodes.get_val(p_back).unwrap(); if let Some(Referent::ThisCNode) = self.cnodes.get_key(cnode.p_this_cnode) { if !self.cnodes.in_same_set(p_back, cnode.p_this_cnode).unwrap() { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cnode:?}.p_this_cnode roundtrip fail" ))) } } else { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cnode:?}.p_this_cnode is invalid" ))) } @@ -103,7 +103,7 @@ impl Channeler { // `ThisCNode` for each surject if let Some(Referent::ThisCNode) = self.cnodes.get_key(p_back) { if p_back != cnode.p_this_cnode { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cnode:?}.p_this_cnode roundtrip fail" ))) } @@ -120,12 +120,12 @@ impl Channeler { if let Some(cedges) = self.cedges.get(*p_cedge) { if *is_sink { if *i > cedges.sinks().len() { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{referent:?} roundtrip out of bounds" ))) } } else if *i > cedges.sources().len() { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{referent:?} roundtrip out of bounds" ))) } @@ -137,21 +137,21 @@ impl Channeler { Referent::EnsembleBackRef(_) => false, }; if invalid { - return Err(EvalError::OtherString(format!("{referent:?} is invalid"))) + return Err(Error::OtherString(format!("{referent:?} is invalid"))) } } for p_cedge in self.cedges.ptrs() { let cedge = self.cedges.get(p_cedge).unwrap(); for p_cnode in cedge.sources().iter() { if !self.cnodes.contains(*p_cnode) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cedge:?}.p_cnodes {p_cnode} is invalid", ))) } } for p_cnode in cedge.sinks().iter() { if !self.cnodes.contains(*p_cnode) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cedge:?}.p_cnodes {p_cnode} is invalid", ))) } @@ -159,7 +159,7 @@ impl Channeler { } for p_cnode in &self.top_level_cnodes { if !self.cnodes.contains(*p_cnode) { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "top_level_cnodes {p_cnode} is invalid" ))) } @@ -206,9 +206,7 @@ impl Channeler { Referent::EnsembleBackRef(_) => todo!(), }; if fail { - return Err(EvalError::OtherString(format!( - "{referent:?} roundtrip fail" - ))) + return Err(Error::OtherString(format!("{referent:?} roundtrip fail"))) } } // non `Ptr` validities @@ -238,7 +236,7 @@ impl Channeler { Programmability::Bulk(_) => todo!(), }; if !ok { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "{cedge:?} an invariant is broken" ))) } diff --git a/starlight/src/route/config.rs b/starlight/src/route/config.rs index 45258deb..c0cbb345 100644 --- a/starlight/src/route/config.rs +++ b/starlight/src/route/config.rs @@ -2,7 +2,7 @@ use awint::awint_dag::triple_arena::{ptr_struct, OrdArena}; use crate::{ ensemble::{self, Ensemble, PExternal}, - EvalError, LazyAwi, + Error, LazyAwi, }; ptr_struct!(PConfig); @@ -42,7 +42,7 @@ impl Configurator { &mut self, ensemble: &Ensemble, config: &LazyAwi, - ) -> Result<(), EvalError> { + ) -> Result<(), Error> { let p_external = config.p_external(); if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { for (bit_i, bit) in rnode.bits.iter().enumerate() { @@ -56,7 +56,7 @@ impl Configurator { // we may want to allow this, if we have a mechanism to make sure they are set // to the same thing if replaced.is_some() { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "`make_configurable(.., {config:?})`: found that the same bit as a \ previous one is configurable, this may be because \ `make_configurable` was called twice on the same or equivalent bit" @@ -66,7 +66,7 @@ impl Configurator { } Ok(()) } else { - Err(EvalError::OtherString(format!( + Err(Error::OtherString(format!( "`make_configurable(.., {config:?})`: could not find the `config` in the \ `Ensemble` (probably, you are using something from the program ensemble instead \ of the target ensemble)" diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index b7f3010f..407a1699 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -4,7 +4,7 @@ use crate::{ ensemble::{self, Ensemble, PExternal}, route::{Channeler, HyperPath, PHyperPath}, triple_arena::Arena, - EvalAwi, EvalError, LazyAwi, SuspendedEpoch, + Error, EvalAwi, LazyAwi, SuspendedEpoch, }; ptr_struct!(PMapping); @@ -49,13 +49,13 @@ impl Router { /// Tell the router what program input bits we want to map to what target /// input bits - pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), EvalError> { + pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), Error> { if let Some((_, program_rnode)) = self.program_ensemble.notary.get_rnode(program) { if let Some((_, target_rnode)) = self.target_ensemble.notary.get_rnode(target) { let len0 = program_rnode.bits.len(); let len1 = target_rnode.bits.len(); if len0 != len1 { - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "when mapping bits, found that the bitwidths of {program:?} ({len0}) and \ {target:?} ({len1}) differ" ))); @@ -96,7 +96,7 @@ impl Router { (None, None) => (), _ => { // maybe it should just be a no-op? haven't encountered a case yet - return Err(EvalError::OtherString(format!( + return Err(Error::OtherString(format!( "when mapping bits {program:?} and {target:?}, one or the other \ bits were optimized away inconsistently" ))); @@ -105,12 +105,12 @@ impl Router { } Ok(()) } else { - Err(EvalError::OtherString(format!( + Err(Error::OtherString(format!( "when mapping bits, could not find {target:?} in the target `Ensemble`" ))) } } else { - Err(EvalError::OtherString(format!( + Err(Error::OtherString(format!( "when mapping bits, could not find {program:?} in the program `Ensemble`" ))) } @@ -118,17 +118,17 @@ impl Router { /// Tell the router what program input bits we want to map to what target /// input bits - pub fn map_lazy(&mut self, program: &LazyAwi, target: &LazyAwi) -> Result<(), EvalError> { + pub fn map_lazy(&mut self, program: &LazyAwi, target: &LazyAwi) -> Result<(), Error> { self.map_rnodes(program.p_external(), target.p_external()) } /// Tell the router what program output bits we want to map to what target /// output bits - pub fn map_eval(&mut self, program: &EvalAwi, target: &EvalAwi) -> Result<(), EvalError> { + pub fn map_eval(&mut self, program: &EvalAwi, target: &EvalAwi) -> Result<(), Error> { self.map_rnodes(program.p_external(), target.p_external()) } - pub fn verify_integrity(&self) -> Result<(), EvalError> { + pub fn verify_integrity(&self) -> Result<(), Error> { self.target_ensemble.verify_integrity()?; self.target_channeler.verify_integrity()?; self.program_ensemble.verify_integrity()?; diff --git a/testcrate/src/lib.rs b/testcrate/src/lib.rs index f167a9f7..f1a54b81 100644 --- a/testcrate/src/lib.rs +++ b/testcrate/src/lib.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; -use starlight::{Epoch, EvalError}; +use starlight::{Epoch, Error}; -pub fn _render(epoch: &Epoch) -> Result<(), EvalError> { +pub fn _render(epoch: &Epoch) -> Result<(), Error> { epoch.render_to_svgs_in_dir(PathBuf::from("./".to_owned())) } From f17fb729bbc940ccabbaafe07f987044f72e17bc Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 10 Jan 2024 13:43:48 -0600 Subject: [PATCH 100/119] fix bugs --- starlight/src/ensemble/debug.rs | 4 +- starlight/src/route.rs | 2 + starlight/src/route/channel.rs | 31 +++++++---- starlight/src/route/config.rs | 6 +++ starlight/src/route/debug.rs | 94 +++++++++++++++++++++++++++++++++ starlight/src/route/router.rs | 30 +++++++++++ 6 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 starlight/src/route/debug.rs diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index aacacfd2..7e21df00 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -95,11 +95,11 @@ pub struct RNodeTmp { #[derive(Debug, Clone)] pub enum NodeKind { + Equiv(Equiv, Vec), StateBit(StateBit), + RNode(RNodeTmp), LNode(LNode), TNode(TNodeTmp), - Equiv(Equiv, Vec), - RNode(RNodeTmp), Remove, } diff --git a/starlight/src/route.rs b/starlight/src/route.rs index ed65f9b2..212e78e7 100644 --- a/starlight/src/route.rs +++ b/starlight/src/route.rs @@ -4,6 +4,8 @@ mod cedge; mod channel; mod cnode; mod config; +#[cfg(feature = "debug")] +mod debug; mod path; mod region_adv; mod router; diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 9cf98794..5687668f 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -188,22 +188,31 @@ impl Channeler { } Referent::CEdgeIncidence(p_cedge, i, is_sink) => { let cedge = self.cedges.get(*p_cedge).unwrap(); - let mut res = false; - cedge.incidents(|incident| { - let p_cnode = cedge.sinks()[*i]; + if *is_sink { + if let Some(sink) = cedge.sinks().get(*i) { + if let Referent::CEdgeIncidence(p_cedge1, i1, is_sink1) = + self.cnodes.get_key(*sink).unwrap() + { + (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) + } else { + true + } + } else { + true + } + } else if let Some(source) = cedge.sources().get(*i) { if let Referent::CEdgeIncidence(p_cedge1, i1, is_sink1) = - self.cnodes.get_key(p_cnode).unwrap() + self.cnodes.get_key(*source).unwrap() { - if (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) { - res = true; - } + (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) } else { - res = true; + true } - }); - res + } else { + true + } } - Referent::EnsembleBackRef(_) => todo!(), + Referent::EnsembleBackRef(_) => false, }; if fail { return Err(Error::OtherString(format!("{referent:?} roundtrip fail"))) diff --git a/starlight/src/route/config.rs b/starlight/src/route/config.rs index c0cbb345..f14b14ca 100644 --- a/starlight/src/route/config.rs +++ b/starlight/src/route/config.rs @@ -45,6 +45,12 @@ impl Configurator { ) -> Result<(), Error> { let p_external = config.p_external(); if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { + if rnode.bits.is_empty() { + return Err(Error::OtherStr( + "`make_configurable(.., {config:?})`: found that the epoch has not been \ + lowered and preferably optimized", + )); + } for (bit_i, bit) in rnode.bits.iter().enumerate() { if let Some(bit) = bit { let p_equiv = ensemble.backrefs.get_val(*bit).unwrap().p_self_equiv; diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs new file mode 100644 index 00000000..1aa95f6c --- /dev/null +++ b/starlight/src/route/debug.rs @@ -0,0 +1,94 @@ +use std::path::PathBuf; + +use awint::awint_dag::{ + triple_arena::{Advancer, Arena, ChainArena}, + triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, +}; + +use super::{channel::Referent, Channeler}; +use crate::{ + ensemble, + route::{CNode, PBack, PCEdge}, + Error, +}; + +#[derive(Debug, Clone)] +pub enum NodeKind { + CNode(CNode, Vec, Vec), + SubNode(PBack), + SuperNode(PBack), + CEdgeIncidence(PCEdge, usize, bool), + EnsembleBackRef(ensemble::PBack), + Remove, +} + +impl DebugNodeTrait for NodeKind { + fn debug_node(p_this: PBack, this: &Self) -> DebugNode { + match this { + NodeKind::CNode(..) => DebugNode { + sources: vec![], + center: { todo!() }, + sinks: vec![], + }, + NodeKind::SubNode(_) => todo!(), + NodeKind::SuperNode(_) => todo!(), + NodeKind::CEdgeIncidence(..) => todo!(), + NodeKind::EnsembleBackRef(_) => todo!(), + NodeKind::Remove => panic!("should have been removed"), + } + } +} + +impl Channeler { + pub fn to_debug(&self) -> Arena { + let mut arena = Arena::::new(); + self.cnodes + .clone_keys_to_arena(&mut arena, |p_self, referent| match referent { + Referent::ThisCNode => todo!(), + Referent::SubNode(_) => todo!(), + Referent::SuperNode(_) => todo!(), + Referent::CEdgeIncidence(..) => todo!(), + Referent::EnsembleBackRef(_) => todo!(), + }); + let mut adv = arena.advancer(); + while let Some(p) = adv.advance(&arena) { + if let NodeKind::Remove = arena.get(p).unwrap() { + arena.remove(p).unwrap(); + } + } + arena + } + + pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), Error> { + let dir = match out_file.canonicalize() { + Ok(o) => { + if !o.is_dir() { + return Err(Error::OtherStr("need a directory not a file")); + } + o + } + Err(e) => { + return Err(Error::OtherString(format!("{e:?}"))); + } + }; + let mut ensemble_file = dir.clone(); + ensemble_file.push("ensemble.svg"); + let mut state_file = dir; + state_file.push("states.svg"); + let res = self.verify_integrity(); + render_to_svg_file(&self.to_debug(), false, ensemble_file).unwrap(); + res + } + + pub fn backrefs_to_chain_arena(&self) -> ChainArena { + let mut chain_arena = ChainArena::new(); + self.cnodes + .clone_keys_to_chain_arena(&mut chain_arena, |_, p_lnode| *p_lnode); + chain_arena + } + + pub fn eprint_debug_summary(&self) { + let chain_arena = self.backrefs_to_chain_arena(); + eprintln!("chain_arena: {:#?}", chain_arena); + } +} diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 407a1699..0dc57dc4 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -47,11 +47,41 @@ impl Router { } } + pub fn target_ensemble(&self) -> &Ensemble { + &self.target_ensemble + } + + pub fn program_ensemble(&self) -> &Ensemble { + &self.program_ensemble + } + + pub fn target_channeler(&self) -> &Channeler { + &self.target_channeler + } + + pub fn program_channeler(&self) -> &Channeler { + &self.program_channeler + } + /// Tell the router what program input bits we want to map to what target /// input bits pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), Error> { if let Some((_, program_rnode)) = self.program_ensemble.notary.get_rnode(program) { + if program_rnode.bits.is_empty() { + return Err(Error::OtherString( + "when mapping bits, found that the program epoch has not been lowered or \ + preferably optimized" + .to_owned(), + )); + } if let Some((_, target_rnode)) = self.target_ensemble.notary.get_rnode(target) { + if target_rnode.bits.is_empty() { + return Err(Error::OtherString( + "when mapping bits, found that the program epoch has not been lowered or \ + preferably optimized" + .to_owned(), + )); + } let len0 = program_rnode.bits.len(); let len1 = target_rnode.bits.len(); if len0 != len1 { From e20c494d6a23a99264f6ddfe7ced6a55397d7964 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 10 Jan 2024 13:55:45 -0600 Subject: [PATCH 101/119] improve `RNode` publicity --- starlight/src/ensemble/debug.rs | 8 +++--- starlight/src/ensemble/optimize.rs | 24 +++++++++-------- starlight/src/ensemble/rnode.rs | 31 +++++++++++++++++++--- starlight/src/ensemble/together.rs | 34 +++++++++++++----------- starlight/src/route/config.rs | 42 ++++++++++++++++-------------- starlight/src/route/router.rs | 23 +++++++++------- 6 files changed, 100 insertions(+), 62 deletions(-) diff --git a/starlight/src/ensemble/debug.rs b/starlight/src/ensemble/debug.rs index 7e21df00..e0c21171 100644 --- a/starlight/src/ensemble/debug.rs +++ b/starlight/src/ensemble/debug.rs @@ -261,9 +261,11 @@ impl Ensemble { Referent::ThisRNode(p_rnode) => { let rnode = self.notary.rnodes().get_val(*p_rnode).unwrap(); let mut inx = u64::MAX; - for (i, bit) in rnode.bits.iter().enumerate() { - if *bit == Some(p_self) { - inx = u64::try_from(i).unwrap(); + if let Some(bits) = rnode.bits() { + for (i, bit) in bits.iter().enumerate() { + if *bit == Some(p_self) { + inx = u64::try_from(i).unwrap(); + } } } let equiv = self.backrefs.get_val(p_self).unwrap(); diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 9c72f553..100bdf41 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -485,7 +485,7 @@ impl Ensemble { } Referent::ThisRNode(p_rnode) => { let rnode = self.notary.rnodes().get(p_rnode).unwrap().1; - if !rnode.read_only { + if !rnode.read_only() { possible_drivers = true; } non_self_rc += 1; @@ -668,16 +668,18 @@ impl Ensemble { Referent::ThisRNode(p_rnode) => { let rnode = self.notary.get_rnode_by_p_rnode_mut(p_rnode).unwrap(); let mut found = false; - for bit in &mut rnode.bits { - if let Some(bit) = bit { - if *bit == p_back { - let p_back_new = self - .backrefs - .insert_key(p_source, Referent::ThisRNode(p_rnode)) - .unwrap(); - *bit = p_back_new; - found = true; - break + if let Some(bits) = rnode.bits_mut() { + for bit in bits { + if let Some(bit) = bit { + if *bit == p_back { + let p_back_new = self + .backrefs + .insert_key(p_source, Referent::ThisRNode(p_rnode)) + .unwrap(); + *bit = p_back_new; + found = true; + break + } } } } diff --git a/starlight/src/ensemble/rnode.rs b/starlight/src/ensemble/rnode.rs index feecd9d8..732a7002 100644 --- a/starlight/src/ensemble/rnode.rs +++ b/starlight/src/ensemble/rnode.rs @@ -24,9 +24,9 @@ ptr_struct!( /// after `State` pruning #[derive(Debug, Clone)] pub struct RNode { - pub nzbw: NonZeroUsize, - pub bits: SmallVec<[Option; 1]>, - pub read_only: bool, + nzbw: NonZeroUsize, + bits: SmallVec<[Option; 1]>, + read_only: bool, pub associated_state: Option, pub lower_before_pruning: bool, } @@ -55,6 +55,31 @@ impl RNode { lower_before_pruning, } } + + pub fn nzbw(&self) -> NonZeroUsize { + self.nzbw + } + + pub fn read_only(&self) -> bool { + self.read_only + } + + /// Returns `None` if the `RNode` has not been initialized yet + pub fn bits(&self) -> Option<&[Option]> { + if self.bits.is_empty() { + None + } else { + Some(&self.bits) + } + } + + pub fn bits_mut(&mut self) -> Option<&mut [Option]> { + if self.bits.is_empty() { + None + } else { + Some(&mut self.bits) + } + } } /// Used for managing external references diff --git a/starlight/src/ensemble/together.rs b/starlight/src/ensemble/together.rs index b0af0e80..c411668a 100644 --- a/starlight/src/ensemble/together.rs +++ b/starlight/src/ensemble/together.rs @@ -324,22 +324,24 @@ impl Ensemble { } } for rnode in self.notary.rnodes().vals() { - for p_back in &rnode.bits { - if let Some(p_back) = p_back { - if let Some(referent) = self.backrefs.get_key(*p_back) { - if let Referent::ThisRNode(p_rnode) = referent { - if !self.notary.rnodes().contains(*p_rnode) { + if let Some(bits) = rnode.bits() { + for p_back in bits { + if let Some(p_back) = p_back { + if let Some(referent) = self.backrefs.get_key(*p_back) { + if let Referent::ThisRNode(p_rnode) = referent { + if !self.notary.rnodes().contains(*p_rnode) { + return Err(Error::OtherString(format!( + "{rnode:?} backref {p_rnode} is invalid" + ))) + } + } else { return Err(Error::OtherString(format!( - "{rnode:?} backref {p_rnode} is invalid" + "{rnode:?} backref {p_back} has incorrect referrent" ))) } } else { - return Err(Error::OtherString(format!( - "{rnode:?} backref {p_back} has incorrect referrent" - ))) + return Err(Error::OtherString(format!("rnode {p_back} is invalid"))) } - } else { - return Err(Error::OtherString(format!("rnode {p_back} is invalid"))) } } } @@ -384,10 +386,12 @@ impl Ensemble { Referent::ThisRNode(p_rnode) => { let rnode = self.notary.rnodes().get_val(*p_rnode).unwrap(); let mut found = false; - for bit in &rnode.bits { - if *bit == Some(p_back) { - found = true; - break + if let Some(bits) = rnode.bits() { + for bit in bits { + if *bit == Some(p_back) { + found = true; + break + } } } !found diff --git a/starlight/src/route/config.rs b/starlight/src/route/config.rs index f14b14ca..a6d84b3d 100644 --- a/starlight/src/route/config.rs +++ b/starlight/src/route/config.rs @@ -45,31 +45,33 @@ impl Configurator { ) -> Result<(), Error> { let p_external = config.p_external(); if let Some((_, rnode)) = ensemble.notary.get_rnode(p_external) { - if rnode.bits.is_empty() { + if let Some(bits) = rnode.bits() { + for (bit_i, bit) in bits.iter().enumerate() { + if let Some(bit) = bit { + let p_equiv = ensemble.backrefs.get_val(*bit).unwrap().p_self_equiv; + let (_, replaced) = self.configurations.insert(p_equiv, Config { + p_external, + bit_i, + value: None, + }); + // we may want to allow this, if we have a mechanism to make sure they are + // set to the same thing + if replaced.is_some() { + return Err(Error::OtherString(format!( + "`make_configurable(.., {config:?})`: found that the same bit as \ + a previous one is configurable, this may be because \ + `make_configurable` was called twice on the same or equivalent \ + bit" + ))); + } + } + } + } else { return Err(Error::OtherStr( "`make_configurable(.., {config:?})`: found that the epoch has not been \ lowered and preferably optimized", )); } - for (bit_i, bit) in rnode.bits.iter().enumerate() { - if let Some(bit) = bit { - let p_equiv = ensemble.backrefs.get_val(*bit).unwrap().p_self_equiv; - let (_, replaced) = self.configurations.insert(p_equiv, Config { - p_external, - bit_i, - value: None, - }); - // we may want to allow this, if we have a mechanism to make sure they are set - // to the same thing - if replaced.is_some() { - return Err(Error::OtherString(format!( - "`make_configurable(.., {config:?})`: found that the same bit as a \ - previous one is configurable, this may be because \ - `make_configurable` was called twice on the same or equivalent bit" - ))); - } - } - } Ok(()) } else { Err(Error::OtherString(format!( diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 0dc57dc4..0121dd54 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -67,33 +67,36 @@ impl Router { /// input bits pub fn map_rnodes(&mut self, program: PExternal, target: PExternal) -> Result<(), Error> { if let Some((_, program_rnode)) = self.program_ensemble.notary.get_rnode(program) { - if program_rnode.bits.is_empty() { + let program_rnode_bits = if let Some(bits) = program_rnode.bits() { + bits + } else { return Err(Error::OtherString( "when mapping bits, found that the program epoch has not been lowered or \ preferably optimized" .to_owned(), )); - } + }; if let Some((_, target_rnode)) = self.target_ensemble.notary.get_rnode(target) { - if target_rnode.bits.is_empty() { + let target_rnode_bits = if let Some(bits) = target_rnode.bits() { + bits + } else { return Err(Error::OtherString( - "when mapping bits, found that the program epoch has not been lowered or \ + "when mapping bits, found that the target epoch has not been lowered or \ preferably optimized" .to_owned(), )); - } - let len0 = program_rnode.bits.len(); - let len1 = target_rnode.bits.len(); + }; + let len0 = program_rnode_bits.len(); + let len1 = target_rnode_bits.len(); if len0 != len1 { return Err(Error::OtherString(format!( "when mapping bits, found that the bitwidths of {program:?} ({len0}) and \ {target:?} ({len1}) differ" ))); } - for (bit_i, the_two) in program_rnode - .bits + for (bit_i, the_two) in program_rnode_bits .iter() - .zip(target_rnode.bits.iter()) + .zip(target_rnode_bits.iter()) .enumerate() { match the_two { From b31c6ee939382c47ed08cb56cf6c78faa1c1cc36 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 10 Jan 2024 16:57:33 -0600 Subject: [PATCH 102/119] improve debuggability --- starlight/src/route/cedge.rs | 8 ++ starlight/src/route/debug.rs | 200 +++++++++++++++++++++++++++++++---- 2 files changed, 186 insertions(+), 22 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 056f1bd3..73cf81bc 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -130,6 +130,14 @@ impl CEdge { &self.sinks } + pub fn sources_mut(&mut self) -> &mut [PBack] { + &mut self.sources + } + + pub fn sinks_mut(&mut self) -> &mut [PBack] { + &mut self.sinks + } + pub fn incidents(&self, mut f: F) { for source in self.sources() { f(*source) diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index 1aa95f6c..b2482603 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -1,54 +1,170 @@ +#![allow(clippy::large_enum_variant)] +#![allow(clippy::vec_init_then_push)] + use std::path::PathBuf; use awint::awint_dag::{ - triple_arena::{Advancer, Arena, ChainArena}, + triple_arena::{Advancer, Arena, ArenaTrait, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, }; -use super::{channel::Referent, Channeler}; +use super::{cedge, Programmability}; use crate::{ ensemble, - route::{CNode, PBack, PCEdge}, + route::{channel::Referent, CEdge, CNode, Channeler, PBack, PCEdge}, Error, }; #[derive(Debug, Clone)] pub enum NodeKind { - CNode(CNode, Vec, Vec), + CNode(CNode), SubNode(PBack), SuperNode(PBack), - CEdgeIncidence(PCEdge, usize, bool), - EnsembleBackRef(ensemble::PBack), + CEdgeIncidence(PBack, PCEdge, usize, bool, CEdge, CEdge), + EnsembleBackRef(PBack, ensemble::PBack), Remove, } impl DebugNodeTrait for NodeKind { fn debug_node(p_this: PBack, this: &Self) -> DebugNode { match this { - NodeKind::CNode(..) => DebugNode { + NodeKind::CNode(cnode) => DebugNode { sources: vec![], - center: { todo!() }, + center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, sinks: vec![], }, NodeKind::SubNode(_) => todo!(), NodeKind::SuperNode(_) => todo!(), - NodeKind::CEdgeIncidence(..) => todo!(), - NodeKind::EnsembleBackRef(_) => todo!(), + NodeKind::CEdgeIncidence(p_back, p_cedge, i, is_sink, cedge, cedge_forwarded) => { + DebugNode { + sources: { + let mut v = vec![(*p_back, String::new())]; + for (i, (source, source_forwarded)) in cedge + .sources() + .iter() + .zip(cedge_forwarded.sources().iter()) + .enumerate() + { + v.push((*source_forwarded, format!("{source}"))); + } + v + }, + center: { vec![format!("{p_cedge}"), format!("{i} {is_sink}")] }, + sinks: { + let mut v = vec![]; + for (i, (sink, sink_forwarded)) in cedge + .sinks() + .iter() + .zip(cedge_forwarded.sinks().iter()) + .enumerate() + { + v.push((*sink_forwarded, format!("{sink}"))); + } + v + }, + } + } + NodeKind::EnsembleBackRef(p_back, ensemble_p_back) => DebugNode { + sources: vec![(*p_back, String::new())], + center: { vec!["backref".to_owned(), format!("{ensemble_p_back}")] }, + sinks: vec![], + }, NodeKind::Remove => panic!("should have been removed"), } } } +#[derive(Debug, Clone)] +pub enum HyperNodeKind { + CNode(CNode), + CEdge(CEdge, CEdge), + Remove, +} + +impl DebugNodeTrait for HyperNodeKind { + fn debug_node(p_this: PBack, this: &Self) -> DebugNode { + match this { + HyperNodeKind::CNode(cnode) => DebugNode { + sources: vec![], + center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, + sinks: vec![], + }, + HyperNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { + sources: { + let mut v = vec![]; + for (i, (source, source_forwarded)) in cedge + .sources() + .iter() + .zip(cedge_forwarded.sources().iter()) + .enumerate() + { + v.push((*source_forwarded, format!("{source}"))); + } + v + }, + center: { + let mut v = vec![]; + v.push(match cedge_forwarded.programmability() { + Programmability::Noop => "Noop".to_owned(), + Programmability::StaticLut(_) => "StaticLut".to_owned(), + Programmability::ArbitraryLut(_) => "ArbitraryLut".to_owned(), + Programmability::SelectorLut(_) => "SelectorLut".to_owned(), + Programmability::Bulk(_) => "Bulk".to_owned(), + }); + v + }, + sinks: { + let mut v = vec![]; + for (i, (sink, sink_forwarded)) in cedge + .sinks() + .iter() + .zip(cedge_forwarded.sinks().iter()) + .enumerate() + { + v.push((*sink_forwarded, format!("{sink}"))); + } + v + }, + }, + HyperNodeKind::Remove => panic!("should have been removed"), + } + } +} + impl Channeler { - pub fn to_debug(&self) -> Arena { + pub fn to_cnode_backrefs_debug(&self) -> Arena { let mut arena = Arena::::new(); self.cnodes - .clone_keys_to_arena(&mut arena, |p_self, referent| match referent { - Referent::ThisCNode => todo!(), - Referent::SubNode(_) => todo!(), - Referent::SuperNode(_) => todo!(), - Referent::CEdgeIncidence(..) => todo!(), - Referent::EnsembleBackRef(_) => todo!(), + .clone_keys_to_arena(&mut arena, |p_self, referent| { + let p_cnode = self.cnodes.get_val(p_self).unwrap().clone().p_this_cnode; + match referent { + Referent::ThisCNode => { + NodeKind::CNode(self.cnodes.get_val(p_self).unwrap().clone()) + } + Referent::SubNode(_) => todo!(), + Referent::SuperNode(_) => todo!(), + Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); + let mut cedge_forwarded = cedge.clone(); + for source in cedge_forwarded.sources_mut() { + *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; + } + for sink in cedge_forwarded.sinks_mut() { + *sink = self.cnodes.get_val(*sink).unwrap().p_this_cnode; + } + NodeKind::CEdgeIncidence( + p_cnode, + *p_cedge, + *i, + *is_sink, + cedge, + cedge_forwarded, + ) + } + Referent::EnsembleBackRef(ensemble_p_backref) => { + NodeKind::EnsembleBackRef(p_cnode, *ensemble_p_backref) + } + } }); let mut adv = arena.advancer(); while let Some(p) = adv.advance(&arena) { @@ -59,6 +175,45 @@ impl Channeler { arena } + pub fn to_cnode_graph_debug(&self) -> Arena { + let mut arena = Arena::::new(); + self.cnodes + .clone_keys_to_arena(&mut arena, |p_self, referent| { + let p_cnode = self.cnodes.get_val(p_self).unwrap().clone().p_this_cnode; + match referent { + Referent::ThisCNode => { + HyperNodeKind::CNode(self.cnodes.get_val(p_self).unwrap().clone()) + } + Referent::SubNode(_) => HyperNodeKind::Remove, + Referent::SuperNode(_) => HyperNodeKind::Remove, + Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + // insures that there is only one `CEdge` per set of incidents + if (*i == 0) && *is_sink { + let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); + let mut cedge_forwarded = cedge.clone(); + for source in cedge_forwarded.sources_mut() { + *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; + } + for sink in cedge_forwarded.sinks_mut() { + *sink = self.cnodes.get_val(*sink).unwrap().p_this_cnode; + } + HyperNodeKind::CEdge(cedge, cedge_forwarded) + } else { + HyperNodeKind::Remove + } + } + Referent::EnsembleBackRef(ensemble_p_backref) => HyperNodeKind::Remove, + } + }); + let mut adv = arena.advancer(); + while let Some(p) = adv.advance(&arena) { + if let HyperNodeKind::Remove = arena.get(p).unwrap() { + arena.remove(p).unwrap(); + } + } + arena + } + pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), Error> { let dir = match out_file.canonicalize() { Ok(o) => { @@ -71,12 +226,13 @@ impl Channeler { return Err(Error::OtherString(format!("{e:?}"))); } }; - let mut ensemble_file = dir.clone(); - ensemble_file.push("ensemble.svg"); - let mut state_file = dir; - state_file.push("states.svg"); + let mut cnode_backrefs_file = dir.clone(); + cnode_backrefs_file.push("cnode_backrefs.svg"); + let mut cnode_graph_file = dir; + cnode_graph_file.push("cnode_graph.svg"); let res = self.verify_integrity(); - render_to_svg_file(&self.to_debug(), false, ensemble_file).unwrap(); + render_to_svg_file(&self.to_cnode_backrefs_debug(), false, cnode_backrefs_file).unwrap(); + render_to_svg_file(&self.to_cnode_graph_debug(), false, cnode_graph_file).unwrap(); res } From 5dccd9f5ceb27eec49cfecb80cbe1faeccbc83f8 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 10 Jan 2024 19:03:48 -0600 Subject: [PATCH 103/119] begin generating hierarchy --- starlight/src/route/cedge.rs | 4 +++- starlight/src/route/channel.rs | 37 +++++++++++++++++++++++++++++----- starlight/src/route/cnode.rs | 14 +++++++++++-- starlight/src/route/debug.rs | 20 ++++++++++++------ 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 73cf81bc..1b3a811f 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -6,7 +6,7 @@ use crate::{ awint_dag::smallvec::SmallVec, ensemble, ensemble::{DynamicValue, Ensemble, LNodeKind}, - route::{channel::Referent, Channeler, Configurator, PBack}, + route::{channel::Referent, cnode::generate_hierarchy, Channeler, Configurator, PBack}, triple_arena::ptr_struct, Error, SuspendedEpoch, }; @@ -336,6 +336,8 @@ impl Channeler { } } + generate_hierarchy(&mut channeler); + Ok(channeler) } } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 5687668f..27ba5901 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,6 +1,6 @@ use awint::awint_dag::{ smallvec::smallvec, - triple_arena::{Arena, OrdArena, SurjectArena}, + triple_arena::{Advancer, Arena, OrdArena, SurjectArena}, }; use crate::{ @@ -11,7 +11,7 @@ use crate::{ Error, }; -ptr_struct!(P0; PBack); +ptr_struct!(P0; PTopLevel; PBack); #[derive(Debug, Clone, Copy)] pub enum Referent { @@ -30,7 +30,7 @@ pub struct Channeler { /// The plan is that this always ends up with a single top level node, with /// all unconnected graphs being connected with `Behavior::Noop` so that the /// normal algorithm can allocate over them - pub top_level_cnodes: SmallVec<[PBack; 1]>, + pub top_level_cnodes: OrdArena, // needed for the unit edges to find incidences pub ensemble_backref_to_channeler_backref: OrdArena, } @@ -40,7 +40,7 @@ impl Channeler { Self { cnodes: SurjectArena::new(), cedges: Arena::new(), - top_level_cnodes: smallvec![], + top_level_cnodes: OrdArena::new(), ensemble_backref_to_channeler_backref: OrdArena::new(), } } @@ -157,7 +157,7 @@ impl Channeler { } } } - for p_cnode in &self.top_level_cnodes { + for p_cnode in self.top_level_cnodes.keys() { if !self.cnodes.contains(*p_cnode) { return Err(Error::OtherString(format!( "top_level_cnodes {p_cnode} is invalid" @@ -250,6 +250,33 @@ impl Channeler { ))) } } + for cnode in self.cnodes.vals() { + let contained = self + .top_level_cnodes + .find_key(&cnode.p_this_cnode) + .is_some(); + let mut adv = self.cnodes.advancer_surject(cnode.p_this_cnode); + let mut found_super_node = false; + while let Some(p) = adv.advance(&self.cnodes) { + match self.cnodes.get_key(p).unwrap() { + Referent::SuperNode(_) => { + found_super_node = true; + break + } + _ => (), + } + } + if contained && found_super_node { + return Err(Error::OtherString(format!( + "{cnode:?} has a super node when it is also a top level node" + ))); + } + if !(contained || found_super_node) { + return Err(Error::OtherString(format!( + "{cnode:?} is not top level node but does not have a super node" + ))); + } + } Ok(()) } } diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index 69cb4412..bfafe74f 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -16,6 +16,9 @@ impl Channeler { .cnodes .insert_with(|p_this_cnode| (Referent::ThisCNode, CNode { p_this_cnode })); for subnode in subnodes { + if let Some(p) = self.top_level_cnodes.find_key(&subnode) { + self.top_level_cnodes.remove(p).unwrap(); + } let p_subnode = self .cnodes .insert_key(subnode, Referent::SuperNode(Ptr::invalid())) @@ -28,7 +31,7 @@ impl Channeler { // `p_this_cnode` *self.cnodes.get_key_mut(p_subnode).unwrap() = Referent::SuperNode(p_supernode); } - self.top_level_cnodes.push(p_cnode); + self.top_level_cnodes.insert(p_cnode, ()); p_cnode } } @@ -120,5 +123,12 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // layers, need to methodically determine what we really want ptr_struct!(P0); - todo!() + // FIXME this is a dummy hierarchy + if channeler.top_level_cnodes.len() > 1 { + let mut set = vec![]; + for p_cnode in channeler.top_level_cnodes.keys() { + set.push(*p_cnode); + } + channeler.make_top_level_cnode(set); + } } diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index b2482603..20fcd522 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -18,8 +18,8 @@ use crate::{ #[derive(Debug, Clone)] pub enum NodeKind { CNode(CNode), - SubNode(PBack), - SuperNode(PBack), + SubNode(PBack, PBack), + SuperNode(PBack, PBack), CEdgeIncidence(PBack, PCEdge, usize, bool, CEdge, CEdge), EnsembleBackRef(PBack, ensemble::PBack), Remove, @@ -33,8 +33,16 @@ impl DebugNodeTrait for NodeKind { center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, sinks: vec![], }, - NodeKind::SubNode(_) => todo!(), - NodeKind::SuperNode(_) => todo!(), + NodeKind::SubNode(p_back, p_back_forwarded) => DebugNode { + sources: vec![], + center: { vec!["sub".to_owned()] }, + sinks: vec![(*p_back_forwarded, format!("{p_back}"))], + }, + NodeKind::SuperNode(p_back, p_back_forwarded) => DebugNode { + sources: vec![(*p_back_forwarded, format!("{p_back}"))], + center: { vec!["super".to_owned()] }, + sinks: vec![], + }, NodeKind::CEdgeIncidence(p_back, p_cedge, i, is_sink, cedge, cedge_forwarded) => { DebugNode { sources: { @@ -141,8 +149,8 @@ impl Channeler { Referent::ThisCNode => { NodeKind::CNode(self.cnodes.get_val(p_self).unwrap().clone()) } - Referent::SubNode(_) => todo!(), - Referent::SuperNode(_) => todo!(), + Referent::SubNode(p_back) => NodeKind::SubNode(*p_back, p_cnode), + Referent::SuperNode(p_back) => NodeKind::SuperNode(*p_back, p_cnode), Referent::CEdgeIncidence(p_cedge, i, is_sink) => { let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); let mut cedge_forwarded = cedge.clone(); From 68c04f8f8d5a2fc842084909c2ceba2588a04034 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 12 Jan 2024 22:37:35 -0600 Subject: [PATCH 104/119] Add `RangeOr, RangeAnd, RangeXor`, and `repeat_` --- starlight/src/ensemble/state.rs | 1 - starlight/src/lower/lower_op.rs | 55 +++++++++++++++++++++++ starlight/src/lower/meta.rs | 61 ++++++++++++++++++++++++- starlight/src/route/channel.rs | 9 ++-- testcrate/tests/fuzz_lower.rs | 80 ++++++++++++++++++++++++++------- 5 files changed, 181 insertions(+), 25 deletions(-) diff --git a/starlight/src/ensemble/state.rs b/starlight/src/ensemble/state.rs index 7e808830..fa1e4951 100644 --- a/starlight/src/ensemble/state.rs +++ b/starlight/src/ensemble/state.rs @@ -414,7 +414,6 @@ fn lower_elementary_to_lnodes_intermediate( Repeat([x]) => { let len = this.stator.states[p_state].p_self_bits.len(); let x_w = this.stator.states[x].p_self_bits.len(); - debug_assert!((len % x_w) == 0); let mut from = 0; for to in 0..len { if from >= x_w { diff --git a/starlight/src/lower/lower_op.rs b/starlight/src/lower/lower_op.rs index 2212b65e..579f1243 100644 --- a/starlight/src/lower/lower_op.rs +++ b/starlight/src/lower/lower_op.rs @@ -159,6 +159,61 @@ pub fn lower_op( let out = funnel(&x, &s); m.graft(&[out.state(), x.state(), s.state()]); } + RangeOr([x, start, end]) => { + let x = Awi::opaque(m.get_nzbw(x)); + let start = Awi::opaque(m.get_nzbw(start)); + let end = Awi::opaque(m.get_nzbw(end)); + + let success = Bits::efficient_ule(start.to_usize(), x.bw()).is_some() + & Bits::efficient_ule(end.to_usize(), x.bw()).is_some(); + let max_w = Bits::nontrivial_bits(x.bw()).unwrap(); + let start_small = + Bits::static_field(&Awi::zero(max_w), 0, &start, 0, max_w.get()).unwrap(); + let end_small = Bits::static_field(&Awi::zero(max_w), 0, &end, 0, max_w.get()).unwrap(); + // to achieve a no-op we just need to set the end to 0 + let mut tmp_end = Awi::zero(max_w); + tmp_end.mux_(&end_small, success).unwrap(); + let out = range_or(&x, &start_small, &tmp_end); + m.graft(&[out.state(), x.state(), start.state(), end.state()]); + } + RangeAnd([x, start, end]) => { + let x = Awi::opaque(m.get_nzbw(x)); + let start = Awi::opaque(m.get_nzbw(start)); + let end = Awi::opaque(m.get_nzbw(end)); + + let success = Bits::efficient_ule(start.to_usize(), x.bw()).is_some() + & Bits::efficient_ule(end.to_usize(), x.bw()).is_some(); + let max_w = Bits::nontrivial_bits(x.bw()).unwrap(); + let start_small = + Bits::static_field(&Awi::zero(max_w), 0, &start, 0, max_w.get()).unwrap(); + let end_small = Bits::static_field(&Awi::zero(max_w), 0, &end, 0, max_w.get()).unwrap(); + // to achieve a no-op we need to set a full range with `start` being zero and + // `end` being `x.bw()` + let mut tmp_start = Awi::zero(max_w); + tmp_start.mux_(&start_small, success).unwrap(); + let mut tmp_end = Awi::zero(max_w); + tmp_end.usize_(x.bw()); + tmp_end.mux_(&end_small, success).unwrap(); + let out = range_and(&x, &tmp_start, &tmp_end); + m.graft(&[out.state(), x.state(), start.state(), end.state()]); + } + RangeXor([x, start, end]) => { + let x = Awi::opaque(m.get_nzbw(x)); + let start = Awi::opaque(m.get_nzbw(start)); + let end = Awi::opaque(m.get_nzbw(end)); + + let success = Bits::efficient_ule(start.to_usize(), x.bw()).is_some() + & Bits::efficient_ule(end.to_usize(), x.bw()).is_some(); + let max_w = Bits::nontrivial_bits(x.bw()).unwrap(); + let start_small = + Bits::static_field(&Awi::zero(max_w), 0, &start, 0, max_w.get()).unwrap(); + let end_small = Bits::static_field(&Awi::zero(max_w), 0, &end, 0, max_w.get()).unwrap(); + // to achieve a no-op we just need to set the end to 0 + let mut tmp_end = Awi::zero(max_w); + tmp_end.mux_(&end_small, success).unwrap(); + let out = range_xor(&x, &start_small, &tmp_end); + m.graft(&[out.state(), x.state(), start.state(), end.state()]); + } FieldFrom([lhs, rhs, from, width]) => { let lhs_w = m.get_nzbw(lhs); let rhs_w = m.get_nzbw(rhs); diff --git a/starlight/src/lower/meta.rs b/starlight/src/lower/meta.rs index 3437a5d1..21a36628 100644 --- a/starlight/src/lower/meta.rs +++ b/starlight/src/lower/meta.rs @@ -492,6 +492,62 @@ pub fn funnel(x: &Bits, s: &Bits) -> Awi { concat(out_w, output) } +/// Assumes that `start` and `end` are their small versions. Setting `end` to 0 +/// guarantees a no-op. +pub fn range_or(x: &Bits, start: &Bits, end: &Bits) -> Awi { + // trailing mask that trails `start`, exclusive + let tmask0 = tsmear_inx(start, x.bw()); + // trailing mask that trails `end`, exclusive + let tmask1 = tsmear_inx(end, x.bw()); + + // or with `x` based on the masks, note that any case where `tmask1` is zero + // needs to result in no-op + let mut out = SmallVec::with_capacity(x.bw()); + for i in 0..x.bw() { + let mut signal = inlawi!(0); + static_lut!(signal; 1111_0100; tmask0[i], tmask1[i], x.get(i).unwrap()); + out.push(signal.state()); + } + concat(x.nzbw(), out) +} + +/// Assumes that `start` and `end` are their small versions. Must be set to a +/// full range for a no-op +pub fn range_and(x: &Bits, start: &Bits, end: &Bits) -> Awi { + // trailing mask that trails `start`, exclusive + let tmask0 = tsmear_inx(start, x.bw()); + // trailing mask that trails `end`, exclusive + let tmask1 = tsmear_inx(end, x.bw()); + + // and with `x` based on the masks, the fourth case can be any bit we choose + let mut out = SmallVec::with_capacity(x.bw()); + for i in 0..x.bw() { + let mut signal = inlawi!(0); + static_lut!(signal; 0100_0000; tmask0[i], tmask1[i], x.get(i).unwrap()); + out.push(signal.state()); + } + concat(x.nzbw(), out) +} + +/// Assumes that `start` and `end` are their small versions. Setting `end` to 0 +/// guarantees a no-op. +pub fn range_xor(x: &Bits, start: &Bits, end: &Bits) -> Awi { + // trailing mask that trails `start`, exclusive + let tmask0 = tsmear_inx(start, x.bw()); + // trailing mask that trails `end`, exclusive + let tmask1 = tsmear_inx(end, x.bw()); + + // xor with `x` based on the masks, note that any case where `tmask1` is zero + // needs to result in no-op + let mut out = SmallVec::with_capacity(x.bw()); + for i in 0..x.bw() { + let mut signal = inlawi!(0); + static_lut!(signal; 1011_0100; tmask0[i], tmask1[i], x.get(i).unwrap()); + out.push(signal.state()); + } + concat(x.nzbw(), out) +} + /// Assumes that `from` and `width` is in range, however setting `width` to 0 /// guarantees that nothing happens to `lhs` even with `from` being out of range pub fn field_from(lhs: &Bits, rhs: &Bits, from: &Bits, width: &Bits) -> Awi { @@ -807,7 +863,10 @@ pub fn field_to(lhs: &Bits, to: &Bits, rhs: &Bits, width: &Bits) -> Awi { let mut out = SmallVec::with_capacity(lhs.bw()); for i in 0..lhs.bw() { let mut signal = inlawi!(0); - static_lut!(signal; 1111_1011_0100_0000; lmask[i], tmask[i], funnel_res.get(i).unwrap(), lhs.get(i).unwrap()); + static_lut!( + signal; 1111_1011_0100_0000; + lmask[i], tmask[i], funnel_res.get(i).unwrap(), lhs.get(i).unwrap() + ); out.push(signal.state()); } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 27ba5901..a2d02b0e 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -258,12 +258,9 @@ impl Channeler { let mut adv = self.cnodes.advancer_surject(cnode.p_this_cnode); let mut found_super_node = false; while let Some(p) = adv.advance(&self.cnodes) { - match self.cnodes.get_key(p).unwrap() { - Referent::SuperNode(_) => { - found_super_node = true; - break - } - _ => (), + if let Referent::SuperNode(_) = self.cnodes.get_key(p).unwrap() { + found_super_node = true; + break } } if contained && found_super_node { diff --git a/testcrate/tests/fuzz_lower.rs b/testcrate/tests/fuzz_lower.rs index 5ebdc639..5d8e4685 100644 --- a/testcrate/tests/fuzz_lower.rs +++ b/testcrate/tests/fuzz_lower.rs @@ -182,7 +182,7 @@ impl Mem { // some kind of way to test coverage. fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { - let next_op = rng.next_u32() % 29; + let next_op = rng.next_u32() % 31; match next_op { // Lut, StaticLut 0 => { @@ -381,8 +381,45 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { let b_s = m.get_dag(s); m.get_mut_dag(lhs).funnel_(&b, &b_s).unwrap(); } - // FieldWidth + // RangeOr, RangeAnd, RangeXor 13 => { + let (w, x) = m.next9(); + let start = m.next_usize(w + 1); + let end = m.next_usize(w + 1); + let start_a = m.get_awi(start); + let end_a = m.get_awi(end); + let start_b = m.get_dag(start); + let end_b = m.get_dag(end); + match rng.index(3).unwrap() { + 0 => { + m.get_mut_awi(x) + .range_or_(start_a.to_usize()..end_a.to_usize()) + .unwrap(); + m.get_mut_dag(x) + .range_or_(start_b.to_usize()..end_b.to_usize()) + .unwrap(); + } + 1 => { + m.get_mut_awi(x) + .range_and_(start_a.to_usize()..end_a.to_usize()) + .unwrap(); + m.get_mut_dag(x) + .range_and_(start_b.to_usize()..end_b.to_usize()) + .unwrap(); + } + 2 => { + m.get_mut_awi(x) + .range_xor_(start_a.to_usize()..end_a.to_usize()) + .unwrap(); + m.get_mut_dag(x) + .range_xor_(start_b.to_usize()..end_b.to_usize()) + .unwrap(); + } + _ => unreachable!(), + } + } + // FieldWidth + 14 => { let (w0, lhs) = m.next9(); let (w1, rhs) = m.next9(); let min_w = min(w0, w1); @@ -399,7 +436,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { .unwrap(); } // FieldFrom - 14 => { + 15 => { let (w0, lhs) = m.next9(); let (w1, rhs) = m.next9(); let min_w = min(w0, w1); @@ -419,7 +456,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { .unwrap(); } // Shl, Lshr, Ashr, Rotl, Rotr - 15 => { + 16 => { let (w, x) = m.next9(); let s = m.next_usize(w); let s_a = m.get_awi(s); @@ -449,7 +486,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // FieldTo - 16 => { + 17 => { let (w0, lhs) = m.next9(); let (w1, rhs) = m.next9(); let min_w = min(w0, w1); @@ -469,7 +506,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { .unwrap(); } // Add, Sub, Rsb - 17 => { + 18 => { let (w, lhs) = m.next9(); let rhs = m.next(w); let rhs_a = m.get_awi(rhs); @@ -491,7 +528,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // Field - 18 => { + 19 => { let (w0, lhs) = m.next9(); let (w1, rhs) = m.next9(); let min_w = min(w0, w1); @@ -525,13 +562,13 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { .unwrap(); } // Rev - 19 => { + 20 => { let x = m.next9().1; m.get_mut_awi(x).rev_(); m.get_mut_dag(x).rev_(); } // Eq, Ne, Ult, Ule, Ilt, Ile - 20 => { + 21 => { let (w, lhs) = m.next9(); let rhs = m.next(w); let lhs_a = m.get_awi(lhs); @@ -568,7 +605,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // IsZero, IsUmax, IsImax, IsImin, IsUone - 21 => { + 22 => { let x = m.next9().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); @@ -598,7 +635,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // CountOnes, Lz, Tz, Sig - 22 => { + 23 => { let x = m.next9().1; let x_a = m.get_awi(x); let x_b = m.get_dag(x); @@ -624,7 +661,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // LutSet - 23 => { + 24 => { let (entry_w, entry) = m.next4(); let (inx_w, inx) = m.next4(); let table_w = entry_w * (1 << inx_w); @@ -637,7 +674,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(table).lut_set(&entry_b, &inx_b).unwrap(); } // Resize - 24 => { + 25 => { let lhs = m.next9().1; let rhs = m.next9().1; let b = m.next(1); @@ -649,7 +686,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(lhs).resize_(&rhs_b, b_b.to_bool()); } // ZeroResizeOverflow, SignResizeOverflow - 25 => { + 26 => { let lhs = m.next9().1; let rhs = m.next9().1; let out = m.next(1); @@ -666,7 +703,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // ArbMulAdd - 26 => { + 27 => { let (w, lhs) = m.next9(); match rng.index(3).unwrap() { 0 => { @@ -703,7 +740,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { } } // Mux - 27 => { + 28 => { let (w, lhs) = m.next9(); let rhs = m.next(w); let b = m.next(1); @@ -715,7 +752,7 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(lhs).mux_(&rhs_b, b_b.to_bool()).unwrap(); } // UQuo, URem, IQuo, IRem - 28 => { + 29 => { let (w, duo) = m.next9(); let div = m.next(w); let quo = m.next(w); @@ -748,6 +785,15 @@ fn num_dag_duo(rng: &mut StarRng, m: &mut Mem) { m.get_mut_dag(out0).copy_(&quo_b).unwrap(); m.get_mut_dag(out1).copy_(&rem_b).unwrap(); } + // Repeat + 30 => { + let lhs = m.next9().1; + let rhs = m.next9().1; + let rhs_a = m.get_awi(rhs); + m.get_mut_awi(lhs).repeat_(&rhs_a); + let rhs_b = m.get_dag(rhs); + m.get_mut_dag(lhs).repeat_(&rhs_b); + } _ => unreachable!(), } } From be602a2c3dd6050b92693cf5f670005ca7ee79f4 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Fri, 12 Jan 2024 23:57:30 -0600 Subject: [PATCH 105/119] lookup table input rotation --- starlight/src/ensemble/lnode.rs | 106 ++++++++++++++++++++++++++++- starlight/src/ensemble/optimize.rs | 23 ++++++- starlight/src/route/router.rs | 4 ++ 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 47befd7c..218285df 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -1,6 +1,7 @@ -use std::{mem, num::NonZeroUsize}; +use std::{cmp::max, mem, num::NonZeroUsize}; use awint::{ + awi, awint_dag::{ smallvec, triple_arena::{Recast, Recaster, SurjectArena}, @@ -104,6 +105,58 @@ fn general_reduce_independent_lut(lut: &mut Awi, i: usize) -> bool { } } +/// Returns an equivalent LUT given that inputs `i` and `j` have been +/// swapped with each other +fn general_rotate_lut(lut: &mut Awi, i: usize, j: usize) { + debug_assert!(lut.bw().is_power_of_two()); + debug_assert!(max(i, j) < (lut.bw().trailing_zeros() as usize)); + // rotates the zeroeth input with the `i`th input, `i > 0` + fn general_basis_rotate(lut: &mut Awi, i: usize) { + use awi::*; + // it turns out that the rotation can be broken down into a stationary part, a + // part that shifts left, and a part that shifts right. This generates the + // masks. + let one = inlawi!(01); + let two = inlawi!(10); + let mut tmp0 = Awi::zero(NonZeroUsize::new(1 << i).unwrap()); + let mut tmp1 = Awi::zero(NonZeroUsize::new(2 << i).unwrap()); + let mut mask0 = Awi::zero(lut.nzbw()); + tmp0.repeat_(&two); + tmp1.resize_(&tmp0, false); + mask0.repeat_(&tmp1); + let mut mask1 = Awi::zero(lut.nzbw()); + tmp0.repeat_(&one); + tmp1.field_to(tmp0.bw(), &tmp0, tmp0.bw()).unwrap(); + mask1.repeat_(&tmp1); + let mut mask2 = Awi::zero(lut.nzbw()); + tmp0.repeat_(&one); + tmp1.resize_(&tmp0, false); + tmp0.repeat_(&two); + tmp1.field_to(tmp0.bw(), &tmp0, tmp0.bw()).unwrap(); + mask2.repeat_(&tmp1); + + // apply the masks, shift, then OR them together to get the result + let s = (1 << i) - 1; + mask0.and_(&lut).unwrap(); + mask0.shl_(s).unwrap(); + mask1.and_(&lut).unwrap(); + mask1.lshr_(s).unwrap(); + lut.and_(&mask2).unwrap(); + lut.or_(&mask0).unwrap(); + lut.or_(&mask1).unwrap(); + } + match (i == 0, j == 0) { + (true, true) => (), + (true, false) => general_basis_rotate(lut, j), + (false, true) => general_basis_rotate(lut, i), + (false, false) => { + general_basis_rotate(lut, i); + general_basis_rotate(lut, j); + general_basis_rotate(lut, i); + } + } +} + const M: [u64; 6] = [ 0x5555_5555_5555_5555, 0x3333_3333_3333_3333, @@ -141,6 +194,44 @@ fn reduce_independent64(mut lut: u64, i: usize) -> Option { None } } +const R0: [u64; 5] = [ + 0x2222_2222_2222_2222, + 0x0a0a_0a0a_0a0a_0a0a, + 0x00aa_00aa_00aa_00aa, + 0x0000_aaaa_0000_aaaa, + 0x0000_0000_aaaa_aaaa, +]; +const R1: [u64; 5] = [ + 0x4444_4444_4444_4444, + 0x5050_5050_5050_5050, + 0x5500_5500_5500_5500, + 0x5555_0000_5555_0000, + 0x5555_5555_0000_0000, +]; +const R2: [u64; 5] = [ + 0x9999_9999_9999_9999, + 0xa5a5_a5a5_a5a5_a5a5, + 0xaa55_aa55_aa55_aa55, + 0xaaaa_5555_aaaa_5555, + 0xaaaa_aaaa_5555_5555, +]; +// Rotates the `i`th column with the 0th column, assumes `i > 0` +fn basis_rotate64(lut: u64, i: usize) -> u64 { + debug_assert!((i > 0) && (i < 6)); + let s = (1 << i) - 1; + // it can be broken into a part that shifts left, a part that shifts right, and + // a stationary part + ((lut & R0[i - 1]) << s) | ((lut & R1[i - 1]) >> s) | (lut & R2[i - 1]) +} +// Rotates the `i`th column with the `j`th column +fn rotate64(lut: u64, i: usize, j: usize) -> u64 { + match (i == 0, j == 0) { + (true, true) => lut, + (true, false) => basis_rotate64(lut, j), + (false, true) => basis_rotate64(lut, i), + (false, false) => basis_rotate64(basis_rotate64(basis_rotate64(lut, i), j), i), + } +} impl LNode { pub fn new(p_self: PBack, kind: LNodeKind, lowered_from: Option) -> Self { @@ -321,4 +412,17 @@ impl LNode { } Some((res, removed)) } + + /// Returns an equivalent LUT given that inputs `i` and `j` have been + /// swapped with each other + pub fn rotate_lut(lut: &mut Awi, i: usize, j: usize) { + debug_assert!(lut.bw().is_power_of_two()); + debug_assert!(max(i, j) < (lut.bw().trailing_zeros() as usize)); + if lut.bw() > 64 { + general_rotate_lut(lut, i, j); + } else { + let rotated = rotate64(lut.to_u64(), i, j); + lut.u64_(rotated); + } + } } diff --git a/starlight/src/ensemble/optimize.rs b/starlight/src/ensemble/optimize.rs index 100bdf41..2adf4079 100644 --- a/starlight/src/ensemble/optimize.rs +++ b/starlight/src/ensemble/optimize.rs @@ -179,8 +179,29 @@ impl Ensemble { self.backrefs.remove_key(p_inp).unwrap(); } } + + // TODO the sorting should be done on equiv ptr comparison, or the LUT could be + // canonicalized, or the equivalences could be found on existence of common + // inputs without any sorting + /* // sort inputs so that `LNode`s can be compared later - // TODO? + let mut changed = false; + // TODO want a more efficient sort that is tailored for basis rotations + loop { + for i in 1..inp.len() { + if inp[i - 1] > inp[i] { + changed = true; + inp.swap(i - 1, i); + LNode::rotate_lut(&mut lut, i - 1, i); + } + } + if changed { + changed = false; + } else { + break + } + } + */ // input independence automatically reduces all zeros and all ones LUTs, so just // need to check if the LUT is one bit for constant generation diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 0121dd54..c28379b0 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -168,4 +168,8 @@ impl Router { self.program_channeler.verify_integrity()?; Ok(()) } + + pub fn route(&mut self) -> Result<(), Error> { + Ok(()) + } } From 0ca119548b6ae2f7ac4fe3cb68ba3f975f471f81 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 15 Jan 2024 18:09:27 -0600 Subject: [PATCH 106/119] more defining --- starlight/src/ensemble/lnode.rs | 4 +- starlight/src/route/cedge.rs | 27 +++++- starlight/src/route/cnode.rs | 140 +++++++++++++++----------------- starlight/src/route/path.rs | 9 ++ starlight/src/route/router.rs | 5 ++ 5 files changed, 106 insertions(+), 79 deletions(-) diff --git a/starlight/src/ensemble/lnode.rs b/starlight/src/ensemble/lnode.rs index 218285df..a455b47f 100644 --- a/starlight/src/ensemble/lnode.rs +++ b/starlight/src/ensemble/lnode.rs @@ -137,9 +137,9 @@ fn general_rotate_lut(lut: &mut Awi, i: usize, j: usize) { // apply the masks, shift, then OR them together to get the result let s = (1 << i) - 1; - mask0.and_(&lut).unwrap(); + mask0.and_(lut).unwrap(); mask0.shl_(s).unwrap(); - mask1.and_(&lut).unwrap(); + mask1.and_(lut).unwrap(); mask1.lshr_(s).unwrap(); lut.and_(&mask2).unwrap(); lut.or_(&mask0).unwrap(); diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 1b3a811f..a81740ee 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroUsize; +use std::{cmp::min, num::NonZeroUsize}; use awint::Awi; @@ -153,6 +153,31 @@ impl CEdge { .checked_add(self.sinks().len()) .unwrap() } + + pub fn channel_entry_width(&self) -> usize { + match self.programmability() { + Programmability::Noop => 0, + Programmability::StaticLut(awi) => awi.bw().trailing_zeros() as usize, + Programmability::ArbitraryLut(table) => table.len().trailing_zeros() as usize, + Programmability::SelectorLut(selector_lut) => selector_lut.v.len(), + Programmability::Bulk(bulk) => bulk.channel_entry_width, + } + } + + pub fn channel_exit_width(&self) -> usize { + match self.programmability() { + Programmability::Noop => 0, + Programmability::StaticLut(awi) => 1, + Programmability::ArbitraryLut(table) => 1, + Programmability::SelectorLut(selector_lut) => 1, + Programmability::Bulk(bulk) => bulk.channel_exit_width, + } + } + + /// Takes the minimum of the channel entry width and channel exit width + pub fn channel_width(&self) -> usize { + min(self.channel_entry_width(), self.channel_exit_width()) + } } impl Channeler { diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index bfafe74f..f906cab9 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -36,94 +36,82 @@ impl Channeler { } } -// - A `CNode` cannot have exactly one subnode and must have either zero or at -// least two subnodes -// - the immediate subnodes of a `CNode` must be in a clique with `CEdge`s - /* -consider a loop of `CNode`s like this -0 --- 1 -| | -| | -2 --- 3 - -If higher `CNode`s and edges formed like - - 01 - / \ -02 13 - \ / - 23 - -It could cause an infinite loop, we need to guarantee logarithmic overhead -with `CEdges` being made such that e.x. 02 should connect with 13 because -02 subnodes connect with 1 and 3 which are subnodes of 13. - - 01 - / | \ -02 -- 13 - \ | / - 23 - -the next level is - -0123 - -for larger loops it will be like - -0--1--2--3--4--5--6--7--0 (wraps around to 0) - ___ ___ ___ ___ - / \ / \ / \ / \ - 01-12-23-34-45-56-67-70-01-12 - \ / \ / \ / \ / - -- -- -- -- - -// we do not want this to continue, or else we end up with n^2 space - 0123 2345 4567 6701 - 1234 3456 5670 7012 - -we notice that 12 and 23 share 0.5 of their nodes in common, what we -do is merge a "extended clique" of cliques sharing the edge between -the two nodes, specifically the 01-12-23 clique and the 12-23-34 clique - - ... - 01234-45-56-67-70-01234 - -the 01-12-23 subedges are still in the hierarchy, if the 23-34 edge is selected -for the commonality merge, 01234 is found as a supernode of 34, and the proposed -merge resulting in 12345 shares 12 and 23 with 01234 (if more than or equal to -half of the subnodes are shared with respect to one or the other (2 out of -01,12,23,34 for one or 2 out of 12,23,34,45 for the other), it should not be -made). 34-45 would also be too close. -45-56 however is successful resulting in 34567 which has the desired overlap. -70 is left without a supernode on this level, but it joins a three clique to -result in the final top level node - - ... -01234-34567-70-01234 - -0123457 - -8 -> 8 -> 3 -> 1 seems right, the first reduction is stalling for wider useful -cliques for the descension algorithm, and this is quickly reduced down in -the logarithmic tree we want +here are the current ideas on the channeling hierarchy + +We know we want a hierarchy for the target and a hierarchy for the program. +The routing starts by having an embedding of the single top level program cnode +into the single top level target cnode (modulo handling how we see fit for if +the target and/or program has disconnections). There are different kinds of steps: + +(1.) Program dilution +In one kind of step, a program's embeddings +are "diluted" (as opposed to concentrating when looking from the bottom to the top +of the hierarchy) with a embedding of one program cnode into a target cnode being +broken into an embedding of that program cnode's subnodes into the same target cnode. + +(2.) Target dilution +A program embedding is diluted with respect to the target channeler side, such that an +embedding of a program cnode into a target cnode is broken into an embedding of a program +cnode into a subnode of one of the target cnodes. +There is one step of embedding movement implicit in this, where we choose which +subnode to embed. + +(3.) Embedding movement +As dilution proceeds and we get a higher resolution picture of the final embedding, we +will have to transverse move the embeddings to neighboring target cnodes + +(4.) Concentration +Equivalent to the "rip-up and reroute" process where we find inaccuracies in the +bulk predictions and need to concentrate before retrying dilution. + +The routing process progresses from the initial single top level embedding by first +diluting the program, and then diluting both while maintaining a more dilution +for the program than the target. There are usually multiple program cnodes embedded +into a single target cnode, until the lowest level. + +The questions are: Do we have distinct mandatory levels? Do we have special CEdges +between the levels? Do we have intersections in the subnode sets of cnodes? + +Currently, it seems that we do not want intersections in the subnode cnode and/or cedge sets, +because the concentrated cedges will have redundancies and probably confuse the channel +routing capacities. We treat the supernode and subnode backrefs as zero cost edges +that the hyperpaths can use during intermediate routing. We keep the levels +distinct and do not let there be super/sub node backrefs within the same level, and +we keep an integer for the level with the `CNode`. We try to store all other +information in the `CEdge`s, and alternate exclusive paths are encoded into the +topology instead. + +This allows the Lagrangian routing algorithm to start with completed paths between program-target +mappings, so that we do not constantly have to use maps to look up where we need to be moving loose +endpoints. The Lagrangians can do advanced things by themselves like promoting concentration or +dilution of paths to different cedges when necessary. + +For one more detail, what do we do about disconnected graphs? */ /// Starting from unit `CNode`s and `CEdge`s describing all known low level /// progam methods, this generates a logarithmic tree of higher level -/// `CNode`s and `CEdge`s that results in top level `CNode`s that have no -/// `CEdges` to any other (and unless the graph was disconnected there will -/// be only one top level `CNode`). +/// `CNode`s and `CEdge`s that results in a single top level `CNode` from which +/// routing can start /// /// We are currently assuming that `generate_hierarchy` is being run once on /// a graph of unit channel nodes and edges pub fn generate_hierarchy(channeler: &mut Channeler) { - // TODO currently we are doing a simpler strategy of merging pairs on distinct - // layers, need to methodically determine what we really want ptr_struct!(P0); - // FIXME this is a dummy hierarchy + // For each cnode on a given level, we will attempt to concentrate it and all + // its neighbors. + // + + // when making a new top level cnode, for each subnode of the new node check + // for other supernodes. For each transverse node Tally the number of times + // each transverse node is seen. If it is seen more than once, cancel making + // the new cnode. + + // if there are multiple cnodes are left in an anticlique, concentrate them into + // a single top level node if channeler.top_level_cnodes.len() > 1 { let mut set = vec![]; for p_cnode in channeler.top_level_cnodes.keys() { diff --git a/starlight/src/route/path.rs b/starlight/src/route/path.rs index c38438c1..90a2ca3a 100644 --- a/starlight/src/route/path.rs +++ b/starlight/src/route/path.rs @@ -5,6 +5,15 @@ use crate::{ ptr_struct!(PHyperPath); +pub enum Edge { + /// Points to a `CEdge` + Transverse(PCEdge), + /// Points to a `Referent::SuperNode` + Concentrate(PBack), + /// Points to a `Referent::SubNode` + Dilute(PBack), +} + /// A single path from a source to sink across multiple `CEdge`s #[derive(Debug, Clone)] pub struct Path { diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index c28379b0..dc1bbb10 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -170,6 +170,11 @@ impl Router { } pub fn route(&mut self) -> Result<(), Error> { + // see cnode.rs for the overall idea + + // initialization + assert_eq!(self.target_channeler().top_level_cnodes.len(), 1); + assert_eq!(self.program_channeler().top_level_cnodes.len(), 1); Ok(()) } } From 20074ec7ea6c233edbd70a110a170cbdf639ecd8 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 12:52:22 -0600 Subject: [PATCH 107/119] tmp --- starlight/src/route/cedge.rs | 184 ++++++++++++++++++++++++-------- starlight/src/route/channel.rs | 110 +++++++------------ starlight/src/route/cnode.rs | 189 ++++++++++++++++++++++++++++++--- starlight/src/route/debug.rs | 101 ++++++++---------- starlight/src/route/path.rs | 3 +- starlight/src/route/router.rs | 29 ++++- 6 files changed, 420 insertions(+), 196 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index a81740ee..6524e09d 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,7 +1,13 @@ use std::{cmp::min, num::NonZeroUsize}; -use awint::Awi; +use awint::{ + awint_dag::triple_arena::{ + surject_iterators::SurjectPtrAdvancer, Advancer, ArenaTrait, OrdArena, + }, + Awi, +}; +use super::CNode; use crate::{ awint_dag::smallvec::SmallVec, ensemble, @@ -50,10 +56,10 @@ impl SelectorLut { } } - pub fn verify_integrity(&self, sources_len: usize, sinks_len: usize) -> Result<(), Error> { + pub fn verify_integrity(&self, sources_len: usize) -> Result<(), Error> { // TODO let pow_len = 1usize << self.v.len(); - if (pow_len.checked_mul(2).unwrap() != self.awi.bw()) || (sinks_len != 1) { + if pow_len.checked_mul(2).unwrap() != self.awi.bw() { return Err(Error::OtherStr("problem with `SelectorLut` validation")); } let mut dynam_len = 0; @@ -72,20 +78,26 @@ impl SelectorLut { /// Used by higher order edges to tell what it is capable of overall #[derive(Debug, Clone)] pub struct BulkBehavior { - /// The number of bits that can enter this channel - pub channel_entry_width: usize, + /// The number of bits that can enter this channel's sources + pub channel_entry_widths: Vec, /// The number of bits that can exit this channel pub channel_exit_width: usize, /// For now, we just add up the number of LUT bits in the channel pub lut_bits: usize, } +impl BulkBehavior { + pub fn empty() -> Self { + Self { + channel_entry_widths: vec![], + channel_exit_width: 0, + lut_bits: 0, + } + } +} + #[derive(Debug, Clone)] pub enum Programmability { - /// Nothing can happen between nodes, used for connecting top level nodes - /// that have no connection to each other - Noop, - StaticLut(Awi), // `DynamicLut`s can go in one of two ways: the table bits directly connect with configurable @@ -107,9 +119,10 @@ pub enum Programmability { /// An edge between channels #[derive(Debug, Clone)] pub struct CEdge { - // sources and sinks incident to nodes + // sources incident to nodes sources: Vec, - sinks: Vec, + // the sink incident to nodes + sink: PBack, programmability: Programmability, // Ideally when `CNode`s are merged, they keep approximately the same weight distribution for @@ -126,47 +139,40 @@ impl CEdge { &self.sources } - pub fn sinks(&self) -> &[PBack] { - &self.sinks + pub fn sink(&self) -> PBack { + self.sink } pub fn sources_mut(&mut self) -> &mut [PBack] { &mut self.sources } - pub fn sinks_mut(&mut self) -> &mut [PBack] { - &mut self.sinks + pub fn sink_mut(&mut self) -> &mut PBack { + &mut self.sink } pub fn incidents(&self, mut f: F) { for source in self.sources() { f(*source) } - for sink in self.sinks() { - f(*sink) - } + f(self.sink) } pub fn incidents_len(&self) -> usize { - self.sources() - .len() - .checked_add(self.sinks().len()) - .unwrap() + self.sources().len().checked_add(1).unwrap() } - pub fn channel_entry_width(&self) -> usize { + /*pub fn channel_entry_width(&self) -> usize { match self.programmability() { - Programmability::Noop => 0, Programmability::StaticLut(awi) => awi.bw().trailing_zeros() as usize, Programmability::ArbitraryLut(table) => table.len().trailing_zeros() as usize, Programmability::SelectorLut(selector_lut) => selector_lut.v.len(), - Programmability::Bulk(bulk) => bulk.channel_entry_width, + Programmability::Bulk(bulk) => bulk.channel_entry_widths.sum(), } } pub fn channel_exit_width(&self) -> usize { match self.programmability() { - Programmability::Noop => 0, Programmability::StaticLut(awi) => 1, Programmability::ArbitraryLut(table) => 1, Programmability::SelectorLut(selector_lut) => 1, @@ -177,7 +183,7 @@ impl CEdge { /// Takes the minimum of the channel entry width and channel exit width pub fn channel_width(&self) -> usize { min(self.channel_entry_width(), self.channel_exit_width()) - } + }*/ } impl Channeler { @@ -186,29 +192,25 @@ impl Channeler { fn make_cedge( &mut self, sources: &[PBack], - sink: &[PBack], + sink: PBack, programmability: Programmability, ) -> PCEdge { self.cedges.insert_with(|p_self| { let mut fixed_sources = vec![]; - let mut fixed_sinks = vec![]; for (i, source) in sources.iter().enumerate() { fixed_sources.push( self.cnodes - .insert_key(*source, Referent::CEdgeIncidence(p_self, i, false)) - .unwrap(), - ); - } - for (i, sink) in sink.iter().enumerate() { - fixed_sinks.push( - self.cnodes - .insert_key(*sink, Referent::CEdgeIncidence(p_self, i, true)) + .insert_key(*source, Referent::CEdgeIncidence(p_self, Some(i))) .unwrap(), ); } + let fixed_sink = self + .cnodes + .insert_key(sink, Referent::CEdgeIncidence(p_self, None)) + .unwrap(); CEdge { sources: fixed_sources, - sinks: fixed_sinks, + sink: fixed_sink, programmability, } }) @@ -231,7 +233,7 @@ impl Channeler { // for each equivalence make a `CNode` with associated `EnsembleBackref` for equiv in ensemble.backrefs.vals() { - let p_cnode = channeler.make_top_level_cnode(vec![]); + let p_cnode = channeler.make_top_level_cnode(vec![], 0); let channeler_backref = channeler .cnodes .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) @@ -276,7 +278,7 @@ impl Channeler { for input in inp { v.push(translate(ensemble, &channeler, *input).1); } - channeler.make_cedge(&v, &[p_self], Programmability::StaticLut(awi.clone())); + channeler.make_cedge(&v, p_self, Programmability::StaticLut(awi.clone())); } LNodeKind::DynamicLut(inp, lut) => { //let p_self = translate(ensemble, &channeler, lnode.p_self).1; @@ -330,7 +332,7 @@ impl Channeler { } channeler.make_cedge( &v, - &[p_self], + p_self, Programmability::SelectorLut(SelectorLut { awi, v: config }), ); } @@ -348,11 +350,7 @@ impl Channeler { unreachable!() } } - channeler.make_cedge( - &v, - &[p_self], - Programmability::ArbitraryLut(config), - ); + channeler.make_cedge(&v, p_self, Programmability::ArbitraryLut(config)); } // we will need interaction with the `Ensemble` to do `LNode` side lowering _ => todo!(), @@ -365,4 +363,98 @@ impl Channeler { Ok(channeler) } + + /// Advances over all neighbors of a node exactly once (do not mutate the + /// surject of `p`). Note that `CNodeNeighborAdvancer` has a function for + /// getting the unique nodes. + pub fn advancer_neighbors_of_node(&self, p: PBack) -> CNodeNeighborAdvancer { + CNodeNeighborAdvancer { + adv: self.cnodes.advancer_surject(p), + unique: OrdArena::new(), + } + } + + /// Advances over all subnodes of a node + pub fn advancer_subnodes_of_node(&self, p: PBack) -> CNodeSubnodeAdvancer { + CNodeSubnodeAdvancer { + adv: self.cnodes.advancer_surject(p), + } + } + + pub fn get_supernode(&self, p: PBack) -> Option { + let mut adv = self.cnodes.advancer_surject(p); + while let Some(p) = adv.advance(&self.cnodes) { + if let Referent::SuperNode(p_supernode) = self.cnodes.get_key(p).unwrap() { + return Some(self.cnodes.get_val(*p_supernode).unwrap().p_this_cnode) + } + } + None + } +} + +ptr_struct!(PUniqueCNode); + +pub struct CNodeNeighborAdvancer { + adv: SurjectPtrAdvancer, + // we have multiedges, so we need to track unique CNodes + unique: OrdArena, +} + +impl CNodeNeighborAdvancer { + /// If this is called after advancing is done, this has all the unique + /// `CNode`s + pub fn into_unique(self) -> OrdArena { + self.unique + } +} + +impl Advancer for CNodeNeighborAdvancer { + type Collection = Channeler; + type Item = PBack; + + fn advance(&mut self, collection: &Self::Collection) -> Option { + while let Some(p_referent) = self.adv.advance(&collection.cnodes) { + if let Referent::CEdgeIncidence(p_cedge, i) = + collection.cnodes.get_key(p_referent).unwrap() + { + let cedge = collection.cedges.get(*p_cedge).unwrap(); + let p_neighbor = if let Some(source_i) = *i { + cedge.sources()[source_i] + } else { + cedge.sink() + }; + let p_neighbor = collection.cnodes.get_val(p_neighbor).unwrap().p_this_cnode; + let replace = self.unique.insert(p_neighbor, ()).1; + if replace.is_none() { + return Some(p_neighbor) + } + } + // need to be in a loop to skip over non-incidence referents + } + None + } +} + +pub struct CNodeSubnodeAdvancer { + adv: SurjectPtrAdvancer, +} + +impl Advancer for CNodeSubnodeAdvancer { + type Collection = Channeler; + type Item = PBack; + + fn advance(&mut self, collection: &Self::Collection) -> Option { + while let Some(p_referent) = self.adv.advance(&collection.cnodes) { + if let Referent::SubNode(p_subnode_ref) = collection.cnodes.get_key(p_referent).unwrap() + { + let p_cnode = collection + .cnodes + .get_val(*p_subnode_ref) + .unwrap() + .p_this_cnode; + return Some(p_cnode); + } + } + None + } } diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index a2d02b0e..9e9d7867 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -18,8 +18,10 @@ pub enum Referent { ThisCNode, SubNode(PBack), SuperNode(PBack), - /// The bool indicates if it is a sink - CEdgeIncidence(PCEdge, usize, bool), + /// The index is `None` if it is a sink, TODO use a NonZeroInxVec if we + /// stick with this + CEdgeIncidence(PCEdge, Option), + // TODO do we actually need this? EnsembleBackRef(ensemble::PBack), } @@ -45,42 +47,14 @@ impl Channeler { } } - /* - /// Starting from `p_cnode` assumed to contain `p_back`, this returns valid - /// subnodes that still contain `ensemble::PBack` - pub fn valid_cnode_descensions(&self, p_cnode: PCNode, p_back: ensemble::PBack) - -> SmallVec<[PCNode; 4]> { - let cnode = self.cnodes.get(p_cnode).unwrap(); - if let Some(mut adv) = RegionAdvancer::new(&self.backref_to_cnode, |_, (p_back1, _), ()| { - p_back1.cmp(&p_back) - }) { - // uses the fact that `subnodes` is ordered to linearly iterate over a region - let mut res = smallvec![]; - let mut i = 0; - 'outer: while let Some(p) = adv.advance(&self.backref_to_cnode) { - let (_, p_cnode1) = self.backref_to_cnode.get_key(p).unwrap(); - loop { - if i >= cnode.subnodes.len() { - break 'outer; - } - match cnode.subnodes[i].cmp(&p_cnode1) { - Ordering::Less => { - i += 1; - } - Ordering::Equal => { - res.push(*p_cnode1); - i += 1; - break - } - Ordering::Greater => break, - } - } - } - res - } else { - unreachable!() - } - }*/ + pub fn find_channeler_backref(&self, ensemble_backref: ensemble::PBack) -> Option { + let p = self + .ensemble_backref_to_channeler_backref + .find_key(&ensemble_backref)?; + self.ensemble_backref_to_channeler_backref + .get(p) + .map(|(_, q)| *q) + } pub fn verify_integrity(&self) -> Result<(), Error> { // return errors in order of most likely to be root cause @@ -116,18 +90,14 @@ impl Channeler { Referent::ThisCNode => false, Referent::SubNode(p_subnode) => !self.cnodes.contains(*p_subnode), Referent::SuperNode(p_supernode) => !self.cnodes.contains(*p_supernode), - Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + Referent::CEdgeIncidence(p_cedge, i) => { if let Some(cedges) = self.cedges.get(*p_cedge) { - if *is_sink { - if *i > cedges.sinks().len() { + if let Some(source_i) = i { + if *source_i > cedges.sources().len() { return Err(Error::OtherString(format!( "{referent:?} roundtrip out of bounds" ))) } - } else if *i > cedges.sources().len() { - return Err(Error::OtherString(format!( - "{referent:?} roundtrip out of bounds" - ))) } false } else { @@ -145,16 +115,15 @@ impl Channeler { for p_cnode in cedge.sources().iter() { if !self.cnodes.contains(*p_cnode) { return Err(Error::OtherString(format!( - "{cedge:?}.p_cnodes {p_cnode} is invalid", + "{cedge:?} source {p_cnode} is invalid", ))) } } - for p_cnode in cedge.sinks().iter() { - if !self.cnodes.contains(*p_cnode) { - return Err(Error::OtherString(format!( - "{cedge:?}.p_cnodes {p_cnode} is invalid", - ))) - } + if !self.cnodes.contains(cedge.sink()) { + return Err(Error::OtherString(format!( + "{cedge:?} sink {} is invalid", + cedge.sink() + ))) } } for p_cnode in self.top_level_cnodes.keys() { @@ -186,28 +155,24 @@ impl Channeler { true } } - Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + Referent::CEdgeIncidence(p_cedge, i) => { let cedge = self.cedges.get(*p_cedge).unwrap(); - if *is_sink { - if let Some(sink) = cedge.sinks().get(*i) { - if let Referent::CEdgeIncidence(p_cedge1, i1, is_sink1) = - self.cnodes.get_key(*sink).unwrap() + if let Some(source_i) = *i { + if let Some(source) = cedge.sources().get(source_i) { + if let Referent::CEdgeIncidence(p_cedge1, i1) = + self.cnodes.get_key(*source).unwrap() { - (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) + (*p_cedge != *p_cedge1) || (*i != *i1) } else { true } } else { true } - } else if let Some(source) = cedge.sources().get(*i) { - if let Referent::CEdgeIncidence(p_cedge1, i1, is_sink1) = - self.cnodes.get_key(*source).unwrap() - { - (*p_cedge != *p_cedge1) || (*i != *i1) || (*is_sink != *is_sink1) - } else { - true - } + } else if let Referent::CEdgeIncidence(p_cedge1, i1) = + self.cnodes.get_key(cedge.sink()).unwrap() + { + (*p_cedge != *p_cedge1) || i1.is_some() } else { true } @@ -223,23 +188,19 @@ impl Channeler { let cedge = self.cedges.get(p_cedge).unwrap(); let incidents_len = cedge.incidents_len(); let sources_len = cedge.sources().len(); - let sinks_len = cedge.sinks().len(); let ok = match cedge.programmability() { - Programmability::Noop => incidents_len == 0, Programmability::StaticLut(lut) => { // TODO find every place I did the trailing zeros thing and have a function that // does the more efficient thing the core `lut_` function does lut.bw().is_power_of_two() && (lut.bw().trailing_zeros() as usize == sources_len) - && (sinks_len == 1) } Programmability::ArbitraryLut(lut) => { lut.len().is_power_of_two() && ((lut.len().trailing_zeros() as usize) == sources_len) - && (sinks_len == 1) } Programmability::SelectorLut(selector_lut) => { - selector_lut.verify_integrity(sources_len, sinks_len)?; + selector_lut.verify_integrity(sources_len)?; true } Programmability::Bulk(_) => todo!(), @@ -250,11 +211,18 @@ impl Channeler { ))) } } + // TODO check uniqueness of super/sub relations, check that there is at most one + // supernode per node for cnode in self.cnodes.vals() { let contained = self .top_level_cnodes .find_key(&cnode.p_this_cnode) .is_some(); + if cnode.has_supernode == contained { + return Err(Error::OtherString(format!( + "{cnode:?}.has_supernode is wrong" + ))); + } let mut adv = self.cnodes.advancer_surject(cnode.p_this_cnode); let mut found_super_node = false; while let Some(p) = adv.advance(&self.cnodes) { diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index f906cab9..15747d53 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -1,20 +1,38 @@ -use awint::awint_dag::triple_arena::{ptr_struct, Ptr}; +use std::cmp::max; +use awint::awint_dag::{ + smallvec::SmallVec, + triple_arena::{ptr_struct, Advancer, ArenaTrait, OrdArena, Ptr}, +}; + +use super::{ + cedge::{self, PUniqueCNode}, + BulkBehavior, Programmability, +}; use crate::route::{channel::Referent, Channeler, PBack}; /// A channel node #[derive(Debug, Clone, Default)] pub struct CNode { pub p_this_cnode: PBack, + pub lvl: u16, + pub has_supernode: bool, } impl Channeler { /// Given the `subnodes` (which should point to unique `ThisCNode`s) for a /// new top level `CNode`, this will manage the backrefs - pub fn make_top_level_cnode(&mut self, subnodes: Vec) -> PBack { - let p_cnode = self - .cnodes - .insert_with(|p_this_cnode| (Referent::ThisCNode, CNode { p_this_cnode })); + pub fn make_top_level_cnode(&mut self, subnodes: I, lvl: u16) -> PBack + where + I: IntoIterator, + { + let p_cnode = self.cnodes.insert_with(|p_this_cnode| { + (Referent::ThisCNode, CNode { + p_this_cnode, + lvl, + has_supernode: false, + }) + }); for subnode in subnodes { if let Some(p) = self.top_level_cnodes.find_key(&subnode) { self.top_level_cnodes.remove(p).unwrap(); @@ -29,7 +47,9 @@ impl Channeler { .unwrap(); // we want the referents to point exactly at each other's keys and not the // `p_this_cnode` - *self.cnodes.get_key_mut(p_subnode).unwrap() = Referent::SuperNode(p_supernode); + let (referent, cnode) = self.cnodes.get_mut(p_subnode).unwrap(); + *referent = Referent::SuperNode(p_supernode); + cnode.has_supernode = true; } self.top_level_cnodes.insert(p_cnode, ()); p_cnode @@ -99,24 +119,163 @@ For one more detail, what do we do about disconnected graphs? /// We are currently assuming that `generate_hierarchy` is being run once on /// a graph of unit channel nodes and edges pub fn generate_hierarchy(channeler: &mut Channeler) { - ptr_struct!(P0); - // For each cnode on a given level, we will attempt to concentrate it and all - // its neighbors. - // + // its neighbors. If any neighbor has a supernode already, it skips the cnode + + let mut current_lvl = 0u16; + // TODO this is somewhat inefficient, may want to keep an array of the previous + // and next level `PBack`s around + loop { + let next_lvl = current_lvl.checked_add(1).unwrap(); + let mut concentrated = false; + let mut adv = channeler.cnodes.advancer(); + 'over_cnodes: while let Some(p_consider) = adv.advance(&channeler.cnodes) { + if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { + let cnode = channeler.cnodes.get_val(p_consider).unwrap(); + if (cnode.lvl != current_lvl) || cnode.has_supernode { + continue + } + // check if the node's neighbors have supernodes + let mut neighbor_adv = channeler.advancer_neighbors_of_node(p_consider); + while let Some(p) = neighbor_adv.advance(&channeler) { + if channeler.cnodes.get_val(p).unwrap().has_supernode { + continue 'over_cnodes; + } + } + // concentrate + let neighbors = neighbor_adv.into_unique(); + channeler.make_top_level_cnode(neighbors.keys().map(|p| *p), next_lvl); + + concentrated = true; + } + } + if !concentrated { + // there are only disconnected nodes left + break + } + // for nodes that couldn't be concentrated, create single subnode supernodes for + // them + let mut adv = channeler.cnodes.advancer(); + 'over_cnodes: while let Some(p_consider) = adv.advance(&channeler.cnodes) { + if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { + let cnode = channeler.cnodes.get_val(p_consider).unwrap(); + if (cnode.lvl != current_lvl) || cnode.has_supernode { + continue + } + channeler.make_top_level_cnode([p_consider], next_lvl); + } + } - // when making a new top level cnode, for each subnode of the new node check - // for other supernodes. For each transverse node Tally the number of times - // each transverse node is seen. If it is seen more than once, cancel making - // the new cnode. + // we have all the next level nodes, but we need to create the bulk `CEdge`s + // between them + let mut adv = channeler.cnodes.advancer(); + 'over_cnodes: while let Some(p_consider) = adv.advance(&channeler.cnodes) { + if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { + let cnode = channeler.cnodes.get_val(p_consider).unwrap(); + if cnode.lvl != next_lvl { + continue + } + // TODO in the referents refactor, we need some formulaic way to add extra data + // to the surject value structs to avoid all these `OrdArena`s + ptr_struct!(P0; P1; P2); + // first get the set of subnodes + let mut subnode_set = OrdArena::::new(); + let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); + while let Some(p_subnode) = subnode_adv.advance(&channeler) { + let _ = subnode_set.insert(p_subnode, ()); + } + // iterate through the subnodes again, but now get a set of the neighbors that + // aren't in the subnodes set + let mut related_subnodes_set = OrdArena::::new(); + let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); + while let Some(p_subnode) = subnode_adv.advance(&channeler) { + let mut second_neighbors = channeler.advancer_neighbors_of_node(p_subnode); + while let Some(p_neighbor) = second_neighbors.advance(&channeler) { + if subnode_set.find_key(&p_neighbor).is_none() { + related_subnodes_set.insert(p_neighbor, ()); + } + } + } + // get all the supernodes of the related subnodes, and associate them with + // bulk behavior for the `CEdge` with them later. This bulk behavior will be the + // edge from this `CNode` under consideration to the related node (so only sink + // incidents will contribute to the bulk behavior), and when the related cnode + // is under consideration it will handle the edge in the other direction, so we + // can avoid duplication. + let mut related_supernodes_set = OrdArena::::new(); + for p_related_subnode in related_subnodes_set.keys() { + let p_related_supernode = channeler.get_supernode(*p_related_subnode).unwrap(); + related_supernodes_set.insert(p_related_supernode, BulkBehavior::empty()); + } + // we want to find hyperedges with incidents that are both in the subnodes and + // related subnodes, which will be concentrated as a bulk edge between the + // supernode under consideration and the related supernodes. To avoid + // duplication we will orient around the sink incident, and only do work in this + // iteration if the sink is in our subnodes set. If the sink is in a related + // subnode, another 'over_cnodes iteration will handle it. This is also one of + // the reasons why we made each node only able to have one supernode. + + // If all the incidents are in our subnodes set, then the edge is internal and + // we would do nothing except that we need to add onto the `InternalBehavior` so + // that the routing knows where LUT bits are. + + // If some source incidents are in our subnodes set, we need to make sure that + // they do not contribute to the concentrated edges. + + // Source incidents from the same edge can be in multiple other related sets, in + // which case the bulk behavior edge can be a hyperedge. + + // iterate through the subnodes one more time, finding bipartite edges between + // the subnodes and related subnodes + let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); + while let Some(p_subnode) = subnode_adv.advance(&channeler) { + let mut adv_edges = channeler.cnodes.advancer_surject(p_subnode); + while let Some(p_referent) = adv_edges.advance(&channeler.cnodes) { + if let Referent::CEdgeIncidence(p_cedge, i) = + channeler.cnodes.get_key(p_referent).unwrap() + { + let cedge = channeler.cedges.get(*p_cedge).unwrap(); + let p_cnode = + channeler.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; + if subnode_set.find_key(&p_cnode).is_some() { + // the sink is in our sphere, if any source is from the related + // subnodes then we need + for p_source in cedge.sources() { + let p_cnode = channeler + .cnodes + .get_val(cedge.sink()) + .unwrap() + .p_this_cnode; + } + } + match cedge.programmability() { + Programmability::StaticLut(_) => todo!(), + Programmability::ArbitraryLut(_) => todo!(), + Programmability::SelectorLut(_) => todo!(), + Programmability::Bulk(_) => todo!(), + } + } + } + } + + //channeler.make_cedge(&[], &[], + // Programmability::Bulk(BulkBehavior { channel_entry_width: + // todo!(), channel_exit_width: todo!(), lut_bits: todo!() })); + } + } + + current_lvl = next_lvl; + } // if there are multiple cnodes are left in an anticlique, concentrate them into // a single top level node if channeler.top_level_cnodes.len() > 1 { let mut set = vec![]; + let mut max_lvl = 0; for p_cnode in channeler.top_level_cnodes.keys() { set.push(*p_cnode); + max_lvl = max(max_lvl, channeler.cnodes.get_val(*p_cnode).unwrap().lvl) } - channeler.make_top_level_cnode(set); + channeler.make_top_level_cnode(set, max_lvl); } } diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index 20fcd522..81f1c1db 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -20,7 +20,7 @@ pub enum NodeKind { CNode(CNode), SubNode(PBack, PBack), SuperNode(PBack, PBack), - CEdgeIncidence(PBack, PCEdge, usize, bool, CEdge, CEdge), + CEdgeIncidence(PBack, PCEdge, Option, CEdge, CEdge), EnsembleBackRef(PBack, ensemble::PBack), Remove, } @@ -43,35 +43,37 @@ impl DebugNodeTrait for NodeKind { center: { vec!["super".to_owned()] }, sinks: vec![], }, - NodeKind::CEdgeIncidence(p_back, p_cedge, i, is_sink, cedge, cedge_forwarded) => { - DebugNode { - sources: { - let mut v = vec![(*p_back, String::new())]; - for (i, (source, source_forwarded)) in cedge - .sources() - .iter() - .zip(cedge_forwarded.sources().iter()) - .enumerate() - { - v.push((*source_forwarded, format!("{source}"))); - } - v - }, - center: { vec![format!("{p_cedge}"), format!("{i} {is_sink}")] }, - sinks: { - let mut v = vec![]; - for (i, (sink, sink_forwarded)) in cedge - .sinks() - .iter() - .zip(cedge_forwarded.sinks().iter()) - .enumerate() - { - v.push((*sink_forwarded, format!("{sink}"))); - } - v - }, - } - } + NodeKind::CEdgeIncidence(p_back, p_cedge, i, cedge, cedge_forwarded) => DebugNode { + sources: { + let mut v = vec![(*p_back, String::new())]; + for (i, (source, source_forwarded)) in cedge + .sources() + .iter() + .zip(cedge_forwarded.sources().iter()) + .enumerate() + { + v.push((*source_forwarded, format!("{source}"))); + } + v + }, + center: { + vec![ + format!("{p_cedge}"), + if let Some(source_i) = i { + format!("{source_i}") + } else { + "".to_owned() + }, + ] + }, + sinks: { + let mut v = vec![]; + if i.is_none() { + v.push((cedge_forwarded.sink(), "".to_owned())); + } + v + }, + }, NodeKind::EnsembleBackRef(p_back, ensemble_p_back) => DebugNode { sources: vec![(*p_back, String::new())], center: { vec!["backref".to_owned(), format!("{ensemble_p_back}")] }, @@ -113,7 +115,6 @@ impl DebugNodeTrait for HyperNodeKind { center: { let mut v = vec![]; v.push(match cedge_forwarded.programmability() { - Programmability::Noop => "Noop".to_owned(), Programmability::StaticLut(_) => "StaticLut".to_owned(), Programmability::ArbitraryLut(_) => "ArbitraryLut".to_owned(), Programmability::SelectorLut(_) => "SelectorLut".to_owned(), @@ -121,18 +122,7 @@ impl DebugNodeTrait for HyperNodeKind { }); v }, - sinks: { - let mut v = vec![]; - for (i, (sink, sink_forwarded)) in cedge - .sinks() - .iter() - .zip(cedge_forwarded.sinks().iter()) - .enumerate() - { - v.push((*sink_forwarded, format!("{sink}"))); - } - v - }, + sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, }, HyperNodeKind::Remove => panic!("should have been removed"), } @@ -151,23 +141,17 @@ impl Channeler { } Referent::SubNode(p_back) => NodeKind::SubNode(*p_back, p_cnode), Referent::SuperNode(p_back) => NodeKind::SuperNode(*p_back, p_cnode), - Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + Referent::CEdgeIncidence(p_cedge, i) => { let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); let mut cedge_forwarded = cedge.clone(); for source in cedge_forwarded.sources_mut() { *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; } - for sink in cedge_forwarded.sinks_mut() { - *sink = self.cnodes.get_val(*sink).unwrap().p_this_cnode; + if i.is_none() { + *cedge_forwarded.sink_mut() = + self.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; } - NodeKind::CEdgeIncidence( - p_cnode, - *p_cedge, - *i, - *is_sink, - cedge, - cedge_forwarded, - ) + NodeKind::CEdgeIncidence(p_cnode, *p_cedge, *i, cedge, cedge_forwarded) } Referent::EnsembleBackRef(ensemble_p_backref) => { NodeKind::EnsembleBackRef(p_cnode, *ensemble_p_backref) @@ -194,16 +178,17 @@ impl Channeler { } Referent::SubNode(_) => HyperNodeKind::Remove, Referent::SuperNode(_) => HyperNodeKind::Remove, - Referent::CEdgeIncidence(p_cedge, i, is_sink) => { + Referent::CEdgeIncidence(p_cedge, i) => { // insures that there is only one `CEdge` per set of incidents - if (*i == 0) && *is_sink { + if i.is_none() { let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); let mut cedge_forwarded = cedge.clone(); for source in cedge_forwarded.sources_mut() { *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; } - for sink in cedge_forwarded.sinks_mut() { - *sink = self.cnodes.get_val(*sink).unwrap().p_this_cnode; + if i.is_none() { + *cedge_forwarded.sink_mut() = + self.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; } HyperNodeKind::CEdge(cedge, cedge_forwarded) } else { diff --git a/starlight/src/route/path.rs b/starlight/src/route/path.rs index 90a2ca3a..2836578d 100644 --- a/starlight/src/route/path.rs +++ b/starlight/src/route/path.rs @@ -5,6 +5,7 @@ use crate::{ ptr_struct!(PHyperPath); +#[derive(Debug, Clone)] pub enum Edge { /// Points to a `CEdge` Transverse(PCEdge), @@ -18,7 +19,7 @@ pub enum Edge { #[derive(Debug, Clone)] pub struct Path { sink: PBack, - edges: Vec, + edges: Vec, //critical_multiplier: u64, } diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index dc1bbb10..9b28f5b8 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -170,11 +170,30 @@ impl Router { } pub fn route(&mut self) -> Result<(), Error> { - // see cnode.rs for the overall idea + route(self) + } +} - // initialization - assert_eq!(self.target_channeler().top_level_cnodes.len(), 1); - assert_eq!(self.program_channeler().top_level_cnodes.len(), 1); - Ok(()) +fn route(router: &mut Router) -> Result<(), Error> { + // see cnode.rs for the overall idea + + // initialization of hyperpaths + assert_eq!(router.target_channeler().top_level_cnodes.len(), 1); + assert_eq!(router.program_channeler().top_level_cnodes.len(), 1); + for (_, program_p_equiv, mapping) in &router.mappings { + let program_p_equiv = *program_p_equiv; + let target_p_equiv = mapping.target_p_equiv; + let program_base_cnode = router + .program_channeler() + .find_channeler_backref(program_p_equiv) + .unwrap(); + let target_base_cnode = router + .target_channeler() + .find_channeler_backref(target_p_equiv) + .unwrap(); + + //let mut hyperpath = } + + Ok(()) } From d9809a49172048170cd026b57c6c50a8903eef81 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 13:49:34 -0600 Subject: [PATCH 108/119] complete initial concentration ideas --- starlight/src/route/cedge.rs | 11 +-- starlight/src/route/channel.rs | 7 +- starlight/src/route/cnode.rs | 158 ++++++++++++++++++++++++--------- starlight/src/route/debug.rs | 30 +++---- 4 files changed, 131 insertions(+), 75 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 6524e09d..6079de9b 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,18 +1,15 @@ -use std::{cmp::min, num::NonZeroUsize}; +use std::num::NonZeroUsize; use awint::{ - awint_dag::triple_arena::{ - surject_iterators::SurjectPtrAdvancer, Advancer, ArenaTrait, OrdArena, - }, + awint_dag::triple_arena::{surject_iterators::SurjectPtrAdvancer, Advancer, OrdArena}, Awi, }; -use super::CNode; use crate::{ awint_dag::smallvec::SmallVec, ensemble, ensemble::{DynamicValue, Ensemble, LNodeKind}, - route::{channel::Referent, cnode::generate_hierarchy, Channeler, Configurator, PBack}, + route::{channel::Referent, cnode::generate_hierarchy, CNode, Channeler, Configurator, PBack}, triple_arena::ptr_struct, Error, SuspendedEpoch, }; @@ -189,7 +186,7 @@ impl CEdge { impl Channeler { /// Given the source and sink incidences (which should point to unique /// `ThisCNode`s), this will manage the backrefs - fn make_cedge( + pub fn make_cedge( &mut self, sources: &[PBack], sink: PBack, diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 9e9d7867..d1c71d5c 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -1,10 +1,6 @@ -use awint::awint_dag::{ - smallvec::smallvec, - triple_arena::{Advancer, Arena, OrdArena, SurjectArena}, -}; +use awint::awint_dag::triple_arena::{Advancer, Arena, OrdArena, SurjectArena}; use crate::{ - awint_dag::smallvec::SmallVec, ensemble, route::{CEdge, CNode, PCEdge, Programmability}, triple_arena::ptr_struct, @@ -186,7 +182,6 @@ impl Channeler { // non `Ptr` validities for p_cedge in self.cedges.ptrs() { let cedge = self.cedges.get(p_cedge).unwrap(); - let incidents_len = cedge.incidents_len(); let sources_len = cedge.sources().len(); let ok = match cedge.programmability() { Programmability::StaticLut(lut) => { diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index 15747d53..d759f0a6 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -1,22 +1,27 @@ use std::cmp::max; -use awint::awint_dag::{ - smallvec::SmallVec, - triple_arena::{ptr_struct, Advancer, ArenaTrait, OrdArena, Ptr}, -}; +use awint::awint_dag::triple_arena::{ptr_struct, Advancer, OrdArena, Ptr}; -use super::{ - cedge::{self, PUniqueCNode}, - BulkBehavior, Programmability, -}; -use crate::route::{channel::Referent, Channeler, PBack}; +use crate::route::{channel::Referent, BulkBehavior, Channeler, PBack, Programmability}; + +#[derive(Debug, Clone)] +pub struct InternalBehavior { + lut_bits: usize, +} + +impl InternalBehavior { + pub fn empty() -> Self { + Self { lut_bits: 0 } + } +} /// A channel node -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct CNode { pub p_this_cnode: PBack, pub lvl: u16, pub has_supernode: bool, + pub internal_behavior: InternalBehavior, } impl Channeler { @@ -31,6 +36,7 @@ impl Channeler { p_this_cnode, lvl, has_supernode: false, + internal_behavior: InternalBehavior::empty(), }) }); for subnode in subnodes { @@ -51,7 +57,7 @@ impl Channeler { *referent = Referent::SuperNode(p_supernode); cnode.has_supernode = true; } - self.top_level_cnodes.insert(p_cnode, ()); + let _ = self.top_level_cnodes.insert(p_cnode, ()); p_cnode } } @@ -137,14 +143,14 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { } // check if the node's neighbors have supernodes let mut neighbor_adv = channeler.advancer_neighbors_of_node(p_consider); - while let Some(p) = neighbor_adv.advance(&channeler) { + while let Some(p) = neighbor_adv.advance(channeler) { if channeler.cnodes.get_val(p).unwrap().has_supernode { continue 'over_cnodes; } } // concentrate let neighbors = neighbor_adv.into_unique(); - channeler.make_top_level_cnode(neighbors.keys().map(|p| *p), next_lvl); + channeler.make_top_level_cnode(neighbors.keys().copied(), next_lvl); concentrated = true; } @@ -156,7 +162,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // for nodes that couldn't be concentrated, create single subnode supernodes for // them let mut adv = channeler.cnodes.advancer(); - 'over_cnodes: while let Some(p_consider) = adv.advance(&channeler.cnodes) { + while let Some(p_consider) = adv.advance(&channeler.cnodes) { if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { let cnode = channeler.cnodes.get_val(p_consider).unwrap(); if (cnode.lvl != current_lvl) || cnode.has_supernode { @@ -169,7 +175,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // we have all the next level nodes, but we need to create the bulk `CEdge`s // between them let mut adv = channeler.cnodes.advancer(); - 'over_cnodes: while let Some(p_consider) = adv.advance(&channeler.cnodes) { + while let Some(p_consider) = adv.advance(&channeler.cnodes) { if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { let cnode = channeler.cnodes.get_val(p_consider).unwrap(); if cnode.lvl != next_lvl { @@ -181,18 +187,18 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // first get the set of subnodes let mut subnode_set = OrdArena::::new(); let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); - while let Some(p_subnode) = subnode_adv.advance(&channeler) { + while let Some(p_subnode) = subnode_adv.advance(channeler) { let _ = subnode_set.insert(p_subnode, ()); } // iterate through the subnodes again, but now get a set of the neighbors that // aren't in the subnodes set let mut related_subnodes_set = OrdArena::::new(); let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); - while let Some(p_subnode) = subnode_adv.advance(&channeler) { + while let Some(p_subnode) = subnode_adv.advance(channeler) { let mut second_neighbors = channeler.advancer_neighbors_of_node(p_subnode); - while let Some(p_neighbor) = second_neighbors.advance(&channeler) { + while let Some(p_neighbor) = second_neighbors.advance(channeler) { if subnode_set.find_key(&p_neighbor).is_none() { - related_subnodes_set.insert(p_neighbor, ()); + let _ = related_subnodes_set.insert(p_neighbor, ()); } } } @@ -205,7 +211,8 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { let mut related_supernodes_set = OrdArena::::new(); for p_related_subnode in related_subnodes_set.keys() { let p_related_supernode = channeler.get_supernode(*p_related_subnode).unwrap(); - related_supernodes_set.insert(p_related_supernode, BulkBehavior::empty()); + let _ = + related_supernodes_set.insert(p_related_supernode, BulkBehavior::empty()); } // we want to find hyperedges with incidents that are both in the subnodes and // related subnodes, which will be concentrated as a bulk edge between the @@ -225,42 +232,107 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // Source incidents from the same edge can be in multiple other related sets, in // which case the bulk behavior edge can be a hyperedge. - // iterate through the subnodes one more time, finding bipartite edges between - // the subnodes and related subnodes + // Multiple source incidents can be in the same related set + + // TODO we allow combinations of edges and various hyperedges to coexist, are + // there any exponential blowup cases that can happen despite the + // internalization? + let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); - while let Some(p_subnode) = subnode_adv.advance(&channeler) { + while let Some(p_subnode) = subnode_adv.advance(channeler) { let mut adv_edges = channeler.cnodes.advancer_surject(p_subnode); while let Some(p_referent) = adv_edges.advance(&channeler.cnodes) { if let Referent::CEdgeIncidence(p_cedge, i) = channeler.cnodes.get_key(p_referent).unwrap() { - let cedge = channeler.cedges.get(*p_cedge).unwrap(); - let p_cnode = - channeler.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; - if subnode_set.find_key(&p_cnode).is_some() { - // the sink is in our sphere, if any source is from the related - // subnodes then we need - for p_source in cedge.sources() { - let p_cnode = channeler + // avoid duplication, if this is a sink incidence we automatically have + // a one time iter of the edge we need to handle + if i.is_none() { + let cedge = channeler.cedges.get(*p_cedge).unwrap(); + // this is an `OrdArena` to handle the multiple incidents from the + // same set redundancy + let mut bulk_info = OrdArena::::new(); + for (i, p_source) in cedge.sources().iter().enumerate() { + let cnode = channeler.cnodes.get_val(*p_source).unwrap(); + // TODO if we commit to having a single supernode, have the info + // in the `CNode` value and not in a referent. + + // if cnode.supernode.unwrap() == ... + + if subnode_set.find_key(&cnode.p_this_cnode).is_none() { + // we have a source incident in the related set + let p = related_subnodes_set + .find_key(&cnode.p_this_cnode) + .unwrap(); + let p_related_subnode = + *related_subnodes_set.get_key(p).unwrap(); + let w = match cedge.programmability() { + Programmability::StaticLut(_) + | Programmability::ArbitraryLut(_) + | Programmability::SelectorLut(_) => 1, + Programmability::Bulk(bulk) => { + bulk.channel_entry_widths[i] + } + }; + // TODO `OrdArena` needs a function for the common update or + // insert new pattern, use find_similar internally instead + // of a potentially expensive replace + let (p, replaced) = bulk_info.insert(p_related_subnode, w); + if let Some((_, w_replaced)) = replaced { + *bulk_info.get_val_mut(p).unwrap() = + w.checked_add(w_replaced).unwrap(); + } + } + } + if bulk_info.is_empty() { + // the edge is internal, need to add to the internal LUT bit + // count + let internal_behavior = &mut channeler .cnodes - .get_val(cedge.sink()) + .get_val_mut(p_consider) .unwrap() - .p_this_cnode; + .internal_behavior; + let lut_bits = match cedge.programmability() { + Programmability::StaticLut(lut) => lut.bw(), + Programmability::ArbitraryLut(lut) => lut.len(), + Programmability::SelectorLut(_) => 0, + Programmability::Bulk(bulk_behavior) => { + bulk_behavior.lut_bits + } + }; + internal_behavior.lut_bits = + internal_behavior.lut_bits.checked_add(lut_bits).unwrap(); + } else { + let mut sources = vec![]; + let mut channel_entry_widths = vec![]; + for (_, source, width) in bulk_info { + sources.push(source); + channel_entry_widths.push(width); + } + let (channel_exit_width, lut_bits) = + match cedge.programmability() { + Programmability::StaticLut(lut) => (1, lut.bw()), + Programmability::ArbitraryLut(lut) => (1, lut.len()), + Programmability::SelectorLut(_) => (1, 0), + Programmability::Bulk(bulk_behavior) => ( + bulk_behavior.channel_exit_width, + bulk_behavior.lut_bits, + ), + }; + channeler.make_cedge( + &sources, + p_consider, + Programmability::Bulk(BulkBehavior { + channel_entry_widths, + channel_exit_width, + lut_bits, + }), + ); } } - match cedge.programmability() { - Programmability::StaticLut(_) => todo!(), - Programmability::ArbitraryLut(_) => todo!(), - Programmability::SelectorLut(_) => todo!(), - Programmability::Bulk(_) => todo!(), - } } } } - - //channeler.make_cedge(&[], &[], - // Programmability::Bulk(BulkBehavior { channel_entry_width: - // todo!(), channel_exit_width: todo!(), lut_bits: todo!() })); } } diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index 81f1c1db..e1e0b994 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -4,14 +4,13 @@ use std::path::PathBuf; use awint::awint_dag::{ - triple_arena::{Advancer, Arena, ArenaTrait, ChainArena}, + triple_arena::{Advancer, Arena, ChainArena}, triple_arena_render::{render_to_svg_file, DebugNode, DebugNodeTrait}, }; -use super::{cedge, Programmability}; use crate::{ ensemble, - route::{channel::Referent, CEdge, CNode, Channeler, PBack, PCEdge}, + route::{channel::Referent, CEdge, CNode, Channeler, PBack, PCEdge, Programmability}, Error, }; @@ -26,7 +25,7 @@ pub enum NodeKind { } impl DebugNodeTrait for NodeKind { - fn debug_node(p_this: PBack, this: &Self) -> DebugNode { + fn debug_node(_p_this: PBack, this: &Self) -> DebugNode { match this { NodeKind::CNode(cnode) => DebugNode { sources: vec![], @@ -46,11 +45,8 @@ impl DebugNodeTrait for NodeKind { NodeKind::CEdgeIncidence(p_back, p_cedge, i, cedge, cedge_forwarded) => DebugNode { sources: { let mut v = vec![(*p_back, String::new())]; - for (i, (source, source_forwarded)) in cedge - .sources() - .iter() - .zip(cedge_forwarded.sources().iter()) - .enumerate() + for (source, source_forwarded) in + cedge.sources().iter().zip(cedge_forwarded.sources().iter()) { v.push((*source_forwarded, format!("{source}"))); } @@ -92,7 +88,7 @@ pub enum HyperNodeKind { } impl DebugNodeTrait for HyperNodeKind { - fn debug_node(p_this: PBack, this: &Self) -> DebugNode { + fn debug_node(_p_this: PBack, this: &Self) -> DebugNode { match this { HyperNodeKind::CNode(cnode) => DebugNode { sources: vec![], @@ -102,11 +98,8 @@ impl DebugNodeTrait for HyperNodeKind { HyperNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { sources: { let mut v = vec![]; - for (i, (source, source_forwarded)) in cedge - .sources() - .iter() - .zip(cedge_forwarded.sources().iter()) - .enumerate() + for (source, source_forwarded) in + cedge.sources().iter().zip(cedge_forwarded.sources().iter()) { v.push((*source_forwarded, format!("{source}"))); } @@ -142,7 +135,7 @@ impl Channeler { Referent::SubNode(p_back) => NodeKind::SubNode(*p_back, p_cnode), Referent::SuperNode(p_back) => NodeKind::SuperNode(*p_back, p_cnode), Referent::CEdgeIncidence(p_cedge, i) => { - let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); + let cedge = self.cedges.get(*p_cedge).unwrap().clone(); let mut cedge_forwarded = cedge.clone(); for source in cedge_forwarded.sources_mut() { *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; @@ -171,7 +164,6 @@ impl Channeler { let mut arena = Arena::::new(); self.cnodes .clone_keys_to_arena(&mut arena, |p_self, referent| { - let p_cnode = self.cnodes.get_val(p_self).unwrap().clone().p_this_cnode; match referent { Referent::ThisCNode => { HyperNodeKind::CNode(self.cnodes.get_val(p_self).unwrap().clone()) @@ -181,7 +173,7 @@ impl Channeler { Referent::CEdgeIncidence(p_cedge, i) => { // insures that there is only one `CEdge` per set of incidents if i.is_none() { - let mut cedge = self.cedges.get(*p_cedge).unwrap().clone(); + let cedge = self.cedges.get(*p_cedge).unwrap().clone(); let mut cedge_forwarded = cedge.clone(); for source in cedge_forwarded.sources_mut() { *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; @@ -195,7 +187,7 @@ impl Channeler { HyperNodeKind::Remove } } - Referent::EnsembleBackRef(ensemble_p_backref) => HyperNodeKind::Remove, + Referent::EnsembleBackRef(_) => HyperNodeKind::Remove, } }); let mut adv = arena.advancer(); From 649dab81f7098893b911138d8a221b9f964661b3 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 15:06:28 -0600 Subject: [PATCH 109/119] fix bugs --- starlight/src/route/cedge.rs | 65 ++++++++++-------------------------- starlight/src/route/cnode.rs | 18 +++++----- 2 files changed, 26 insertions(+), 57 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 6079de9b..62e4bf51 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,7 +1,9 @@ use std::num::NonZeroUsize; use awint::{ - awint_dag::triple_arena::{surject_iterators::SurjectPtrAdvancer, Advancer, OrdArena}, + awint_dag::triple_arena::{ + surject_iterators::SurjectPtrAdvancer, Advancer, ArenaTrait, OrdArena, + }, Awi, }; @@ -363,12 +365,22 @@ impl Channeler { /// Advances over all neighbors of a node exactly once (do not mutate the /// surject of `p`). Note that `CNodeNeighborAdvancer` has a function for - /// getting the unique nodes. - pub fn advancer_neighbors_of_node(&self, p: PBack) -> CNodeNeighborAdvancer { - CNodeNeighborAdvancer { - adv: self.cnodes.advancer_surject(p), - unique: OrdArena::new(), + /// getting the unique nodes. Excludes `p` itself. + pub fn neighbors_of_node(&self, p: PBack) -> OrdArena { + let mut res = OrdArena::new(); + let mut adv = self.cnodes.advancer_surject(p); + while let Some(p_referent) = adv.advance(&self.cnodes) { + if let Referent::CEdgeIncidence(p_cedge, _) = self.cnodes.get_key(p_referent).unwrap() { + let cedge = self.cedges.get(*p_cedge).unwrap(); + cedge.incidents(|p_incident| { + let p_tmp = self.cnodes.get_val(p_incident).unwrap().p_this_cnode; + if p_tmp != p { + let _ = res.insert(p_tmp, ()); + } + }); + } } + res } /// Advances over all subnodes of a node @@ -391,47 +403,6 @@ impl Channeler { ptr_struct!(PUniqueCNode); -pub struct CNodeNeighborAdvancer { - adv: SurjectPtrAdvancer, - // we have multiedges, so we need to track unique CNodes - unique: OrdArena, -} - -impl CNodeNeighborAdvancer { - /// If this is called after advancing is done, this has all the unique - /// `CNode`s - pub fn into_unique(self) -> OrdArena { - self.unique - } -} - -impl Advancer for CNodeNeighborAdvancer { - type Collection = Channeler; - type Item = PBack; - - fn advance(&mut self, collection: &Self::Collection) -> Option { - while let Some(p_referent) = self.adv.advance(&collection.cnodes) { - if let Referent::CEdgeIncidence(p_cedge, i) = - collection.cnodes.get_key(p_referent).unwrap() - { - let cedge = collection.cedges.get(*p_cedge).unwrap(); - let p_neighbor = if let Some(source_i) = *i { - cedge.sources()[source_i] - } else { - cedge.sink() - }; - let p_neighbor = collection.cnodes.get_val(p_neighbor).unwrap().p_this_cnode; - let replace = self.unique.insert(p_neighbor, ()).1; - if replace.is_none() { - return Some(p_neighbor) - } - } - // need to be in a loop to skip over non-incidence referents - } - None - } -} - pub struct CNodeSubnodeAdvancer { adv: SurjectPtrAdvancer, } diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index d759f0a6..30dabea4 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -141,15 +141,14 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { if (cnode.lvl != current_lvl) || cnode.has_supernode { continue } + let neighbors = channeler.neighbors_of_node(p_consider); // check if the node's neighbors have supernodes - let mut neighbor_adv = channeler.advancer_neighbors_of_node(p_consider); - while let Some(p) = neighbor_adv.advance(channeler) { - if channeler.cnodes.get_val(p).unwrap().has_supernode { + for p_neighbor in neighbors.keys() { + if channeler.cnodes.get_val(*p_neighbor).unwrap().has_supernode { continue 'over_cnodes; } } // concentrate - let neighbors = neighbor_adv.into_unique(); channeler.make_top_level_cnode(neighbors.keys().copied(), next_lvl); concentrated = true; @@ -183,7 +182,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { } // TODO in the referents refactor, we need some formulaic way to add extra data // to the surject value structs to avoid all these `OrdArena`s - ptr_struct!(P0; P1; P2); + ptr_struct!(P0; P1; P2; P3); // first get the set of subnodes let mut subnode_set = OrdArena::::new(); let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); @@ -195,10 +194,9 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { let mut related_subnodes_set = OrdArena::::new(); let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); while let Some(p_subnode) = subnode_adv.advance(channeler) { - let mut second_neighbors = channeler.advancer_neighbors_of_node(p_subnode); - while let Some(p_neighbor) = second_neighbors.advance(channeler) { + for p_neighbor in channeler.neighbors_of_node(p_subnode).keys() { if subnode_set.find_key(&p_neighbor).is_none() { - let _ = related_subnodes_set.insert(p_neighbor, ()); + let _ = related_subnodes_set.insert(*p_neighbor, ()); } } } @@ -208,7 +206,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // incidents will contribute to the bulk behavior), and when the related cnode // is under consideration it will handle the edge in the other direction, so we // can avoid duplication. - let mut related_supernodes_set = OrdArena::::new(); + let mut related_supernodes_set = OrdArena::::new(); for p_related_subnode in related_subnodes_set.keys() { let p_related_supernode = channeler.get_supernode(*p_related_subnode).unwrap(); let _ = @@ -251,7 +249,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { let cedge = channeler.cedges.get(*p_cedge).unwrap(); // this is an `OrdArena` to handle the multiple incidents from the // same set redundancy - let mut bulk_info = OrdArena::::new(); + let mut bulk_info = OrdArena::::new(); for (i, p_source) in cedge.sources().iter().enumerate() { let cnode = channeler.cnodes.get_val(*p_source).unwrap(); // TODO if we commit to having a single supernode, have the info From d36a928514ed6dbb1f060c60cb6890c9da596a45 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 15:28:14 -0600 Subject: [PATCH 110/119] do not make `CNode`s for the configurables --- starlight/src/route/cedge.rs | 57 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 62e4bf51..71de3ea1 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -230,18 +230,25 @@ impl Channeler { pub fn new(ensemble: &Ensemble, configurator: &Configurator) -> Result { let mut channeler = Self::empty(); - // for each equivalence make a `CNode` with associated `EnsembleBackref` + // for each equivalence make a `CNode` with associated `EnsembleBackref`, unless + // it is one of the configurable bits for equiv in ensemble.backrefs.vals() { - let p_cnode = channeler.make_top_level_cnode(vec![], 0); - let channeler_backref = channeler - .cnodes - .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) - .unwrap(); - let replaced = channeler - .ensemble_backref_to_channeler_backref - .insert(equiv.p_self_equiv, channeler_backref) - .1; - assert!(replaced.is_none()); + if configurator + .configurations + .find_key(&equiv.p_self_equiv) + .is_none() + { + let p_cnode = channeler.make_top_level_cnode(vec![], 0); + let channeler_backref = channeler + .cnodes + .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) + .unwrap(); + let replaced = channeler + .ensemble_backref_to_channeler_backref + .insert(equiv.p_self_equiv, channeler_backref) + .1; + assert!(replaced.is_none()); + } } // translate from any ensemble backref to the equivalence backref to the @@ -250,7 +257,7 @@ impl Channeler { ensemble: &Ensemble, channeler: &Channeler, ensemble_backref: ensemble::PBack, - ) -> (ensemble::PBack, PBack) { + ) -> (ensemble::PBack, Option) { let p_equiv = ensemble .backrefs .get_val(ensemble_backref) @@ -258,29 +265,31 @@ impl Channeler { .p_self_equiv; let p0 = channeler .ensemble_backref_to_channeler_backref - .find_key(&p_equiv) - .unwrap(); - let channeler_p_back = *channeler - .ensemble_backref_to_channeler_backref - .get_val(p0) - .unwrap(); - (p_equiv, channeler_p_back) + .find_key(&p_equiv); + if let Some(p0) = p0 { + let channeler_p_back = *channeler + .ensemble_backref_to_channeler_backref + .get_val(p0) + .unwrap(); + (p_equiv, Some(channeler_p_back)) + } else { + (p_equiv, None) + } } // add `CEdge`s according to `LNode`s for lnode in ensemble.lnodes.vals() { - let p_self = translate(ensemble, &channeler, lnode.p_self).1; + let p_self = translate(ensemble, &channeler, lnode.p_self).1.unwrap(); match &lnode.kind { LNodeKind::Copy(_) => return Err(Error::OtherStr("the epoch was not optimized")), LNodeKind::Lut(inp, awi) => { let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); for input in inp { - v.push(translate(ensemble, &channeler, *input).1); + v.push(translate(ensemble, &channeler, *input).1.unwrap()); } channeler.make_cedge(&v, p_self, Programmability::StaticLut(awi.clone())); } LNodeKind::DynamicLut(inp, lut) => { - //let p_self = translate(ensemble, &channeler, lnode.p_self).1; let mut is_full_selector = true; for input in inp { let p_equiv = translate(ensemble, &channeler, *input).0; @@ -325,7 +334,7 @@ impl Channeler { } } DynamicValue::Dynam(p) => { - v.push(translate(ensemble, &channeler, *p).1); + v.push(translate(ensemble, &channeler, *p).1.unwrap()); } } } @@ -338,7 +347,7 @@ impl Channeler { (false, true) => { let mut v = SmallVec::<[PBack; 8]>::with_capacity(inp.len()); for input in inp { - v.push(translate(ensemble, &channeler, *input).1); + v.push(translate(ensemble, &channeler, *input).1.unwrap()); } let mut config = vec![]; for lut_bit in lut.iter() { From e85b2411c4b512f297a5088b648bbc0fa4e25033 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 16:05:19 -0600 Subject: [PATCH 111/119] Update debug.rs --- starlight/src/route/debug.rs | 151 +++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 17 deletions(-) diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index e1e0b994..78e2aea9 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -14,6 +14,7 @@ use crate::{ Error, }; +/// For viewing everything at once #[derive(Debug, Clone)] pub enum NodeKind { CNode(CNode), @@ -80,22 +81,23 @@ impl DebugNodeTrait for NodeKind { } } +/// For viewing only the base level #[derive(Debug, Clone)] -pub enum HyperNodeKind { +pub enum BaseNodeKind { CNode(CNode), CEdge(CEdge, CEdge), Remove, } -impl DebugNodeTrait for HyperNodeKind { +impl DebugNodeTrait for BaseNodeKind { fn debug_node(_p_this: PBack, this: &Self) -> DebugNode { match this { - HyperNodeKind::CNode(cnode) => DebugNode { + BaseNodeKind::CNode(cnode) => DebugNode { sources: vec![], center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, sinks: vec![], }, - HyperNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { + BaseNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { sources: { let mut v = vec![]; for (source, source_forwarded) in @@ -117,7 +119,55 @@ impl DebugNodeTrait for HyperNodeKind { }, sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, }, - HyperNodeKind::Remove => panic!("should have been removed"), + BaseNodeKind::Remove => panic!("should have been removed"), + } + } +} + +/// For viewing the hierarchy structure +#[derive(Debug, Clone)] +pub enum HierarchyNodeKind { + // supernode edge is stored on the end + CNode(CNode, Option), + CEdge(CEdge, CEdge), + Remove, +} + +impl DebugNodeTrait for HierarchyNodeKind { + fn debug_node(_p_this: PBack, this: &Self) -> DebugNode { + match this { + HierarchyNodeKind::CNode(cnode, p_super) => DebugNode { + sources: if let Some(p_super) = p_super { + vec![(*p_super, String::new())] + } else { + vec![] + }, + center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, + sinks: vec![], + }, + HierarchyNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { + sources: { + let mut v = vec![]; + for (source, source_forwarded) in + cedge.sources().iter().zip(cedge_forwarded.sources().iter()) + { + v.push((*source_forwarded, format!("{source}"))); + } + v + }, + center: { + let mut v = vec![]; + v.push(match cedge_forwarded.programmability() { + Programmability::StaticLut(_) => "StaticLut".to_owned(), + Programmability::ArbitraryLut(_) => "ArbitraryLut".to_owned(), + Programmability::SelectorLut(_) => "SelectorLut".to_owned(), + Programmability::Bulk(_) => "Bulk".to_owned(), + }); + v + }, + sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, + }, + HierarchyNodeKind::Remove => panic!("should have been removed"), } } } @@ -160,16 +210,75 @@ impl Channeler { arena } - pub fn to_cnode_graph_debug(&self) -> Arena { - let mut arena = Arena::::new(); + pub fn to_cnode_base_level_debug(&self) -> Arena { + let mut arena = Arena::::new(); self.cnodes .clone_keys_to_arena(&mut arena, |p_self, referent| { match referent { Referent::ThisCNode => { - HyperNodeKind::CNode(self.cnodes.get_val(p_self).unwrap().clone()) + let cnode = self.cnodes.get_val(p_self).unwrap(); + if cnode.lvl == 0 { + BaseNodeKind::CNode(cnode.clone()) + } else { + BaseNodeKind::Remove + } + } + Referent::SubNode(_) => BaseNodeKind::Remove, + Referent::SuperNode(_) => BaseNodeKind::Remove, + Referent::CEdgeIncidence(p_cedge, i) => { + // insures that there is only one `CEdge` per set of incidents + if i.is_none() { + let cedge = self.cedges.get(*p_cedge).unwrap().clone(); + let is_base = match cedge.programmability() { + Programmability::StaticLut(_) => true, + Programmability::ArbitraryLut(_) => true, + Programmability::SelectorLut(_) => true, + Programmability::Bulk(_) => false, + }; + if is_base { + let mut cedge_forwarded = cedge.clone(); + for source in cedge_forwarded.sources_mut() { + *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; + } + if i.is_none() { + *cedge_forwarded.sink_mut() = + self.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; + } + BaseNodeKind::CEdge(cedge, cedge_forwarded) + } else { + BaseNodeKind::Remove + } + } else { + BaseNodeKind::Remove + } + } + Referent::EnsembleBackRef(_) => BaseNodeKind::Remove, + } + }); + let mut adv = arena.advancer(); + while let Some(p) = adv.advance(&arena) { + if let BaseNodeKind::Remove = arena.get(p).unwrap() { + arena.remove(p).unwrap(); + } + } + arena + } + + pub fn to_cnode_hierarchy_debug(&self) -> Arena { + let mut arena = Arena::::new(); + self.cnodes + .clone_keys_to_arena(&mut arena, |p_self, referent| { + match referent { + Referent::ThisCNode => { + let cnode = self.cnodes.get_val(p_self).unwrap(); + if let Some(p) = self.get_supernode(cnode.p_this_cnode) { + HierarchyNodeKind::CNode(cnode.clone(), Some(p)) + } else { + HierarchyNodeKind::CNode(cnode.clone(), None) + } } - Referent::SubNode(_) => HyperNodeKind::Remove, - Referent::SuperNode(_) => HyperNodeKind::Remove, + Referent::SubNode(_) => HierarchyNodeKind::Remove, + Referent::SuperNode(_) => HierarchyNodeKind::Remove, Referent::CEdgeIncidence(p_cedge, i) => { // insures that there is only one `CEdge` per set of incidents if i.is_none() { @@ -182,17 +291,17 @@ impl Channeler { *cedge_forwarded.sink_mut() = self.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; } - HyperNodeKind::CEdge(cedge, cedge_forwarded) + HierarchyNodeKind::CEdge(cedge, cedge_forwarded) } else { - HyperNodeKind::Remove + HierarchyNodeKind::Remove } } - Referent::EnsembleBackRef(_) => HyperNodeKind::Remove, + Referent::EnsembleBackRef(_) => HierarchyNodeKind::Remove, } }); let mut adv = arena.advancer(); while let Some(p) = adv.advance(&arena) { - if let HyperNodeKind::Remove = arena.get(p).unwrap() { + if let HierarchyNodeKind::Remove = arena.get(p).unwrap() { arena.remove(p).unwrap(); } } @@ -213,11 +322,19 @@ impl Channeler { }; let mut cnode_backrefs_file = dir.clone(); cnode_backrefs_file.push("cnode_backrefs.svg"); - let mut cnode_graph_file = dir; - cnode_graph_file.push("cnode_graph.svg"); + let mut cnode_base_file = dir.clone(); + cnode_base_file.push("cnode_base.svg"); + let mut cnode_hierarchy_file = dir; + cnode_hierarchy_file.push("cnode_hierarchy.svg"); let res = self.verify_integrity(); render_to_svg_file(&self.to_cnode_backrefs_debug(), false, cnode_backrefs_file).unwrap(); - render_to_svg_file(&self.to_cnode_graph_debug(), false, cnode_graph_file).unwrap(); + render_to_svg_file(&self.to_cnode_base_level_debug(), false, cnode_base_file).unwrap(); + render_to_svg_file( + &self.to_cnode_hierarchy_debug(), + false, + cnode_hierarchy_file, + ) + .unwrap(); res } From c2810633a6bbaa655c8415dcfd6d15d9fff93fd4 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Wed, 17 Jan 2024 18:40:27 -0600 Subject: [PATCH 112/119] fix bugs --- starlight/src/route/cedge.rs | 12 +++++------- starlight/src/route/cnode.rs | 18 +++++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 71de3ea1..dc55061d 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -372,20 +372,18 @@ impl Channeler { Ok(channeler) } - /// Advances over all neighbors of a node exactly once (do not mutate the - /// surject of `p`). Note that `CNodeNeighborAdvancer` has a function for - /// getting the unique nodes. Excludes `p` itself. - pub fn neighbors_of_node(&self, p: PBack) -> OrdArena { + /// Returns an `OrdArena` of `ThisCNode` `PBack`s of `p` itself and all + /// nodes directly incident to it through edges. + pub fn related_nodes(&self, p: PBack) -> OrdArena { let mut res = OrdArena::new(); + res.insert(p, ()); let mut adv = self.cnodes.advancer_surject(p); while let Some(p_referent) = adv.advance(&self.cnodes) { if let Referent::CEdgeIncidence(p_cedge, _) = self.cnodes.get_key(p_referent).unwrap() { let cedge = self.cedges.get(*p_cedge).unwrap(); cedge.incidents(|p_incident| { let p_tmp = self.cnodes.get_val(p_incident).unwrap().p_this_cnode; - if p_tmp != p { - let _ = res.insert(p_tmp, ()); - } + let _ = res.insert(p_tmp, ()); }); } } diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index 30dabea4..dec2966f 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -141,15 +141,19 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { if (cnode.lvl != current_lvl) || cnode.has_supernode { continue } - let neighbors = channeler.neighbors_of_node(p_consider); + let related = channeler.related_nodes(p_consider); + if related.len() == 1 { + // the node is disconnected + continue + } // check if the node's neighbors have supernodes - for p_neighbor in neighbors.keys() { + for p_neighbor in related.keys() { if channeler.cnodes.get_val(*p_neighbor).unwrap().has_supernode { continue 'over_cnodes; } } // concentrate - channeler.make_top_level_cnode(neighbors.keys().copied(), next_lvl); + channeler.make_top_level_cnode(related.keys().copied(), next_lvl); concentrated = true; } @@ -159,7 +163,7 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { break } // for nodes that couldn't be concentrated, create single subnode supernodes for - // them + // them, so that edges are only between nodes at the same level let mut adv = channeler.cnodes.advancer(); while let Some(p_consider) = adv.advance(&channeler.cnodes) { if let Referent::ThisCNode = channeler.cnodes.get_key(p_consider).unwrap() { @@ -194,9 +198,9 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { let mut related_subnodes_set = OrdArena::::new(); let mut subnode_adv = channeler.advancer_subnodes_of_node(p_consider); while let Some(p_subnode) = subnode_adv.advance(channeler) { - for p_neighbor in channeler.neighbors_of_node(p_subnode).keys() { - if subnode_set.find_key(&p_neighbor).is_none() { - let _ = related_subnodes_set.insert(*p_neighbor, ()); + for p_related in channeler.related_nodes(p_subnode).keys() { + if subnode_set.find_key(p_related).is_none() { + let _ = related_subnodes_set.insert(*p_related, ()); } } } From 04b73ef79706f33b5c0e3a317601369a7bc79291 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 18 Jan 2024 14:36:30 -0600 Subject: [PATCH 113/119] avoid unnecessary assertions --- starlight/src/awi_structs/epoch.rs | 43 +++++++++++++++++------------- starlight/src/route/channel.rs | 4 ++- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index e7de221e..db2ede30 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -494,25 +494,30 @@ pub fn _callback() -> EpochCallback { }) } fn register_assertion_bit(bit: dag::bool, location: Location) { - // need a new bit to attach new location data to - let new_bit = new_pstate(bw(1), Op::Assert([bit.state()]), Some(location)); - let eval_awi = EvalAwi::from_state(new_bit); - // manual to get around closure issue - CURRENT_EPOCH.with(|top| { - let mut top = top.borrow_mut(); - if let Some(current) = top.as_mut() { - let mut epoch_data = current.epoch_data.borrow_mut(); - epoch_data - .responsible_for - .get_mut(current.p_self) - .unwrap() - .assertions - .bits - .push(eval_awi); - } else { - panic!("There needs to be an `Epoch` in scope for this to work"); - } - }) + if let Some(awi) = bit.state().try_get_as_awi() { + assert_eq!(awi.bw(), 1); + // don't need to do anything + } else { + // need a new bit to attach new location data to + let new_bit = new_pstate(bw(1), Op::Assert([bit.state()]), Some(location)); + let eval_awi = EvalAwi::from_state(new_bit); + // manual to get around closure issue + CURRENT_EPOCH.with(|top| { + let mut top = top.borrow_mut(); + if let Some(current) = top.as_mut() { + let mut epoch_data = current.epoch_data.borrow_mut(); + epoch_data + .responsible_for + .get_mut(current.p_self) + .unwrap() + .assertions + .bits + .push(eval_awi); + } else { + panic!("There needs to be an `Epoch` in scope for this to work"); + } + }) + } } fn get_nzbw(p_state: PState) -> NonZeroUsize { no_recursive_current_epoch(|current| { diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index d1c71d5c..1088eb88 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -198,7 +198,9 @@ impl Channeler { selector_lut.verify_integrity(sources_len)?; true } - Programmability::Bulk(_) => todo!(), + Programmability::Bulk(bulk_behavior) => { + bulk_behavior.channel_entry_widths.len() == cedge.sources().len() + } }; if !ok { return Err(Error::OtherString(format!( From 46c06945acc18af54b28e870ccbaab876e30e39b Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 18 Jan 2024 15:06:55 -0600 Subject: [PATCH 114/119] fix bug --- starlight/src/route/cnode.rs | 5 +++- starlight/src/route/debug.rs | 54 +++++++++++++++++------------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index dec2966f..ded31ffb 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -276,10 +276,13 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { bulk.channel_entry_widths[i] } }; + let p_related_supernode = + channeler.get_supernode(p_related_subnode).unwrap(); // TODO `OrdArena` needs a function for the common update or // insert new pattern, use find_similar internally instead // of a potentially expensive replace - let (p, replaced) = bulk_info.insert(p_related_subnode, w); + let (p, replaced) = + bulk_info.insert(p_related_supernode, w); if let Some((_, w_replaced)) = replaced { *bulk_info.get_val_mut(p).unwrap() = w.checked_add(w_replaced).unwrap(); diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index 78e2aea9..e87ba4aa 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -81,23 +81,23 @@ impl DebugNodeTrait for NodeKind { } } -/// For viewing only the base level +/// For viewing the cgraph at only one level #[derive(Debug, Clone)] -pub enum BaseNodeKind { +pub enum LevelNodeKind { CNode(CNode), CEdge(CEdge, CEdge), Remove, } -impl DebugNodeTrait for BaseNodeKind { +impl DebugNodeTrait for LevelNodeKind { fn debug_node(_p_this: PBack, this: &Self) -> DebugNode { match this { - BaseNodeKind::CNode(cnode) => DebugNode { + LevelNodeKind::CNode(cnode) => DebugNode { sources: vec![], center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, sinks: vec![], }, - BaseNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { + LevelNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { sources: { let mut v = vec![]; for (source, source_forwarded) in @@ -119,7 +119,7 @@ impl DebugNodeTrait for BaseNodeKind { }, sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, }, - BaseNodeKind::Remove => panic!("should have been removed"), + LevelNodeKind::Remove => panic!("should have been removed"), } } } @@ -210,32 +210,28 @@ impl Channeler { arena } - pub fn to_cnode_base_level_debug(&self) -> Arena { - let mut arena = Arena::::new(); + pub fn to_cnode_level_debug(&self, lvl: usize) -> Arena { + let mut arena = Arena::::new(); self.cnodes .clone_keys_to_arena(&mut arena, |p_self, referent| { match referent { Referent::ThisCNode => { let cnode = self.cnodes.get_val(p_self).unwrap(); - if cnode.lvl == 0 { - BaseNodeKind::CNode(cnode.clone()) + if cnode.lvl == u16::try_from(lvl).unwrap() { + LevelNodeKind::CNode(cnode.clone()) } else { - BaseNodeKind::Remove + LevelNodeKind::Remove } } - Referent::SubNode(_) => BaseNodeKind::Remove, - Referent::SuperNode(_) => BaseNodeKind::Remove, + Referent::SubNode(_) => LevelNodeKind::Remove, + Referent::SuperNode(_) => LevelNodeKind::Remove, Referent::CEdgeIncidence(p_cedge, i) => { // insures that there is only one `CEdge` per set of incidents if i.is_none() { let cedge = self.cedges.get(*p_cedge).unwrap().clone(); - let is_base = match cedge.programmability() { - Programmability::StaticLut(_) => true, - Programmability::ArbitraryLut(_) => true, - Programmability::SelectorLut(_) => true, - Programmability::Bulk(_) => false, - }; - if is_base { + if self.cnodes.get_val(cedge.sink()).unwrap().lvl + == u16::try_from(lvl).unwrap() + { let mut cedge_forwarded = cedge.clone(); for source in cedge_forwarded.sources_mut() { *source = self.cnodes.get_val(*source).unwrap().p_this_cnode; @@ -244,20 +240,20 @@ impl Channeler { *cedge_forwarded.sink_mut() = self.cnodes.get_val(cedge.sink()).unwrap().p_this_cnode; } - BaseNodeKind::CEdge(cedge, cedge_forwarded) + LevelNodeKind::CEdge(cedge, cedge_forwarded) } else { - BaseNodeKind::Remove + LevelNodeKind::Remove } } else { - BaseNodeKind::Remove + LevelNodeKind::Remove } } - Referent::EnsembleBackRef(_) => BaseNodeKind::Remove, + Referent::EnsembleBackRef(_) => LevelNodeKind::Remove, } }); let mut adv = arena.advancer(); while let Some(p) = adv.advance(&arena) { - if let BaseNodeKind::Remove = arena.get(p).unwrap() { + if let LevelNodeKind::Remove = arena.get(p).unwrap() { arena.remove(p).unwrap(); } } @@ -308,7 +304,7 @@ impl Channeler { arena } - pub fn render_to_svgs_in_dir(&self, out_file: PathBuf) -> Result<(), Error> { + pub fn render_to_svgs_in_dir(&self, lvl: usize, out_file: PathBuf) -> Result<(), Error> { let dir = match out_file.canonicalize() { Ok(o) => { if !o.is_dir() { @@ -322,13 +318,13 @@ impl Channeler { }; let mut cnode_backrefs_file = dir.clone(); cnode_backrefs_file.push("cnode_backrefs.svg"); - let mut cnode_base_file = dir.clone(); - cnode_base_file.push("cnode_base.svg"); + let mut cnode_level_file = dir.clone(); + cnode_level_file.push("cnode_level.svg"); let mut cnode_hierarchy_file = dir; cnode_hierarchy_file.push("cnode_hierarchy.svg"); let res = self.verify_integrity(); render_to_svg_file(&self.to_cnode_backrefs_debug(), false, cnode_backrefs_file).unwrap(); - render_to_svg_file(&self.to_cnode_base_level_debug(), false, cnode_base_file).unwrap(); + render_to_svg_file(&self.to_cnode_level_debug(lvl), false, cnode_level_file).unwrap(); render_to_svg_file( &self.to_cnode_hierarchy_debug(), false, From 49c4c11ab062c3bf3909ae497ca33a436cbbfbe3 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 18 Jan 2024 15:34:10 -0600 Subject: [PATCH 115/119] debug improvements --- starlight/src/route/cedge.rs | 34 ++++++++++++++++++++++++++++--- starlight/src/route/cnode.rs | 39 +++++++++++++++++++++++++++++------- starlight/src/route/debug.rs | 36 +++++++++++++-------------------- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index dc55061d..0ab35b82 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroUsize; +use std::{fmt::Write, num::NonZeroUsize}; use awint::{ awint_dag::triple_arena::{ @@ -11,7 +11,11 @@ use crate::{ awint_dag::smallvec::SmallVec, ensemble, ensemble::{DynamicValue, Ensemble, LNodeKind}, - route::{channel::Referent, cnode::generate_hierarchy, CNode, Channeler, Configurator, PBack}, + route::{ + channel::Referent, + cnode::{generate_hierarchy, InternalBehavior}, + CNode, Channeler, Configurator, PBack, + }, triple_arena::ptr_struct, Error, SuspendedEpoch, }; @@ -115,6 +119,30 @@ pub enum Programmability { Bulk(BulkBehavior), } +impl Programmability { + pub fn debug_strings(&self) -> Vec { + let mut v = vec![]; + match self { + Programmability::StaticLut(lut) => v.push(format!("{}", lut.bw().trailing_zeros())), + Programmability::ArbitraryLut(lut) => { + v.push(format!("ArbLut {}", lut.len().trailing_zeros())) + } + Programmability::SelectorLut(selector_lut) => { + v.push(format!("SelLut {}", selector_lut.v.len().trailing_zeros())) + } + Programmability::Bulk(bulk) => { + let mut s = String::new(); + for width in &bulk.channel_entry_widths { + write!(s, " {}", width).unwrap(); + } + v.push(s); + v.push(format!("lut_bits {}", bulk.lut_bits)); + } + } + v + } +} + /// An edge between channels #[derive(Debug, Clone)] pub struct CEdge { @@ -238,7 +266,7 @@ impl Channeler { .find_key(&equiv.p_self_equiv) .is_none() { - let p_cnode = channeler.make_top_level_cnode(vec![], 0); + let p_cnode = channeler.make_top_level_cnode(vec![], 0, InternalBehavior::empty()); let channeler_backref = channeler .cnodes .insert_key(p_cnode, Referent::EnsembleBackRef(equiv.p_self_equiv)) diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index ded31ffb..2335bda8 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -6,7 +6,7 @@ use crate::route::{channel::Referent, BulkBehavior, Channeler, PBack, Programmab #[derive(Debug, Clone)] pub struct InternalBehavior { - lut_bits: usize, + pub lut_bits: usize, } impl InternalBehavior { @@ -24,10 +24,21 @@ pub struct CNode { pub internal_behavior: InternalBehavior, } +impl CNode { + pub fn internal_behavior(&self) -> &InternalBehavior { + &self.internal_behavior + } +} + impl Channeler { /// Given the `subnodes` (which should point to unique `ThisCNode`s) for a /// new top level `CNode`, this will manage the backrefs - pub fn make_top_level_cnode(&mut self, subnodes: I, lvl: u16) -> PBack + pub fn make_top_level_cnode( + &mut self, + subnodes: I, + lvl: u16, + internal_behavior: InternalBehavior, + ) -> PBack where I: IntoIterator, { @@ -36,7 +47,7 @@ impl Channeler { p_this_cnode, lvl, has_supernode: false, - internal_behavior: InternalBehavior::empty(), + internal_behavior, }) }); for subnode in subnodes { @@ -153,7 +164,11 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { } } // concentrate - channeler.make_top_level_cnode(related.keys().copied(), next_lvl); + channeler.make_top_level_cnode( + related.keys().copied(), + next_lvl, + InternalBehavior::empty(), + ); concentrated = true; } @@ -171,7 +186,12 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { if (cnode.lvl != current_lvl) || cnode.has_supernode { continue } - channeler.make_top_level_cnode([p_consider], next_lvl); + // need to also forward the internal behavior + channeler.make_top_level_cnode( + [p_consider], + next_lvl, + cnode.internal_behavior().clone(), + ); } } @@ -349,10 +369,15 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { if channeler.top_level_cnodes.len() > 1 { let mut set = vec![]; let mut max_lvl = 0; + let mut lut_bits = 0usize; for p_cnode in channeler.top_level_cnodes.keys() { set.push(*p_cnode); - max_lvl = max(max_lvl, channeler.cnodes.get_val(*p_cnode).unwrap().lvl) + let cnode = channeler.cnodes.get_val(*p_cnode).unwrap(); + max_lvl = max(max_lvl, cnode.lvl); + lut_bits = lut_bits + .checked_add(cnode.internal_behavior().lut_bits) + .unwrap(); } - channeler.make_top_level_cnode(set, max_lvl); + channeler.make_top_level_cnode(set, max_lvl, InternalBehavior { lut_bits }); } } diff --git a/starlight/src/route/debug.rs b/starlight/src/route/debug.rs index e87ba4aa..0d5f76f3 100644 --- a/starlight/src/route/debug.rs +++ b/starlight/src/route/debug.rs @@ -94,7 +94,12 @@ impl DebugNodeTrait for LevelNodeKind { match this { LevelNodeKind::CNode(cnode) => DebugNode { sources: vec![], - center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, + center: { + vec![ + format!("{} cnode {}", cnode.lvl, cnode.internal_behavior.lut_bits), + format!("{}", cnode.p_this_cnode), + ] + }, sinks: vec![], }, LevelNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { @@ -107,16 +112,7 @@ impl DebugNodeTrait for LevelNodeKind { } v }, - center: { - let mut v = vec![]; - v.push(match cedge_forwarded.programmability() { - Programmability::StaticLut(_) => "StaticLut".to_owned(), - Programmability::ArbitraryLut(_) => "ArbitraryLut".to_owned(), - Programmability::SelectorLut(_) => "SelectorLut".to_owned(), - Programmability::Bulk(_) => "Bulk".to_owned(), - }); - v - }, + center: { cedge.programmability().debug_strings() }, sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, }, LevelNodeKind::Remove => panic!("should have been removed"), @@ -142,7 +138,12 @@ impl DebugNodeTrait for HierarchyNodeKind { } else { vec![] }, - center: { vec!["cnode".to_owned(), format!("{}", cnode.p_this_cnode)] }, + center: { + vec![ + format!("{} cnode {}", cnode.lvl, cnode.internal_behavior.lut_bits), + format!("{}", cnode.p_this_cnode), + ] + }, sinks: vec![], }, HierarchyNodeKind::CEdge(cedge, cedge_forwarded) => DebugNode { @@ -155,16 +156,7 @@ impl DebugNodeTrait for HierarchyNodeKind { } v }, - center: { - let mut v = vec![]; - v.push(match cedge_forwarded.programmability() { - Programmability::StaticLut(_) => "StaticLut".to_owned(), - Programmability::ArbitraryLut(_) => "ArbitraryLut".to_owned(), - Programmability::SelectorLut(_) => "SelectorLut".to_owned(), - Programmability::Bulk(_) => "Bulk".to_owned(), - }); - v - }, + center: { cedge.programmability().debug_strings() }, sinks: { vec![(cedge_forwarded.sink(), "".to_owned())] }, }, HierarchyNodeKind::Remove => panic!("should have been removed"), From 347c55d76b16cd0a538680bde39c13281be8cf7c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 18 Jan 2024 16:10:58 -0600 Subject: [PATCH 116/119] Update cnode.rs --- starlight/src/route/cnode.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/starlight/src/route/cnode.rs b/starlight/src/route/cnode.rs index 2335bda8..3af9d1c3 100644 --- a/starlight/src/route/cnode.rs +++ b/starlight/src/route/cnode.rs @@ -157,17 +157,31 @@ pub fn generate_hierarchy(channeler: &mut Channeler) { // the node is disconnected continue } - // check if the node's neighbors have supernodes - for p_neighbor in related.keys() { - if channeler.cnodes.get_val(*p_neighbor).unwrap().has_supernode { + // check if any related nodes have supernodes + for p_related in related.keys() { + if channeler.cnodes.get_val(*p_related).unwrap().has_supernode { continue 'over_cnodes; } } + // add up internal bits + let mut lut_bits = 0usize; + for p in related.keys() { + lut_bits = lut_bits + .checked_add( + channeler + .cnodes + .get_val(*p) + .unwrap() + .internal_behavior() + .lut_bits, + ) + .unwrap(); + } // concentrate channeler.make_top_level_cnode( related.keys().copied(), next_lvl, - InternalBehavior::empty(), + InternalBehavior { lut_bits }, ); concentrated = true; From 0d1fb2d1787e99d7988a4ff0827cc0184f510e1e Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 18 Jan 2024 16:29:18 -0600 Subject: [PATCH 117/119] fix assertion registration --- starlight/src/awi_structs/epoch.rs | 8 ++++++-- starlight/src/route/cedge.rs | 8 +++----- testcrate/tests/basic.rs | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/starlight/src/awi_structs/epoch.rs b/starlight/src/awi_structs/epoch.rs index db2ede30..7accdf14 100644 --- a/starlight/src/awi_structs/epoch.rs +++ b/starlight/src/awi_structs/epoch.rs @@ -494,10 +494,14 @@ pub fn _callback() -> EpochCallback { }) } fn register_assertion_bit(bit: dag::bool, location: Location) { - if let Some(awi) = bit.state().try_get_as_awi() { + let need_register = if let Some(awi) = bit.state().try_get_as_awi() { assert_eq!(awi.bw(), 1); - // don't need to do anything + // only need to register false bits so the location can get propogated + awi.is_zero() } else { + true + }; + if need_register { // need a new bit to attach new location data to let new_bit = new_pstate(bw(1), Op::Assert([bit.state()]), Some(location)); let eval_awi = EvalAwi::from_state(new_bit); diff --git a/starlight/src/route/cedge.rs b/starlight/src/route/cedge.rs index 0ab35b82..120a9336 100644 --- a/starlight/src/route/cedge.rs +++ b/starlight/src/route/cedge.rs @@ -123,12 +123,10 @@ impl Programmability { pub fn debug_strings(&self) -> Vec { let mut v = vec![]; match self { - Programmability::StaticLut(lut) => v.push(format!("{}", lut.bw().trailing_zeros())), - Programmability::ArbitraryLut(lut) => { - v.push(format!("ArbLut {}", lut.len().trailing_zeros())) - } + Programmability::StaticLut(lut) => v.push(format!("{}", lut)), + Programmability::ArbitraryLut(lut) => v.push(format!("ArbLut {}", lut.len())), Programmability::SelectorLut(selector_lut) => { - v.push(format!("SelLut {}", selector_lut.v.len().trailing_zeros())) + v.push(format!("SelLut {}", selector_lut.v.len())) } Programmability::Bulk(bulk) => { let mut s = String::new(); diff --git a/testcrate/tests/basic.rs b/testcrate/tests/basic.rs index 793a2efb..6954399f 100644 --- a/testcrate/tests/basic.rs +++ b/testcrate/tests/basic.rs @@ -1,4 +1,9 @@ -use starlight::{awi, awi::*, dag, Epoch, EvalAwi, LazyAwi}; +use starlight::{ + awi, + awi::*, + awint_dag::{epoch::register_assertion_bit_for_current_epoch, Location}, + dag, Epoch, EvalAwi, LazyAwi, +}; #[test] fn lazy_awi() { @@ -78,6 +83,18 @@ fn multiplier() { drop(epoch); } +#[test] +fn const_assertion_fail() { + let epoch = Epoch::new(); + // directly register because most of the functions calling this have their own + // handling + register_assertion_bit_for_current_epoch(false.into(), Location::dummy()); + { + awi::assert!(epoch.assert_assertions(false).is_err()); + } + drop(epoch); +} + #[test] fn unknown_masking() { use dag::*; From db7911a7691038c0a7136c906a918f7c24ee1b05 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 22 Jan 2024 22:10:32 -0600 Subject: [PATCH 118/119] use `awint` 0.16 --- CHANGELOG.md | 3 ++- starlight/Cargo.toml | 4 ++-- starlight/src/route/channel.rs | 4 ++++ starlight/src/route/path.rs | 9 +++++++++ starlight/src/route/router.rs | 32 ++++++++++++++++++++++++++++++-- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb0432c..be4a7f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ - merged `Epoch::assert_assertions` and `Epoch::assert_assertions_strict` - many fixes for `Epoch` behavior - `LNode`s now have a `LNodeKind` -- `StarRng::index` was renamed to `index_slice`, and a `index_slice_mut` and new `index` function were added +- `StarRng::index` was renamed to `index_slice`, and a `index_slice_mut` and new `index` function + were added - Redid the error system ### Additions diff --git a/starlight/Cargo.toml b/starlight/Cargo.toml index 637fd32e..4b9443b2 100644 --- a/starlight/Cargo.toml +++ b/starlight/Cargo.toml @@ -12,8 +12,8 @@ keywords = ["dag", "rtl", "hdl"] categories = ["algorithms"] [dependencies] -awint = { path = "../../awint/awint", default-features = false, features = ["rand_support", "dag"] } -#awint = { version = "0.15", default-features = false, features = ["rand_support", "dag"] } +#awint = { path = "../../awint/awint", default-features = false, features = ["rand_support", "dag"] } +awint = { version = "0.16", default-features = false, features = ["rand_support", "dag"] } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rand_xoshiro = { version = "0.6", default-features = false } thiserror = "1.0" diff --git a/starlight/src/route/channel.rs b/starlight/src/route/channel.rs index 1088eb88..5de8eee5 100644 --- a/starlight/src/route/channel.rs +++ b/starlight/src/route/channel.rs @@ -9,6 +9,10 @@ use crate::{ ptr_struct!(P0; PTopLevel; PBack); +// TODO Mapping nodes, Divergence edges, and Convergence edges? Or are we only +// going to end up with Convergence edges and the hyperpath claws work from the +// sink perspectives? + #[derive(Debug, Clone, Copy)] pub enum Referent { ThisCNode, diff --git a/starlight/src/route/path.rs b/starlight/src/route/path.rs index 2836578d..f49a3309 100644 --- a/starlight/src/route/path.rs +++ b/starlight/src/route/path.rs @@ -30,3 +30,12 @@ pub struct HyperPath { source: PBack, paths: Vec, } + +impl HyperPath { + pub fn new(source: PBack) -> Self { + Self { + source, + paths: vec![], + } + } +} diff --git a/starlight/src/route/router.rs b/starlight/src/route/router.rs index 9b28f5b8..276abd30 100644 --- a/starlight/src/route/router.rs +++ b/starlight/src/route/router.rs @@ -2,7 +2,7 @@ use awint::awint_dag::triple_arena::{ptr_struct, OrdArena}; use crate::{ ensemble::{self, Ensemble, PExternal}, - route::{Channeler, HyperPath, PHyperPath}, + route::{Channeler, HyperPath, PBack, PCEdge, PHyperPath}, triple_arena::Arena, Error, EvalAwi, LazyAwi, SuspendedEpoch, }; @@ -180,6 +180,15 @@ fn route(router: &mut Router) -> Result<(), Error> { // initialization of hyperpaths assert_eq!(router.target_channeler().top_level_cnodes.len(), 1); assert_eq!(router.program_channeler().top_level_cnodes.len(), 1); + ptr_struct!(P0; P1); + let p = router.target_channeler().top_level_cnodes.min().unwrap(); + let root_level_target_cnode = *router + .target_channeler() + .top_level_cnodes + .get_key(p) + .unwrap(); + let mut cnode_embeddings = OrdArena::::new(); + let mut cedge_embeddings = OrdArena::::new(); for (_, program_p_equiv, mapping) in &router.mappings { let program_p_equiv = *program_p_equiv; let target_p_equiv = mapping.target_p_equiv; @@ -192,8 +201,27 @@ fn route(router: &mut Router) -> Result<(), Error> { .find_channeler_backref(target_p_equiv) .unwrap(); - //let mut hyperpath = + let replaced = cnode_embeddings + .insert(program_base_cnode, target_base_cnode) + .1; + assert!(replaced.is_none()); } + for cnode in router.program_channeler().cnodes.vals() { + if cnode_embeddings.find_key(&cnode.p_this_cnode).is_none() { + // all CNodes that weren't embedded in guaranteed places are now embedded in the + // root level target node + cnode_embeddings.insert(cnode.p_this_cnode, root_level_target_cnode); + } + } + + // property: if a program CNode is embedded in a certain target CNode, the + // supernodes of the program CNode should be embedded somewhere in the + // supernode chain of the target CNode including itself. Embeddings should + // be in a ladder like ordering + + // in order to program a target CEdge, the incidents of a base level program + // CEdge must be compatible with their embedded incidents in the target. + // Then the edge is embedded. Ok(()) } From 2865c6aa98954a04e08063e0a1b0e2ccdb973121 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Mon, 22 Jan 2024 22:16:03 -0600 Subject: [PATCH 119/119] Version 0.3.0 --- starlight/Cargo.toml | 2 +- starlight/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/starlight/Cargo.toml b/starlight/Cargo.toml index 4b9443b2..57fc9388 100644 --- a/starlight/Cargo.toml +++ b/starlight/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starlight" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0" diff --git a/starlight/src/lib.rs b/starlight/src/lib.rs index 3aac6e47..e766d0b1 100644 --- a/starlight/src/lib.rs +++ b/starlight/src/lib.rs @@ -170,8 +170,10 @@ mod awi_structs; /// Data structure internals used by this crate pub mod ensemble; +/// Internal definitions used in lowering pub mod lower; mod misc; +/// WIP routing functionality pub mod route; pub use awi_structs::{ epoch, Assertions, Epoch, EvalAwi, LazyAwi, LazyInlAwi, Loop, Net, SuspendedEpoch,