diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs new file mode 100644 index 0000000000..bd82be62b6 --- /dev/null +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -0,0 +1,200 @@ +use crate::analysis::{ControlOrder, PromotionAnalysis}; +use calyx_ir::{self as ir}; +use ir::GetAttributes; +use itertools::Itertools; +use petgraph::{algo, graph::NodeIndex}; +use std::collections::HashMap; + +use super::read_write_set::AssignmentAnalysis; + +/// Struct to perform compaction on `seqs`. +/// It will only work if you update_cont_read_writes for each component that +/// you run it on. +#[derive(Debug, Default)] +pub struct CompactionAnalysis { + cont_reads: Vec>, + cont_writes: Vec>, +} + +impl CompactionAnalysis { + /// Updates self so that compaction will take continuous assignments into account + pub fn update_cont_read_writes(&mut self, comp: &mut ir::Component) { + let (cont_reads, cont_writes) = ( + comp.continuous_assignments + .iter() + .analysis() + .cell_reads() + .collect(), + comp.continuous_assignments + .iter() + .analysis() + .cell_writes() + .collect(), + ); + self.cont_reads = cont_reads; + self.cont_writes = cont_writes; + } + + // Given a total_order and sorted schedule, builds a vec of the original seq. + // Note that this function assumes the `total_order`` and `sorted_schedule` + // represent a completely sequential schedule. + fn recover_seq( + mut total_order: petgraph::graph::DiGraph, ()>, + sorted_schedule: Vec<(NodeIndex, u64)>, + ) -> Vec { + sorted_schedule + .into_iter() + .map(|(i, _)| total_order[i].take().unwrap()) + .collect_vec() + } + + /// Takes a vec of ctrl stmts and turns it into a compacted schedule. + /// If compaction doesn't lead to any latency decreases, it just returns + /// a vec of stmts in the original order. + /// If it can compact, then it returns a vec with one + /// element: a compacted static par. + pub fn compact_control_vec( + &mut self, + stmts: Vec, + promotion_analysis: &mut PromotionAnalysis, + builder: &mut ir::Builder, + ) -> Vec { + // Records the corresponding node indices that each control program + // has data dependency on. + let mut dependency: HashMap> = HashMap::new(); + // Records the latency of corresponding control operator for each + // node index. + let mut latency_map: HashMap = HashMap::new(); + // Records the scheduled start time of corresponding control operator + // for each node index. + let mut schedule: HashMap = HashMap::new(); + + let og_latency: u64 = stmts + .iter() + .map(PromotionAnalysis::get_inferred_latency) + .sum(); + + let mut total_order = ControlOrder::::get_dependency_graph_seq( + stmts.into_iter(), + (&self.cont_reads, &self.cont_writes), + &mut dependency, + &mut latency_map, + ); + + if let Ok(order) = algo::toposort(&total_order, None) { + let mut total_time: u64 = 0; + + // First we build the schedule. + for i in order { + // Start time is when the latest dependency finishes + let start = dependency + .get(&i) + .unwrap() + .iter() + .map(|node| schedule[node] + latency_map[node]) + .max() + .unwrap_or(0); + schedule.insert(i, start); + total_time = std::cmp::max(start + latency_map[&i], total_time); + } + + // We sort the schedule by start time. + let mut sorted_schedule: Vec<(NodeIndex, u64)> = + schedule.into_iter().collect(); + sorted_schedule + .sort_by(|(k1, v1), (k2, v2)| (v1, k1).cmp(&(v2, k2))); + + if total_time == og_latency { + // If we can't comapct at all, then just recover the and return + // the original seq. + return Self::recover_seq(total_order, sorted_schedule); + } + + // Threads for the static par, where each entry is (thread, thread_latency) + let mut par_threads: Vec<(Vec, u64)> = Vec::new(); + + // We encode the schedule while trying to minimize the number of + // par threads. + 'outer: for (i, start) in sorted_schedule { + let control = total_order[i].take().unwrap(); + for (thread, thread_latency) in par_threads.iter_mut() { + if *thread_latency <= start { + if *thread_latency < start { + // Need a no-op group so the schedule starts correctly + let no_op = builder.add_static_group( + "no-op", + start - *thread_latency, + ); + thread.push(ir::Control::Static( + ir::StaticControl::Enable(ir::StaticEnable { + group: no_op, + attributes: ir::Attributes::default(), + }), + )); + *thread_latency = start; + } + thread.push(control); + *thread_latency += latency_map[&i]; + continue 'outer; + } + } + // We must create a new par thread. + if start > 0 { + // If start > 0, then we must add a delay to the start of the + // group. + let no_op = builder.add_static_group("no-op", start); + let no_op_enable = ir::Control::Static( + ir::StaticControl::Enable(ir::StaticEnable { + group: no_op, + attributes: ir::Attributes::default(), + }), + ); + par_threads.push(( + vec![no_op_enable, control], + start + latency_map[&i], + )); + } else { + par_threads.push((vec![control], latency_map[&i])); + } + } + // Turn Vec -> StaticSeq + let mut par_control_threads: Vec = Vec::new(); + for (thread, thread_latency) in par_threads { + let mut promoted_stmts = thread + .into_iter() + .map(|mut stmt| { + promotion_analysis.convert_to_static(&mut stmt, builder) + }) + .collect_vec(); + if promoted_stmts.len() == 1 { + // Don't wrap in static seq if we don't need to. + par_control_threads.push(promoted_stmts.pop().unwrap()); + } else { + par_control_threads.push(ir::StaticControl::Seq( + ir::StaticSeq { + stmts: promoted_stmts, + attributes: ir::Attributes::default(), + latency: thread_latency, + }, + )); + } + } + // Double checking that we have built the static par correctly. + let max: Option = + par_control_threads.iter().map(|c| c.get_latency()).max(); + assert!(max.unwrap() == total_time, "The schedule expects latency {}. The static par that was built has latency {}", total_time, max.unwrap()); + + let mut s_par = ir::StaticControl::Par(ir::StaticPar { + stmts: par_control_threads, + attributes: ir::Attributes::default(), + latency: total_time, + }); + s_par.get_mut_attributes().insert(ir::BoolAttr::Promoted, 1); + vec![ir::Control::Static(s_par)] + } else { + panic!( + "Error when producing topo sort. Dependency graph has a cycle." + ); + } + } +} diff --git a/calyx-opt/src/analysis/inference_analysis.rs b/calyx-opt/src/analysis/inference_analysis.rs index 48ae55fa33..315113c08b 100644 --- a/calyx-opt/src/analysis/inference_analysis.rs +++ b/calyx-opt/src/analysis/inference_analysis.rs @@ -204,7 +204,11 @@ impl InferenceAnalysis { /// Note that this expects that the component already is accounted for /// in self.latency_data and self.static_component_latencies. pub fn remove_component(&mut self, comp_name: ir::Id) { - self.updated_components.insert(comp_name); + if self.latency_data.contains_key(&comp_name) { + // To make inference as strong as possible, only update updated_components + // if we actually updated it. + self.updated_components.insert(comp_name); + } self.latency_data.remove(&comp_name); self.static_component_latencies.remove(&comp_name); } @@ -216,15 +220,22 @@ impl InferenceAnalysis { &mut self, (comp_name, adjusted_latency): (ir::Id, u64), ) { - self.updated_components.insert(comp_name); + // Check whether we actually updated the component's latency. + let mut updated = false; self.latency_data.entry(comp_name).and_modify(|go_done| { for (_, _, cur_latency) in &mut go_done.ports { // Updating components with latency data. - *cur_latency = adjusted_latency; + if *cur_latency != adjusted_latency { + *cur_latency = adjusted_latency; + updated = true; + } } }); self.static_component_latencies .insert(comp_name, adjusted_latency); + if updated { + self.updated_components.insert(comp_name); + } } /// Return true if the edge (`src`, `dst`) meet one these criteria, and false otherwise: @@ -502,6 +513,26 @@ impl InferenceAnalysis { seq.update_static(&self.static_component_latencies); } + pub fn fixup_par(&self, par: &mut ir::Par) { + par.update_static(&self.static_component_latencies); + } + + pub fn fixup_if(&self, _if: &mut ir::If) { + _if.update_static(&self.static_component_latencies); + } + + pub fn fixup_while(&self, _while: &mut ir::While) { + _while.update_static(&self.static_component_latencies); + } + + pub fn fixup_repeat(&self, repeat: &mut ir::Repeat) { + repeat.update_static(&self.static_component_latencies); + } + + pub fn fixup_ctrl(&self, ctrl: &mut ir::Control) { + ctrl.update_static(&self.static_component_latencies); + } + /// "Fixes Up" the component. In particular: /// 1. Removes @promotable annotations for any groups that write to any /// `updated_components`. diff --git a/calyx-opt/src/analysis/mod.rs b/calyx-opt/src/analysis/mod.rs index cb00684f85..a187615f82 100644 --- a/calyx-opt/src/analysis/mod.rs +++ b/calyx-opt/src/analysis/mod.rs @@ -3,6 +3,7 @@ //! The analyses construct data-structures that make answering certain queries //! about Calyx programs easier. +mod compaction_analysis; mod compute_static; mod control_id; mod control_order; @@ -22,6 +23,7 @@ mod share_set; mod static_par_timing; mod variable_detection; +pub use compaction_analysis::CompactionAnalysis; pub use compute_static::IntoStatic; pub use compute_static::WithStatic; pub use control_id::ControlId; diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index 20989f6052..74782acb50 100644 --- a/calyx-opt/src/default_passes.rs +++ b/calyx-opt/src/default_passes.rs @@ -6,10 +6,10 @@ use crate::passes::{ DeadAssignmentRemoval, DeadCellRemoval, DeadGroupRemoval, DiscoverExternal, Externalize, GoInsertion, GroupToInvoke, GroupToSeq, HoleInliner, InferShare, LowerGuards, MergeAssign, Papercut, ParToSeq, - RegisterUnsharing, RemoveIds, ResetInsertion, ScheduleCompaction, - SimplifyStaticGuards, SimplifyWithControl, StaticInference, StaticInliner, - StaticPromotion, SynthesisPapercut, TopDownCompileControl, UnrollBounded, - WellFormed, WireInliner, WrapMain, + RegisterUnsharing, RemoveIds, ResetInsertion, SimplifyStaticGuards, + SimplifyWithControl, StaticInference, StaticInliner, StaticPromotion, + SynthesisPapercut, TopDownCompileControl, UnrollBounded, WellFormed, + WireInliner, WrapMain, }; use crate::traversal::Named; use crate::{pass_manager::PassManager, register_alias}; @@ -35,7 +35,6 @@ impl PassManager { pm.register_pass::()?; pm.register_pass::()?; pm.register_pass::()?; - pm.register_pass::()?; pm.register_pass::()?; pm.register_pass::()?; pm.register_pass::()?; @@ -94,8 +93,6 @@ impl PassManager { SimplifyWithControl, // Must run before compile-invoke CompileInvoke, // creates dead comb groups StaticInference, - ScheduleCompaction, - StaticPromotion, StaticPromotion, CompileRepeat, DeadGroupRemoval, // Since previous passes potentially create dead groups diff --git a/calyx-opt/src/passes/mod.rs b/calyx-opt/src/passes/mod.rs index b9aa04c153..87e9c81718 100644 --- a/calyx-opt/src/passes/mod.rs +++ b/calyx-opt/src/passes/mod.rs @@ -26,7 +26,6 @@ mod par_to_seq; mod register_unsharing; mod remove_ids; mod reset_insertion; -mod schedule_compaction; mod simplify_static_guards; mod static_inference; mod static_inliner; @@ -72,7 +71,6 @@ pub use par_to_seq::ParToSeq; pub use register_unsharing::RegisterUnsharing; pub use remove_ids::RemoveIds; pub use reset_insertion::ResetInsertion; -pub use schedule_compaction::ScheduleCompaction; pub use simplify_static_guards::SimplifyStaticGuards; pub use simplify_with_control::SimplifyWithControl; pub use static_inference::StaticInference; diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 475d0f9f9d..b13c116955 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -1,4 +1,6 @@ -use crate::analysis::{InferenceAnalysis, PromotionAnalysis}; +use crate::analysis::{ + CompactionAnalysis, InferenceAnalysis, PromotionAnalysis, +}; use crate::traversal::{ Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult, Visitor, @@ -33,12 +35,16 @@ pub struct StaticPromotion { /// PromotionAnalysis object so that we can easily infer control, and keep /// track of which groups were promoted. promotion_analysis: PromotionAnalysis, + /// CompactionAnalysis object so that we can easily perform compaction + compaction_analysis: CompactionAnalysis, /// Threshold for promotion threshold: u64, /// Threshold for difference in latency for if statements if_diff_limit: Option, /// Whether we should stop promoting when we see a loop. cycle_limit: Option, + /// Whether to perform compaction. True by default + compaction: bool, } // Override constructor to build latency_data information from the primitives @@ -49,15 +55,18 @@ impl ConstructVisitor for StaticPromotion { Ok(StaticPromotion { inference_analysis: InferenceAnalysis::from_ctx(ctx), promotion_analysis: PromotionAnalysis::default(), + compaction_analysis: CompactionAnalysis::default(), threshold: opts["threshold"].pos_num().unwrap(), if_diff_limit: opts["if-diff-limit"].pos_num(), cycle_limit: opts["cycle-limit"].pos_num(), + compaction: opts["compaction"].bool(), }) } // This pass shared information between components fn clear_data(&mut self) { self.promotion_analysis = PromotionAnalysis::default(); + self.compaction_analysis = CompactionAnalysis::default(); } } @@ -89,12 +98,29 @@ impl Named for StaticPromotion { "the maximum difference between if branches that we tolerate for promotion", ParseVal::Num(1), PassOpt::parse_num, + ), + PassOpt::new( + "compaction", + "Whether to perform compaction. True by Default ", + ParseVal::Bool(true), + PassOpt::parse_bool, ) ] } } impl StaticPromotion { + // Remove @promotable(n) attribute if n is above the cycle limit, since + // we know we will never promote such a control. + // This can be helpful to the pass when applying the heuristics. + fn remove_large_promotables(&self, c: &mut ir::Control) { + if let Some(pr) = c.get_attribute(ir::NumAttr::Promotable) { + if !self.within_cycle_limit(pr) { + c.get_mut_attributes().remove(ir::NumAttr::Promotable) + } + } + } + fn within_cycle_limit(&self, latency: u64) -> bool { if self.cycle_limit.is_none() { return true; @@ -109,6 +135,12 @@ impl StaticPromotion { diff <= self.if_diff_limit.unwrap() } + fn fits_heuristics(&self, c: &ir::Control) -> bool { + let approx_size = Self::approx_size(c); + let latency = PromotionAnalysis::get_inferred_latency(c); + self.within_cycle_limit(latency) && approx_size > self.threshold + } + fn approx_size_static(sc: &ir::StaticControl, promoted: bool) -> u64 { if !(sc.get_attributes().has(ir::BoolAttr::Promoted) || promoted) { return APPROX_ENABLE_SIZE; @@ -169,47 +201,78 @@ impl StaticPromotion { v.iter().map(Self::approx_size).sum() } - /// Converts the control_vec (i..e, the stmts of the seq) using heuristics. - fn promote_vec_seq_heuristic( + fn promote_seq_heuristic( &mut self, builder: &mut ir::Builder, mut control_vec: Vec, ) -> Vec { if control_vec.is_empty() { - // Base case - return vec![]; + // Base case len == 0 + vec![] } else if control_vec.len() == 1 { - return vec![control_vec.pop().unwrap()]; - } else if Self::approx_control_vec_size(&control_vec) <= self.threshold - { - // Too small to be promoted, return as is - return control_vec; - } else if !self.within_cycle_limit( - control_vec - .iter() - .map(PromotionAnalysis::get_inferred_latency) - .sum(), - ) { - // Too large, try to break up - let right = control_vec.split_off(control_vec.len() / 2); - dbg!(control_vec.len()); - dbg!(right.len()); - let mut left_res = - self.promote_vec_seq_heuristic(builder, control_vec); - let right_res = self.promote_vec_seq_heuristic(builder, right); - left_res.extend(right_res); - return left_res; + // Base case len == 1. + // Promote if it fits the promotion heuristics. + let mut stmt = control_vec.pop().unwrap(); + if self.fits_heuristics(&stmt) { + vec![ir::Control::Static( + self.promotion_analysis + .convert_to_static(&mut stmt, builder), + )] + } else { + vec![stmt] + } + } else { + let mut possibly_compacted_ctrl = if self.compaction { + // If compaction is turned on, then we possibly compact + self.compaction_analysis.compact_control_vec( + control_vec, + &mut self.promotion_analysis, + builder, + ) + } else { + // Otherwise it's just the og control vec + control_vec + }; + // If length == 1 this means we have a vec[compacted_static_par], + // so we can return. + // (Note that the og control_vec must be of length >=2, since we + // have already checked for two base cases.) + if possibly_compacted_ctrl.len() == 1 { + return possibly_compacted_ctrl; + } + // Otherwise we cannot compact at all, + // so go through normal promotion heuristic analysis. + if Self::approx_control_vec_size(&possibly_compacted_ctrl) + <= self.threshold + { + // Too small to be promoted, return as is + return possibly_compacted_ctrl; + } else if !self.within_cycle_limit( + possibly_compacted_ctrl + .iter() + .map(PromotionAnalysis::get_inferred_latency) + .sum(), + ) { + // Too large, try to break up + let right = possibly_compacted_ctrl + .split_off(possibly_compacted_ctrl.len() / 2); + let mut left_res = self + .promote_seq_heuristic(builder, possibly_compacted_ctrl); + let right_res = self.promote_seq_heuristic(builder, right); + left_res.extend(right_res); + return left_res; + } + // Correct size, convert the entire vec + let s_seq_stmts = self + .promotion_analysis + .convert_vec_to_static(builder, possibly_compacted_ctrl); + let latency = s_seq_stmts.iter().map(|sc| sc.get_latency()).sum(); + let sseq = ir::Control::Static(ir::StaticControl::seq( + s_seq_stmts, + latency, + )); + vec![sseq] } - // Correct size, convert the entire vec - let s_seq_stmts = self - .promotion_analysis - .convert_vec_to_static(builder, control_vec); - let latency = s_seq_stmts.iter().map(|sc| sc.get_latency()).sum(); - let sseq = - ir::Control::Static(ir::StaticControl::seq(s_seq_stmts, latency)); - // sseq.get_mut_attributes() - // .insert(ir::NumAttr::Compactable, 1); - vec![sseq] } /// First checks if the vec of control statements meets the self.threshold @@ -281,34 +344,34 @@ impl Visitor for StaticPromotion { ) -> VisResult { if comp.name != "main" { let comp_sig = comp.signature.borrow(); - let go_ports = - comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec(); - if go_ports.iter().any(|go_port| { - let go_ref = go_port.borrow_mut(); - go_ref.attributes.has(ir::NumAttr::Promotable) - || go_ref.attributes.has(ir::NumAttr::Interval) - }) { - if comp.control.borrow().is_static() { - // We ended up promoting it - if !comp.is_static() { - // Need this attribute for a weird, in-between state. - // It has a known latency but also produces a done signal. - comp.attributes.insert(ir::BoolAttr::Promoted, 1); - } - // This makes the component appear as a static component. - comp.latency = Some( - NonZeroU64::new( - comp.control.borrow().get_latency().unwrap(), - ) - .unwrap(), - ); - } else { - // We decided not to promote, so we need to update data structures. - // This case should only happen on @promotable components - // (i.e., it shouldn't happen with @interval components). - self.inference_analysis.remove_component(comp.name); + if comp.control.borrow().is_static() { + // We ended up promoting it + if !comp.is_static() { + // Need this attribute for a weird, in-between state. + // It has a known latency but also produces a done signal. + comp.attributes.insert(ir::BoolAttr::Promoted, 1); } + // (Possibly) new latency because of compaction + let new_latency = NonZeroU64::new( + comp.control.borrow().get_latency().unwrap(), + ) + .unwrap(); + // This makes the component appear as a static component. + comp.latency = Some(new_latency); + // Adjust inference analysis to account for this new latency. + self.inference_analysis + .adjust_component((comp.name, new_latency.into())); + } else if !comp.control.borrow().is_empty() { + // This is for the case where we didn't end up promoting, so + // we remove it from our inference_analysis. + // Note that sometimes you can have components with only continuous + // assignments with @interval annotations: in that case, + // we don't want to remove our inference analysis. + self.inference_analysis.remove_component(comp.name); }; + + let go_ports = + comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec(); // Either we have upgraded component to static or we have decided // not to promote component at all. Either way, we can remove the // @promotable attribute. @@ -319,7 +382,7 @@ impl Visitor for StaticPromotion { .remove(ir::NumAttr::Promotable); } } - // Remove @promotable (i.e., @promote_static) attribute from control. + // Remove @promotable attribute from control. // Probably not necessary, since we'll ignore it anyways, but makes for // cleaner code. InferenceAnalysis::remove_promotable_attribute( @@ -337,6 +400,8 @@ impl Visitor for StaticPromotion { // Re-infer static timing based on the components we have updated in // this pass. self.inference_analysis.fixup_timing(comp); + // Update the continuous reads and writes + self.compaction_analysis.update_cont_read_writes(comp); Ok(Action::Continue) } @@ -390,34 +455,14 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_seq(s); + // Remove @promotable attributes that are too large to be promoted. + // This helps the promotion heuristic make smarter decisions + s.stmts + .iter_mut() + .for_each(|c| self.remove_large_promotables(c)); + let mut builder = ir::Builder::new(comp, sigs); - // Checking if entire seq is promotable - if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { - // If seq is too small to promote, then continue without doing anything. - if Self::approx_control_vec_size(&s.stmts) <= self.threshold { - return Ok(Action::Continue); - } else if self.within_cycle_limit(latency) { - // We promote entire seq. - let sseq = ir::Control::Static(ir::StaticControl::seq( - self.promotion_analysis.convert_vec_to_static( - &mut builder, - std::mem::take(&mut s.stmts), - ), - latency, - )); - // sseq.get_mut_attributes() - // .insert(ir::NumAttr::Compactable, 1); - return Ok(Action::change(sseq)); - } - } - // The seq either a) takes too many cylces to promote entirely or - // b) has dynamic stmts in it. Either way, the solution is to - // break it up into smaller static seqs. - // We know that this seq will *never* be promoted. Therefore, we can - // safely replace it with a standard `seq` that does not have an `@promotable` - // attribute. This temporarily messes up its parents' `@promotable` - // attribute, but this is fine since we know its parent will never try - // to promote it. let old_stmts = std::mem::take(&mut s.stmts); let mut new_stmts: Vec = Vec::new(); let mut cur_vec: Vec = Vec::new(); @@ -427,7 +472,7 @@ impl Visitor for StaticPromotion { } else { // Use heuristics to decide how to promote this cur_vec of promotable stmts. let possibly_promoted_stmts = - self.promote_vec_seq_heuristic(&mut builder, cur_vec); + self.promote_seq_heuristic(&mut builder, cur_vec); new_stmts.extend(possibly_promoted_stmts); // Add the current (non-promotable) stmt new_stmts.push(stmt); @@ -435,12 +480,17 @@ impl Visitor for StaticPromotion { cur_vec = Vec::new(); } } - new_stmts.extend(self.promote_vec_seq_heuristic(&mut builder, cur_vec)); - let new_seq = ir::Control::Seq(ir::Seq { - stmts: new_stmts, - attributes: ir::Attributes::default(), - }); - Ok(Action::change(new_seq)) + new_stmts.extend(self.promote_seq_heuristic(&mut builder, cur_vec)); + let mut new_ctrl = if new_stmts.len() == 1 { + new_stmts.pop().unwrap() + } else { + ir::Control::Seq(ir::Seq { + stmts: new_stmts, + attributes: ir::Attributes::default(), + }) + }; + self.inference_analysis.fixup_ctrl(&mut new_ctrl); + Ok(Action::change(new_ctrl)) } fn finish_par( @@ -450,6 +500,8 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_par(s); + let mut builder = ir::Builder::new(comp, sigs); // Check if entire par is promotable if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { @@ -499,6 +551,7 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_if(s); let mut builder = ir::Builder::new(comp, sigs); if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { let approx_size_if = Self::approx_size(&s.tbranch) @@ -548,6 +601,8 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_while(s); + let mut builder = ir::Builder::new(comp, sigs); // First check that while loop is promotable if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { @@ -593,6 +648,8 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_repeat(s); + let mut builder = ir::Builder::new(comp, sigs); if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { let approx_size = diff --git a/tests/passes/schedule-compaction/continuous-compaction.expect b/tests/passes/schedule-compaction/continuous-compaction.expect index ab9b64b5b8..ea98c5efc7 100644 --- a/tests/passes/schedule-compaction/continuous-compaction.expect +++ b/tests/passes/schedule-compaction/continuous-compaction.expect @@ -30,7 +30,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: add.left = r0.out; } control { - @promotable(2) @promoted static<2> par { + @promoted static<2> par { static<2> seq { write_r00; write_r10; diff --git a/tests/passes/schedule-compaction/continuous-compaction.futil b/tests/passes/schedule-compaction/continuous-compaction.futil index f39bd31e81..9b86d4c119 100644 --- a/tests/passes/schedule-compaction/continuous-compaction.futil +++ b/tests/passes/schedule-compaction/continuous-compaction.futil @@ -1,4 +1,4 @@ -// -p validate -p schedule-compaction -p dead-group-removal +// -p validate -p static-promotion -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/schedule-compaction/continuous-no-compaction.expect b/tests/passes/schedule-compaction/continuous-no-compaction.expect index ad7b41f28a..06a5cb5b60 100644 --- a/tests/passes/schedule-compaction/continuous-no-compaction.expect +++ b/tests/passes/schedule-compaction/continuous-no-compaction.expect @@ -10,20 +10,17 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: ud = undef(1); } wires { - group write_r0<"promotable"=1> { + static<1> group write_r00 { r0.in = 8'd1; r0.write_en = 1'd1; - write_r0[done] = r0.done; } - group write_r1<"promotable"=1> { + static<1> group write_r10 { r1.in = add.out; r1.write_en = 1'd1; - write_r1[done] = r1.done; } - group write_add1<"promotable"=1> { + static<1> group write_add10 { add1.right = 8'd4; add1.left = 8'd1; - write_add1[done] = ud.out; } out = r1.out; add.right = 8'd1; @@ -31,14 +28,14 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: r2.in = add1.out; } control { - @promotable(4) seq { - @promotable(2) seq { - @promotable write_r0; - @promotable write_r1; + static<4> seq { + @promotable(2) static<2> seq { + write_r00; + write_r10; } - @promotable(2) seq { - @promotable write_r0; - @promotable write_add1; + @promotable(2) static<2> seq { + write_r00; + write_add10; } } } diff --git a/tests/passes/schedule-compaction/continuous-no-compaction.futil b/tests/passes/schedule-compaction/continuous-no-compaction.futil index 77c435e993..5a3f3a6b40 100644 --- a/tests/passes/schedule-compaction/continuous-no-compaction.futil +++ b/tests/passes/schedule-compaction/continuous-no-compaction.futil @@ -1,4 +1,4 @@ -// -p validate -p schedule-compaction -p dead-group-removal +// -p validate -p static-promotion -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/schedule-compaction/fixup-necessary.expect b/tests/passes/schedule-compaction/fixup-necessary.expect index c3f9fa30fd..9c248a04c0 100644 --- a/tests/passes/schedule-compaction/fixup-necessary.expect +++ b/tests/passes/schedule-compaction/fixup-necessary.expect @@ -1,6 +1,6 @@ import "primitives/core.futil"; import "primitives/memories/comb.futil"; -component example(@go @promotable go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: 1) { +static<1> component example<"promoted"=1>(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: 1) { cells { r0 = std_reg(8); r1 = std_reg(8); @@ -9,7 +9,7 @@ component example(@go @promotable go: 1, @clk clk: 1, @reset reset: 1) -> (out: out = r1.out; } control { - @promotable @promoted static<1> par { + @promoted static<1> par { static<1> invoke r0( in = 8'd1 )(); @@ -26,9 +26,9 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } wires {} control { - @promotable(2) seq { - @promotable invoke ex()(); - @promotable invoke mem( + seq { + invoke ex()(); + invoke mem( addr0 = 1'd0, write_data = ex.out )(); diff --git a/tests/passes/schedule-compaction/fixup-necessary.futil b/tests/passes/schedule-compaction/fixup-necessary.futil index e971a9aad6..c2694582c7 100644 --- a/tests/passes/schedule-compaction/fixup-necessary.futil +++ b/tests/passes/schedule-compaction/fixup-necessary.futil @@ -1,4 +1,4 @@ -// -p validate -p static-inference -p schedule-compaction -p dead-group-removal +// -p validate -p static-inference -p static-promotion -x static-promotion:threshold=2 -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/schedule-compaction/no-compact.expect b/tests/passes/schedule-compaction/no-compact.expect index 7b5282c947..90b3284d10 100644 --- a/tests/passes/schedule-compaction/no-compact.expect +++ b/tests/passes/schedule-compaction/no-compact.expect @@ -24,10 +24,10 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } } control { - @promotable(3) seq { - @promotable A; - @promotable B; - @promotable D; + seq { + A; + B; + D; } } } diff --git a/tests/passes/schedule-compaction/no-compact.futil b/tests/passes/schedule-compaction/no-compact.futil index cc70a6b3e2..40c51a72df 100644 --- a/tests/passes/schedule-compaction/no-compact.futil +++ b/tests/passes/schedule-compaction/no-compact.futil @@ -1,4 +1,4 @@ -// -p validate -p static-inference -p schedule-compaction -p dead-group-removal +// -p validate -p static-inference -p static-promotion -x static-promotion:threshold=5 -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/schedule-compaction/partial-compaction.expect b/tests/passes/schedule-compaction/partial-compaction.expect index 8ed953a6bd..13d34bcbef 100644 --- a/tests/passes/schedule-compaction/partial-compaction.expect +++ b/tests/passes/schedule-compaction/partial-compaction.expect @@ -44,12 +44,10 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } control { seq { - @promotable(2) seq { - @promotable A; - @promotable B; - } + A; + B; C; - @promotable @promoted static<1> par { + @promoted static<1> par { D0; E0; F0; diff --git a/tests/passes/schedule-compaction/partial-compaction.futil b/tests/passes/schedule-compaction/partial-compaction.futil index eda318e31e..dd684c7573 100644 --- a/tests/passes/schedule-compaction/partial-compaction.futil +++ b/tests/passes/schedule-compaction/partial-compaction.futil @@ -1,21 +1,4 @@ -// -p validate -p schedule-compaction -p dead-group-removal -// for control operators under static seq, -// we consider the subsequent control operator B to have data dependency on -// prior operator A in the following three cases: -// 1. B writes to a cell A reads from -// 2. B reads from a cell A writes to -// 3. B writes to a cell A writes to -// As such, we can draw the following dependency graph for the control program: -// A C -// | \ / -// | \ / -// | \ / -// | \ -// | / \ -// | / \ -// | / \ -// B D -// So we can compact the execution schedule to respect this data dependency +// -p validate -p static-promotion -x static-promotion:threshold=5 -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/schedule-compaction/schedule-compaction.expect b/tests/passes/schedule-compaction/schedule-compaction.expect index 5503b55d1c..6099271d03 100644 --- a/tests/passes/schedule-compaction/schedule-compaction.expect +++ b/tests/passes/schedule-compaction/schedule-compaction.expect @@ -30,7 +30,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } } control { - @promotable(11) @promoted static<11> par { + @promoted static<11> par { static<11> seq { A0; D0; diff --git a/tests/passes/schedule-compaction/schedule-compaction.futil b/tests/passes/schedule-compaction/schedule-compaction.futil index 44e709619f..48813ed66a 100644 --- a/tests/passes/schedule-compaction/schedule-compaction.futil +++ b/tests/passes/schedule-compaction/schedule-compaction.futil @@ -1,4 +1,4 @@ -// -p validate -p schedule-compaction -p dead-group-removal +// -p validate -p static-promotion -p dead-group-removal // for control operators under static seq, // we consider the subsequent control operator B to have data dependency on // prior operator A in the following three cases: diff --git a/tests/passes/static-inference-promotion/component.futil b/tests/passes/static-inference-promotion/component.futil index 27efe57a02..1ce2cb35b5 100644 --- a/tests/passes/static-inference-promotion/component.futil +++ b/tests/passes/static-inference-promotion/component.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -x static-promotion:compaction=false -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/groups.futil b/tests/passes/static-inference-promotion/groups.futil index 679ab9499e..9084f4b155 100644 --- a/tests/passes/static-inference-promotion/groups.futil +++ b/tests/passes/static-inference-promotion/groups.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -x static-promotion:compaction=false -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/if-diff.futil b/tests/passes/static-inference-promotion/if-diff.futil index 13bfa41424..b34916d5b2 100644 --- a/tests/passes/static-inference-promotion/if-diff.futil +++ b/tests/passes/static-inference-promotion/if-diff.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -x static-promotion:if-diff-limit=3 +// -p well-formed -p static-inference -p static-promotion -x static-promotion:compaction=false -x static-promotion:if-diff-limit=3 import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/if-no-else.futil b/tests/passes/static-inference-promotion/if-no-else.futil index a658eac116..692e3613b1 100644 --- a/tests/passes/static-inference-promotion/if-no-else.futil +++ b/tests/passes/static-inference-promotion/if-no-else.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -x static-promotion:compaction=false -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/invoke.futil b/tests/passes/static-inference-promotion/invoke.futil index ecc2a9d414..61d23ce510 100644 --- a/tests/passes/static-inference-promotion/invoke.futil +++ b/tests/passes/static-inference-promotion/invoke.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/multi-static.futil b/tests/passes/static-inference-promotion/multi-static.futil index 2361e05f99..fdb925babc 100644 --- a/tests/passes/static-inference-promotion/multi-static.futil +++ b/tests/passes/static-inference-promotion/multi-static.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/no_promote_loop.futil b/tests/passes/static-inference-promotion/no_promote_loop.futil index cb90f4fe88..fe918d2cdf 100644 --- a/tests/passes/static-inference-promotion/no_promote_loop.futil +++ b/tests/passes/static-inference-promotion/no_promote_loop.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:cycle-limit=25 +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:cycle-limit=25 -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/par.futil b/tests/passes/static-inference-promotion/par.futil index 5604bf10b8..be732419e0 100644 --- a/tests/passes/static-inference-promotion/par.futil +++ b/tests/passes/static-inference-promotion/par.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/promote-nested.futil b/tests/passes/static-inference-promotion/promote-nested.futil index de8435d995..7983de6126 100644 --- a/tests/passes/static-inference-promotion/promote-nested.futil +++ b/tests/passes/static-inference-promotion/promote-nested.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:threshold=5 +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:threshold=5 -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/threshold.futil b/tests/passes/static-inference-promotion/threshold.futil index 3e6e8d0462..c30bd7f1b5 100644 --- a/tests/passes/static-inference-promotion/threshold.futil +++ b/tests/passes/static-inference-promotion/threshold.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:threshold=4 +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:threshold=4 -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-inference-promotion/upgrade-bound.expect b/tests/passes/static-inference-promotion/upgrade-bound.expect index 0bf3df2e87..d8f44c6040 100644 --- a/tests/passes/static-inference-promotion/upgrade-bound.expect +++ b/tests/passes/static-inference-promotion/upgrade-bound.expect @@ -23,7 +23,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } control { static repeat 5 { - static<3> seq { + @promotable(3) static<3> seq { A0; B0; C0; diff --git a/tests/passes/static-inference-promotion/upgrade-bound.futil b/tests/passes/static-inference-promotion/upgrade-bound.futil index 63247fbf11..b87884945d 100644 --- a/tests/passes/static-inference-promotion/upgrade-bound.futil +++ b/tests/passes/static-inference-promotion/upgrade-bound.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p static-promotion -p dead-group-removal +// -p well-formed -p static-inference -p static-promotion -p dead-group-removal -x static-promotion:compaction=false import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/static-passes/both-passes-promote.futil b/tests/passes/static-passes/both-passes-promote.futil index fa0bd0db56..52551400b0 100644 --- a/tests/passes/static-passes/both-passes-promote.futil +++ b/tests/passes/static-passes/both-passes-promote.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-inference -p schedule-compaction -p static-promotion -x static-promotion:threshold=5 +// -p well-formed -p static-inference -p static-promotion -x static-promotion:threshold=5 // Compaction should promote the body, promotion should promote the loop. import "primitives/core.futil"; diff --git a/tests/passes/static-passes/no-compact.expect b/tests/passes/static-passes/no-compact.expect new file mode 100644 index 0000000000..1e9418ec53 --- /dev/null +++ b/tests/passes/static-passes/no-compact.expect @@ -0,0 +1,63 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +component foo(base: 32, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 32, @done done: 1) { + cells { + r1 = std_reg(32); + r2 = std_reg(32); + r3 = std_reg(32); + } + wires { + group upd3<"promotable"=1> { + r3.in = base; + r3.write_en = 1'd1; + upd3[done] = r3.done; + } + group upd2<"promotable"=1> { + r2.in = r3.out; + r2.write_en = 1'd1; + upd2[done] = r2.done; + } + group upd1<"promotable"=1> { + r1.in = r2.out; + r1.write_en = 1'd1; + upd1[done] = r1.done; + } + } + control { + seq { + upd3; + upd2; + upd1; + } + } +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(2); + b = std_reg(2); + foo_inst = foo(); + } + wires { + group A<"promotable"=1> { + a.in = 2'd0; + a.write_en = 1'd1; + A[done] = a.done; + } + group B<"promotable"=1> { + b.in = 2'd1; + b.write_en = 1'd1; + B[done] = b.done; + } + group F { + foo_inst.go = 1'd1; + F[done] = foo_inst.done; + } + } + control { + seq { + A; + F; + B; + } + } +} diff --git a/tests/passes/static-passes/no-compact.futil b/tests/passes/static-passes/no-compact.futil new file mode 100644 index 0000000000..553bfb6f3e --- /dev/null +++ b/tests/passes/static-passes/no-compact.futil @@ -0,0 +1,66 @@ +// -p well-formed -p static-inference -p static-promotion -x static-promotion:threshold=5 -p dead-group-removal + +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +component foo(base: 32) -> (out: 32) { + cells { + r1 = std_reg(32); + r2 = std_reg(32); + r3 = std_reg(32); + } + wires { + group upd3 { + r3.in = base; + r3.write_en = 1'd1; + upd3[done] = r3.done; + } + group upd2 { + r2.in = r3.out; + r2.write_en = 1'd1; + upd2[done] = r2.done; + } + group upd1 { + r1.in = r2.out; + r1.write_en = 1'd1; + upd1[done] = r1.done; + } + } + control { + seq { + upd3; + upd2; + upd1; + } + } +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(2); + b = std_reg(2); + foo_inst = foo(); + } + wires { + group A<"promotable"=1> { + a.in = 2'd0; + a.write_en = 1'd1; + A[done] = a.done; + } + group B<"promotable"=1> { + b.in = 2'd1; + b.write_en = 1'd1; + B[done] = b.done; + } + group F<"promotable"=2> { + foo_inst.go = 1'd1; + F[done] = foo_inst.done; + } + } + control { + seq { + A; + F; + B; + } + } +} \ No newline at end of file diff --git a/tests/passes/static-promotion/fixup-invoke.futil b/tests/passes/static-promotion/fixup-invoke.futil index a4c72d6bf5..fd20538b4e 100644 --- a/tests/passes/static-promotion/fixup-invoke.futil +++ b/tests/passes/static-promotion/fixup-invoke.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-promotion -x static-promotion:threshold=5 -p dead-group-removal +// -p well-formed -p static-promotion -x static-promotion:threshold=5 -x static-promotion:compaction=false -p dead-group-removal import "primitives/core.futil"; diff --git a/tests/passes/static-promotion/fixup-necessary.futil b/tests/passes/static-promotion/fixup-necessary.futil index 25a304b82a..0b2da44a3e 100644 --- a/tests/passes/static-promotion/fixup-necessary.futil +++ b/tests/passes/static-promotion/fixup-necessary.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-promotion -x static-promotion:threshold=5 -p dead-group-removal +// -p well-formed -p static-promotion -x static-promotion:threshold=5 -x static-promotion:compaction=false -p dead-group-removal import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/tdcc/new-fsm-nested.futil b/tests/passes/tdcc/new-fsm-nested.futil index 785fa2606f..ca45024831 100644 --- a/tests/passes/tdcc/new-fsm-nested.futil +++ b/tests/passes/tdcc/new-fsm-nested.futil @@ -1,4 +1,4 @@ -// -x tdcc:dump-fsm -d schedule-compaction -d static-promotion -d post-opt -d group2invoke -d lower -b none +// -x tdcc:dump-fsm -d static-promotion -d post-opt -d group2invoke -d lower -b none import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/tdcc/while.futil b/tests/passes/tdcc/while.futil index fc1460df24..35a7165873 100644 --- a/tests/passes/tdcc/while.futil +++ b/tests/passes/tdcc/while.futil @@ -1,4 +1,4 @@ -// -x tdcc:dump-fsm -d schedule-compaction -d static-promotion -d post-opt -d group2invoke -d lower -b none +// -x tdcc:dump-fsm -d static-promotion -d post-opt -d group2invoke -d lower -b none import "primitives/core.futil"; import "primitives/memories/comb.futil";