From 449985bfeec571c48cf38900ada1bb5ec2f85b73 Mon Sep 17 00:00:00 2001 From: Caleb Date: Sat, 10 Feb 2024 16:19:33 -0500 Subject: [PATCH 01/13] throat clearing' --- calyx-opt/src/analysis/compaction_analysis.rs | 210 ++++++++++++++++++ calyx-opt/src/analysis/mod.rs | 2 + calyx-opt/src/passes/static_promotion.rs | 14 +- 3 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 calyx-opt/src/analysis/compaction_analysis.rs diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs new file mode 100644 index 000000000..5507ec8c4 --- /dev/null +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -0,0 +1,210 @@ +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; + +#[derive(Debug, Default)] +pub struct CompactionAnalysis; + +impl CompactionAnalysis { + // Compacts `cur_stmts`, and appends the result to `new_stmts`. + pub fn append_and_compact( + &mut self, + (cont_reads, cont_writes): ( + &Vec>, + &Vec>, + ), + promotion_analysis: &mut PromotionAnalysis, + builder: &mut ir::Builder, + cur_stmts: Vec, + new_stmts: &mut Vec, + ) { + if !cur_stmts.is_empty() { + let og_latency = cur_stmts + .iter() + .map(PromotionAnalysis::get_inferred_latency) + .sum(); + // Try to compact cur_stmts. + let possibly_compacted_stmt = self.compact_control_vec( + cur_stmts, + (cont_reads, cont_writes), + promotion_analysis, + builder, + og_latency, + ir::Attributes::default(), + ); + new_stmts.push(possibly_compacted_stmt); + } + } + + // Given a total_order and sorted schedule, builds a seq based on the original + // schedule. + // 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)>, + attributes: ir::Attributes, + ) -> ir::Control { + let stmts = sorted_schedule + .into_iter() + .map(|(i, _)| total_order[i].take().unwrap()) + .collect_vec(); + ir::Control::Seq(ir::Seq { stmts, attributes }) + } + + // Takes a vec of ctrl stmts and turns it into a compacted schedule (a static par). + // If it can't compact at all, then it just returns a `seq` in the `stmts` + // original order. + pub fn compact_control_vec( + &mut self, + stmts: Vec, + (cont_reads, cont_writes): ( + &Vec>, + &Vec>, + ), + promotion_analysis: &mut PromotionAnalysis, + builder: &mut ir::Builder, + og_latency: u64, + attributes: ir::Attributes, + ) -> ir::Control { + // 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 mut total_order = ControlOrder::::get_dependency_graph_seq( + stmts.into_iter(), + (cont_reads, 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, + attributes, + ); + } + + // 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); + ir::Control::Static(s_par) + } else { + panic!( + "Error when producing topo sort. Dependency graph has a cycle." + ); + } + } +} diff --git a/calyx-opt/src/analysis/mod.rs b/calyx-opt/src/analysis/mod.rs index 6be29f0d2..5d3b3dfbd 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/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 475d0f9f9..a241c7fba 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, + /// Set no_compaction = true to disable compaction + no_compaction: bool, } // Override constructor to build latency_data information from the primitives @@ -49,9 +55,11 @@ 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(), + no_compaction: opts["no-compaction"].pos_num().is_some(), }) } @@ -192,8 +200,6 @@ impl StaticPromotion { ) { // 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); @@ -207,8 +213,6 @@ impl StaticPromotion { 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] } From 7e69c1c4f50bc63c5662404929ec67aec3b0efe4 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 08:18:52 -0500 Subject: [PATCH 02/13] progress --- calyx-opt/src/analysis/compaction_analysis.rs | 90 +++++++++---------- calyx-opt/src/passes/static_promotion.rs | 60 +++++++++++++ 2 files changed, 103 insertions(+), 47 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index 5507ec8c4..9f3f75503 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -6,38 +6,39 @@ use petgraph::{algo, graph::NodeIndex}; use std::collections::HashMap; #[derive(Debug, Default)] -pub struct CompactionAnalysis; +pub struct CompactionAnalysis { + cont_reads: Vec>, + cont_writes: Vec>, +} impl CompactionAnalysis { // Compacts `cur_stmts`, and appends the result to `new_stmts`. - pub fn append_and_compact( - &mut self, - (cont_reads, cont_writes): ( - &Vec>, - &Vec>, - ), - promotion_analysis: &mut PromotionAnalysis, - builder: &mut ir::Builder, - cur_stmts: Vec, - new_stmts: &mut Vec, - ) { - if !cur_stmts.is_empty() { - let og_latency = cur_stmts - .iter() - .map(PromotionAnalysis::get_inferred_latency) - .sum(); - // Try to compact cur_stmts. - let possibly_compacted_stmt = self.compact_control_vec( - cur_stmts, - (cont_reads, cont_writes), - promotion_analysis, - builder, - og_latency, - ir::Attributes::default(), - ); - new_stmts.push(possibly_compacted_stmt); - } - } + // pub fn append_and_compact( + // &mut self, + // (cont_reads, cont_writes): ( + // &Vec>, + // &Vec>, + // ), + // promotion_analysis: &mut PromotionAnalysis, + // builder: &mut ir::Builder, + // cur_stmts: Vec, + // new_stmts: &mut Vec, + // ) { + // if !cur_stmts.is_empty() { + // let og_latency = cur_stmts + // .iter() + // .map(PromotionAnalysis::get_inferred_latency) + // .sum(); + // // Try to compact cur_stmts. + // let possibly_compacted_stmt = self.compact_control_vec( + // cur_stmts, + // (cont_reads, cont_writes), + // promotion_analysis, + // builder, + // ); + // new_stmts.push(possibly_compacted_stmt); + // } + // } // Given a total_order and sorted schedule, builds a seq based on the original // schedule. @@ -46,13 +47,12 @@ impl CompactionAnalysis { fn recover_seq( mut total_order: petgraph::graph::DiGraph, ()>, sorted_schedule: Vec<(NodeIndex, u64)>, - attributes: ir::Attributes, - ) -> ir::Control { + ) -> Vec { let stmts = sorted_schedule .into_iter() .map(|(i, _)| total_order[i].take().unwrap()) .collect_vec(); - ir::Control::Seq(ir::Seq { stmts, attributes }) + stmts } // Takes a vec of ctrl stmts and turns it into a compacted schedule (a static par). @@ -67,9 +67,7 @@ impl CompactionAnalysis { ), promotion_analysis: &mut PromotionAnalysis, builder: &mut ir::Builder, - og_latency: u64, - attributes: ir::Attributes, - ) -> ir::Control { + ) -> Vec { // Records the corresponding node indices that each control program // has data dependency on. let mut dependency: HashMap> = HashMap::new(); @@ -80,6 +78,11 @@ impl CompactionAnalysis { // for each node index. let mut schedule: HashMap = HashMap::new(); + let og_latency: u64 = stmts + .iter() + .map(|stmt| PromotionAnalysis::get_inferred_latency(stmt)) + .sum(); + let mut total_order = ControlOrder::::get_dependency_graph_seq( stmts.into_iter(), (cont_reads, cont_writes), @@ -113,11 +116,7 @@ impl CompactionAnalysis { 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, - attributes, - ); + return Self::recover_seq(total_order, sorted_schedule); } // Threads for the static par, where each entry is (thread, thread_latency) @@ -194,13 +193,10 @@ impl CompactionAnalysis { 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); - ir::Control::Static(s_par) + par_control_threads + .into_iter() + .map(|sc| ir::Control::Static(sc)) + .collect() } else { panic!( "Error when producing topo sort. Dependency graph has a cycle." diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index a241c7fba..28c5f7a06 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -117,6 +117,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; @@ -177,6 +183,60 @@ impl StaticPromotion { v.iter().map(Self::approx_size).sum() } + fn promote_seq_heuristic( + &mut self, + builder: &mut ir::Builder, + mut control_vec: Vec, + ) -> Vec { + if control_vec.is_empty() { + // Base case + return vec![]; + } else if control_vec.len() == 1 { + // Base case. Promote if + // self.promotion_analysis. + let mut stmt = control_vec.pop().unwrap(); + if self.fits_heuristics(&stmt) { + return vec![ir::Control::Static( + self.promotion_analysis + .convert_to_static(&mut stmt, builder), + )]; + } else { + return vec![stmt]; + } + } else { + if true { + } 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); + 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; + } + // 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, + )); + vec![sseq] + } + } + /// Converts the control_vec (i..e, the stmts of the seq) using heuristics. fn promote_vec_seq_heuristic( &mut self, From 9ad7d0ddb2419d3e379af2865213324e84e87bfd Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 08:40:32 -0500 Subject: [PATCH 03/13] heuristic/compaction choose --- calyx-opt/src/analysis/compaction_analysis.rs | 17 ++++----- calyx-opt/src/passes/static_promotion.rs | 37 +++++++++++++------ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index 9f3f75503..4e11184a8 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -61,10 +61,6 @@ impl CompactionAnalysis { pub fn compact_control_vec( &mut self, stmts: Vec, - (cont_reads, cont_writes): ( - &Vec>, - &Vec>, - ), promotion_analysis: &mut PromotionAnalysis, builder: &mut ir::Builder, ) -> Vec { @@ -85,7 +81,7 @@ impl CompactionAnalysis { let mut total_order = ControlOrder::::get_dependency_graph_seq( stmts.into_iter(), - (cont_reads, cont_writes), + (&self.cont_reads, &self.cont_writes), &mut dependency, &mut latency_map, ); @@ -193,10 +189,13 @@ impl CompactionAnalysis { 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()); - par_control_threads - .into_iter() - .map(|sc| ir::Control::Static(sc)) - .collect() + 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); + return vec![ir::Control::Static(s_par)]; } else { panic!( "Error when producing topo sort. Dependency graph has a cycle." diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 28c5f7a06..04bc71561 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -189,11 +189,11 @@ impl StaticPromotion { mut control_vec: Vec, ) -> Vec { if control_vec.is_empty() { - // Base case + // Base case len == 0 return vec![]; } else if control_vec.len() == 1 { - // Base case. Promote if - // self.promotion_analysis. + // Base case len == 1. + // Promote if it fits the promotion heuristics. let mut stmt = control_vec.pop().unwrap(); if self.fits_heuristics(&stmt) { return vec![ir::Control::Static( @@ -204,22 +204,37 @@ impl StaticPromotion { return vec![stmt]; } } else { - if true { - } else if Self::approx_control_vec_size(&control_vec) + let mut possibly_compacted_ctrl = + self.compaction_analysis.compact_control_vec( + control_vec, + &mut self.promotion_analysis, + builder, + ); + // If length == 1 this means we have a vec[compacted_static_par], + // so we can return + 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 control_vec; + return possibly_compacted_ctrl; } else if !self.within_cycle_limit( - control_vec + possibly_compacted_ctrl .iter() .map(PromotionAnalysis::get_inferred_latency) .sum(), ) { // Too large, try to break up - let right = control_vec.split_off(control_vec.len() / 2); - let mut left_res = - self.promote_vec_seq_heuristic(builder, control_vec); + let right = possibly_compacted_ctrl + .split_off(possibly_compacted_ctrl.len() / 2); + let mut left_res = self.promote_vec_seq_heuristic( + builder, + possibly_compacted_ctrl, + ); let right_res = self.promote_vec_seq_heuristic(builder, right); left_res.extend(right_res); return left_res; @@ -227,7 +242,7 @@ impl StaticPromotion { // Correct size, convert the entire vec let s_seq_stmts = self .promotion_analysis - .convert_vec_to_static(builder, control_vec); + .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, From aa9f21aba3a51cd1987c262816cd48e966d1f840 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 09:02:30 -0500 Subject: [PATCH 04/13] fix ups --- calyx-opt/src/analysis/inference_analysis.rs | 20 ++++++ calyx-opt/src/passes/schedule_compaction.rs | 2 +- calyx-opt/src/passes/static_promotion.rs | 74 +++++++++----------- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/calyx-opt/src/analysis/inference_analysis.rs b/calyx-opt/src/analysis/inference_analysis.rs index 0b425649a..94269b81d 100644 --- a/calyx-opt/src/analysis/inference_analysis.rs +++ b/calyx-opt/src/analysis/inference_analysis.rs @@ -503,6 +503,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/passes/schedule_compaction.rs b/calyx-opt/src/passes/schedule_compaction.rs index 69f051bea..18ea7bd3a 100644 --- a/calyx-opt/src/passes/schedule_compaction.rs +++ b/calyx-opt/src/passes/schedule_compaction.rs @@ -128,7 +128,7 @@ impl ScheduleCompaction { // First we build the schedule. for i in order { - // Start time is when the latest dependency finishes + // Start time is when the latest depexndency finishes let start = dependency .get(&i) .unwrap() diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 04bc71561..ec6fd4c2c 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -1,5 +1,5 @@ use crate::analysis::{ - CompactionAnalysis, InferenceAnalysis, PromotionAnalysis, + CompactionAnalysis, InferenceAnalysis, PromotionAnalysis, WithStatic, }; use crate::traversal::{ Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult, @@ -374,13 +374,16 @@ impl Visitor for StaticPromotion { // 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( - NonZeroU64::new( - comp.control.borrow().get_latency().unwrap(), - ) - .unwrap(), - ); + 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 { // We decided not to promote, so we need to update data structures. // This case should only happen on @promotable components @@ -398,7 +401,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( @@ -469,34 +472,9 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + self.inference_analysis.fixup_seq(s); + 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(); @@ -506,7 +484,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); @@ -514,12 +492,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( @@ -529,6 +512,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) { @@ -578,6 +563,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) @@ -627,6 +613,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) { @@ -672,6 +660,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 = From 21e4fd9e39f577aa8c8fddefd3e906a5060a9b36 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 09:54:25 -0500 Subject: [PATCH 05/13] rewrite tests --- calyx-opt/src/analysis/compaction_analysis.rs | 35 ++------ calyx-opt/src/passes/static_promotion.rs | 89 ++++++++----------- .../continuous-compaction.expect | 2 +- .../continuous-compaction.futil | 2 +- .../continuous-no-compaction.expect | 23 +++-- .../continuous-no-compaction.futil | 2 +- .../fixup-necessary.expect | 10 +-- .../schedule-compaction/fixup-necessary.futil | 2 +- .../schedule-compaction/no-compact.expect | 8 +- .../schedule-compaction/no-compact.futil | 2 +- .../partial-compaction.expect | 8 +- .../partial-compaction.futil | 19 +--- .../schedule-compaction.expect | 2 +- .../schedule-compaction.futil | 2 +- .../component.futil | 2 +- .../static-inference-promotion/groups.futil | 2 +- .../static-inference-promotion/if-diff.futil | 2 +- .../if-no-else.futil | 2 +- .../static-inference-promotion/invoke.futil | 2 +- .../multi-static.futil | 2 +- .../no_promote_loop.futil | 2 +- .../static-inference-promotion/par.futil | 2 +- .../promote-nested.futil | 2 +- .../threshold.futil | 2 +- .../upgrade-bound.expect | 2 +- .../upgrade-bound.futil | 2 +- .../static-passes/both-passes-promote.futil | 2 +- .../static-promotion/fixup-invoke.futil | 2 +- .../static-promotion/fixup-necessary.futil | 2 +- 29 files changed, 91 insertions(+), 145 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index 4e11184a8..66ab63f5a 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -5,6 +5,8 @@ use itertools::Itertools; use petgraph::{algo, graph::NodeIndex}; use std::collections::HashMap; +use super::ReadWriteSet; + #[derive(Debug, Default)] pub struct CompactionAnalysis { cont_reads: Vec>, @@ -12,34 +14,11 @@ pub struct CompactionAnalysis { } impl CompactionAnalysis { - // Compacts `cur_stmts`, and appends the result to `new_stmts`. - // pub fn append_and_compact( - // &mut self, - // (cont_reads, cont_writes): ( - // &Vec>, - // &Vec>, - // ), - // promotion_analysis: &mut PromotionAnalysis, - // builder: &mut ir::Builder, - // cur_stmts: Vec, - // new_stmts: &mut Vec, - // ) { - // if !cur_stmts.is_empty() { - // let og_latency = cur_stmts - // .iter() - // .map(PromotionAnalysis::get_inferred_latency) - // .sum(); - // // Try to compact cur_stmts. - // let possibly_compacted_stmt = self.compact_control_vec( - // cur_stmts, - // (cont_reads, cont_writes), - // promotion_analysis, - // builder, - // ); - // new_stmts.push(possibly_compacted_stmt); - // } - // } - + pub fn update_cont_read_writes(&mut self, comp: &mut ir::Component) { + let (cont_reads, cont_writes) = ReadWriteSet::cont_read_write_set(comp); + self.cont_reads = cont_reads; + self.cont_writes = cont_writes; + } // Given a total_order and sorted schedule, builds a seq based on the original // schedule. // Note that this function assumes the `total_order`` and `sorted_schedule` diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index ec6fd4c2c..341451145 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -1,5 +1,5 @@ use crate::analysis::{ - CompactionAnalysis, InferenceAnalysis, PromotionAnalysis, WithStatic, + CompactionAnalysis, InferenceAnalysis, PromotionAnalysis, }; use crate::traversal::{ Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult, @@ -43,8 +43,8 @@ pub struct StaticPromotion { if_diff_limit: Option, /// Whether we should stop promoting when we see a loop. cycle_limit: Option, - /// Set no_compaction = true to disable compaction - no_compaction: bool, + /// Whether to perform compaction. True by default + compaction: bool, } // Override constructor to build latency_data information from the primitives @@ -59,13 +59,14 @@ impl ConstructVisitor for StaticPromotion { threshold: opts["threshold"].pos_num().unwrap(), if_diff_limit: opts["if-diff-limit"].pos_num(), cycle_limit: opts["cycle-limit"].pos_num(), - no_compaction: opts["no-compaction"].pos_num().is_some(), + 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(); } } @@ -97,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 { + fn remove_large_promotables(&self, c: &mut ir::Control) { + match c.get_attribute(ir::NumAttr::Promotable) { + Some(pr) => { + 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; @@ -204,12 +222,17 @@ impl StaticPromotion { return vec![stmt]; } } else { - let mut possibly_compacted_ctrl = + 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 if possibly_compacted_ctrl.len() == 1 { @@ -231,11 +254,9 @@ impl StaticPromotion { // Too large, try to break up let right = possibly_compacted_ctrl .split_off(possibly_compacted_ctrl.len() / 2); - let mut left_res = self.promote_vec_seq_heuristic( - builder, - possibly_compacted_ctrl, - ); - let right_res = self.promote_vec_seq_heuristic(builder, right); + 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; } @@ -252,45 +273,6 @@ impl StaticPromotion { } } - /// Converts the control_vec (i..e, the stmts of the seq) using heuristics. - fn promote_vec_seq_heuristic( - &mut self, - builder: &mut ir::Builder, - mut control_vec: Vec, - ) -> Vec { - if control_vec.is_empty() { - // Base case - return 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); - 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; - } - // 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)); - vec![sseq] - } - /// First checks if the vec of control statements meets the self.threshold /// and is within self.cycle_limit /// If so, converts vec of control to a static par, and returns a vec containing @@ -419,6 +401,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) } @@ -473,6 +457,11 @@ impl Visitor for StaticPromotion { _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); let old_stmts = std::mem::take(&mut s.stmts); diff --git a/tests/passes/schedule-compaction/continuous-compaction.expect b/tests/passes/schedule-compaction/continuous-compaction.expect index ab9b64b5b..ea98c5efc 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 f39bd31e8..9b86d4c11 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 ad7b41f28..06a5cb5b6 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 77c435e99..5a3f3a6b4 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 c3f9fa30f..9c248a04c 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 e971a9aad..c2694582c 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 7b5282c94..90b3284d1 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 cc70a6b3e..40c51a72d 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 8ed953a6b..13d34bcbe 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 eda318e31..dd684c757 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 5503b55d1..6099271d0 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 44e709619..48813ed66 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 27efe57a0..1ce2cb35b 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 679ab9499..9084f4b15 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 13bfa4142..b34916d5b 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 a658eac11..692e3613b 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 ecc2a9d41..61d23ce51 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 2361e05f9..fdb925bab 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 cb90f4fe8..fe918d2cd 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 5604bf10b..be732419e 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 de8435d99..7983de612 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 3e6e8d046..c30bd7f1b 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 0bf3df2e8..d8f44c604 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 63247fbf1..b87884945 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 fa0bd0db5..52551400b 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-promotion/fixup-invoke.futil b/tests/passes/static-promotion/fixup-invoke.futil index a4c72d6bf..fd20538b4 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 25a304b82..0b2da44a3 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"; From b0f92237593c049f688cd495fbcb9a551a7ad6ae Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 10:06:18 -0500 Subject: [PATCH 06/13] promotion --- calyx-opt/src/default_passes.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index 20989f605..70aebdfe6 100644 --- a/calyx-opt/src/default_passes.rs +++ b/calyx-opt/src/default_passes.rs @@ -94,8 +94,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 From 8b15479aef775954aba1077c5ee96caefa59c023 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 10:21:32 -0500 Subject: [PATCH 07/13] cargo --- calyx-opt/src/analysis/compaction_analysis.rs | 9 ++++----- calyx-opt/src/passes/static_promotion.rs | 17 +++++++---------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index 66ab63f5a..fe30da671 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -27,11 +27,10 @@ impl CompactionAnalysis { mut total_order: petgraph::graph::DiGraph, ()>, sorted_schedule: Vec<(NodeIndex, u64)>, ) -> Vec { - let stmts = sorted_schedule + sorted_schedule .into_iter() .map(|(i, _)| total_order[i].take().unwrap()) - .collect_vec(); - stmts + .collect_vec() } // Takes a vec of ctrl stmts and turns it into a compacted schedule (a static par). @@ -55,7 +54,7 @@ impl CompactionAnalysis { let og_latency: u64 = stmts .iter() - .map(|stmt| PromotionAnalysis::get_inferred_latency(stmt)) + .map(PromotionAnalysis::get_inferred_latency) .sum(); let mut total_order = ControlOrder::::get_dependency_graph_seq( @@ -174,7 +173,7 @@ impl CompactionAnalysis { latency: total_time, }); s_par.get_mut_attributes().insert(ir::BoolAttr::Promoted, 1); - return vec![ir::Control::Static(s_par)]; + vec![ir::Control::Static(s_par)] } else { panic!( "Error when producing topo sort. Dependency graph has a cycle." diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 341451145..d762552a3 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -111,13 +111,10 @@ impl Named for StaticPromotion { impl StaticPromotion { fn remove_large_promotables(&self, c: &mut ir::Control) { - match c.get_attribute(ir::NumAttr::Promotable) { - Some(pr) => { - if !self.within_cycle_limit(pr) { - c.get_mut_attributes().remove(ir::NumAttr::Promotable) - } + if let Some(pr) = c.get_attribute(ir::NumAttr::Promotable) { + if !self.within_cycle_limit(pr) { + c.get_mut_attributes().remove(ir::NumAttr::Promotable) } - _ => (), } } @@ -208,18 +205,18 @@ impl StaticPromotion { ) -> Vec { if control_vec.is_empty() { // Base case len == 0 - return vec![]; + vec![] } else if control_vec.len() == 1 { // Base case len == 1. // Promote if it fits the promotion heuristics. let mut stmt = control_vec.pop().unwrap(); if self.fits_heuristics(&stmt) { - return vec![ir::Control::Static( + vec![ir::Control::Static( self.promotion_analysis .convert_to_static(&mut stmt, builder), - )]; + )] } else { - return vec![stmt]; + vec![stmt] } } else { let mut possibly_compacted_ctrl = if self.compaction { From 1ffc50e75b494bcfa7ba83e6e73a47d07fc6eefa Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 10:33:57 -0500 Subject: [PATCH 08/13] no compact --- tests/passes/static-passes/no-compact.expect | 63 +++++++++++++++++++ tests/passes/static-passes/no-compact.futil | 66 ++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 tests/passes/static-passes/no-compact.expect create mode 100644 tests/passes/static-passes/no-compact.futil diff --git a/tests/passes/static-passes/no-compact.expect b/tests/passes/static-passes/no-compact.expect new file mode 100644 index 000000000..1e9418ec5 --- /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 000000000..553bfb6f3 --- /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 From fef222bf974508451ded83aa876eaa357df6881a Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 10:50:40 -0500 Subject: [PATCH 09/13] delte compaction --- calyx-opt/src/default_passes.rs | 9 +- calyx-opt/src/passes/mod.rs | 2 - calyx-opt/src/passes/schedule_compaction.rs | 378 -------------------- 3 files changed, 4 insertions(+), 385 deletions(-) delete mode 100644 calyx-opt/src/passes/schedule_compaction.rs diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index 70aebdfe6..74782acb5 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::()?; diff --git a/calyx-opt/src/passes/mod.rs b/calyx-opt/src/passes/mod.rs index b9aa04c15..87e9c8171 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/schedule_compaction.rs b/calyx-opt/src/passes/schedule_compaction.rs deleted file mode 100644 index 18ea7bd3a..000000000 --- a/calyx-opt/src/passes/schedule_compaction.rs +++ /dev/null @@ -1,378 +0,0 @@ -use crate::analysis::{InferenceAnalysis, PromotionAnalysis, ReadWriteSet}; -use crate::traversal::{Action, ConstructVisitor}; -use crate::{ - analysis, - traversal::{Named, Visitor}, -}; -use calyx_ir as ir; -use calyx_utils::CalyxResult; -use ir::GetAttributes; -use itertools::Itertools; -use petgraph::{algo, graph::NodeIndex}; -use std::collections::HashMap; - -/// For static seqs that are statically promoted by the compiler. -/// Aggressively compacts the execution schedule so that the execution -/// order of control operators still respects data dependency -/// Example: see tests/passes/schedule-compaction/schedule-compaction.futil -pub struct ScheduleCompaction { - inference_analysis: InferenceAnalysis, - promotion_analysis: PromotionAnalysis, -} - -// Override constructor to build latency_data information from the primitives -// library. -impl ConstructVisitor for ScheduleCompaction { - fn from(ctx: &ir::Context) -> CalyxResult { - Ok(ScheduleCompaction { - inference_analysis: InferenceAnalysis::from_ctx(ctx), - promotion_analysis: PromotionAnalysis::default(), - }) - } - - // This pass shared information between components - fn clear_data(&mut self) { - self.promotion_analysis = PromotionAnalysis::default() - } -} - -impl Named for ScheduleCompaction { - fn name() -> &'static str { - "schedule-compaction" - } - - fn description() -> &'static str { - "compact execution scheduled for reschedulable static programs" - } -} - -impl ScheduleCompaction { - // Compacts `cur_stmts`, and appends the result to `new_stmts`. - fn append_and_compact( - &mut self, - (cont_reads, cont_writes): ( - &Vec>, - &Vec>, - ), - builder: &mut ir::Builder, - cur_stmts: Vec, - new_stmts: &mut Vec, - ) { - if !cur_stmts.is_empty() { - let og_latency = cur_stmts - .iter() - .map(PromotionAnalysis::get_inferred_latency) - .sum(); - // Try to compact cur_stmts. - let possibly_compacted_stmt = self.compact_control_vec( - cur_stmts, - (cont_reads, cont_writes), - builder, - og_latency, - ir::Attributes::default(), - ); - new_stmts.push(possibly_compacted_stmt); - } - } - - // Given a total_order and sorted schedule, builds a seq based on the original - // schedule. - // 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)>, - attributes: ir::Attributes, - ) -> ir::Control { - let stmts = sorted_schedule - .into_iter() - .map(|(i, _)| total_order[i].take().unwrap()) - .collect_vec(); - ir::Control::Seq(ir::Seq { stmts, attributes }) - } - - // Takes a vec of ctrl stmts and turns it into a compacted schedule (a static par). - // If it can't compact at all, then it just returns a `seq` in the `stmts` - // original order. - fn compact_control_vec( - &mut self, - stmts: Vec, - (cont_reads, cont_writes): ( - &Vec>, - &Vec>, - ), - builder: &mut ir::Builder, - og_latency: u64, - attributes: ir::Attributes, - ) -> ir::Control { - // 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 mut total_order = - analysis::ControlOrder::::get_dependency_graph_seq( - stmts.into_iter(), - (cont_reads, 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 depexndency 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, - attributes, - ); - } - - // 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| { - self.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); - ir::Control::Static(s_par) - } else { - panic!( - "Error when producing topo sort. Dependency graph has a cycle." - ); - } - } -} - -impl Visitor for ScheduleCompaction { - fn iteration_order() -> crate::traversal::Order - where - Self: Sized, - { - crate::traversal::Order::Post - } - - fn finish_seq( - &mut self, - s: &mut calyx_ir::Seq, - comp: &mut calyx_ir::Component, - sigs: &calyx_ir::LibrarySignatures, - _comps: &[calyx_ir::Component], - ) -> crate::traversal::VisResult { - let (cont_reads, cont_writes) = ReadWriteSet::cont_read_write_set(comp); - InferenceAnalysis::remove_promotable_from_seq(s); - self.inference_analysis.fixup_seq(s); - - let mut builder = ir::Builder::new(comp, sigs); - - if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) { - // If entire seq is promotable, then we can compact entire thing - // and replace it with a static construct. - return Ok(Action::Change(Box::new(self.compact_control_vec( - std::mem::take(&mut s.stmts), - (&cont_reads, &cont_writes), - &mut builder, - latency, - std::mem::take(&mut s.attributes), - )))); - } - - // We have to break up the seq into portions that we can compact. - let old_stmts = std::mem::take(&mut s.stmts); - let mut new_stmts: Vec = Vec::new(); - let mut cur_stmts: Vec = Vec::new(); - - for stmt in old_stmts { - if PromotionAnalysis::can_be_promoted(&stmt) { - cur_stmts.push(stmt); - } else { - self.append_and_compact( - (&cont_reads, &cont_writes), - &mut builder, - cur_stmts, - &mut new_stmts, - ); - // Appending the non-promotable statement. - new_stmts.push(stmt); - // New cur_vec - cur_stmts = Vec::new(); - } - } - self.append_and_compact( - (&cont_reads, &cont_writes), - &mut builder, - cur_stmts, - &mut new_stmts, - ); - Ok(Action::change(ir::Control::Seq(ir::Seq { - stmts: new_stmts, - attributes: ir::Attributes::default(), - }))) - } - - fn finish( - &mut self, - comp: &mut ir::Component, - _sigs: &ir::LibrarySignatures, - _comps: &[ir::Component], - ) -> crate::traversal::VisResult { - // Re-infer component's latency. - self.inference_analysis.fixup_timing(comp); - if comp.name != "main" { - // Fixup components to show the new latency. - let comp_sig = comp.signature.borrow(); - let go_ports: Vec<_> = - comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec(); - // We only need to check for the @promotable attribute. - // The @interval attribute means the component's control is entirely - // static, meaning it's interval/latency is already locked in, so - // we know we can't change its control, so no need to change its - // signature. - if go_ports.iter().any(|go_port| { - go_port.borrow_mut().attributes.has(ir::NumAttr::Promotable) - }) { - // Getting current latency - let cur_latency = go_ports - .iter() - .filter_map(|go_port| { - go_port - .borrow_mut() - .attributes - .get(ir::NumAttr::Promotable) - }) - .next() - .unwrap(); - // Getting new latency. We know it will exist because compaction - // does not remove latency information, it just alters it. - let new_latency = InferenceAnalysis::get_possible_latency( - &comp.control.borrow(), - ) - .unwrap(); - if cur_latency != new_latency { - // We adjust the signature of our component - self.inference_analysis - .adjust_component((comp.name, new_latency)); - for go_port in go_ports { - go_port - .borrow_mut() - .attributes - .insert(ir::NumAttr::Promotable, new_latency); - } - } - }; - } - Ok(Action::Continue) - } - - fn start( - &mut self, - comp: &mut ir::Component, - _sigs: &ir::LibrarySignatures, - _comps: &[ir::Component], - ) -> crate::traversal::VisResult { - self.inference_analysis.fixup_timing(comp); - Ok(Action::Continue) - } -} From 7f14e004eb42b08c91b6794e6fb4ca4f35825d1c Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 11:11:09 -0500 Subject: [PATCH 10/13] fix tests --- tests/passes/tdcc/new-fsm-nested.futil | 2 +- tests/passes/tdcc/while.futil | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/passes/tdcc/new-fsm-nested.futil b/tests/passes/tdcc/new-fsm-nested.futil index 785fa2606..ca4502483 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 fc1460df2..35a716587 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"; From 757c622cb0e3bfc4f5e645694703c8882142089b Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 17:06:21 -0500 Subject: [PATCH 11/13] documentation --- calyx-opt/src/analysis/compaction_analysis.rs | 16 +++++++++++----- calyx-opt/src/passes/static_promotion.rs | 7 ++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index fe30da671..5b5e06993 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -7,6 +7,9 @@ use std::collections::HashMap; use super::ReadWriteSet; +/// 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>, @@ -14,13 +17,14 @@ pub struct CompactionAnalysis { } 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) = ReadWriteSet::cont_read_write_set(comp); self.cont_reads = cont_reads; self.cont_writes = cont_writes; } - // Given a total_order and sorted schedule, builds a seq based on the original - // schedule. + + // 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( @@ -33,9 +37,11 @@ impl CompactionAnalysis { .collect_vec() } - // Takes a vec of ctrl stmts and turns it into a compacted schedule (a static par). - // If it can't compact at all, then it just returns a `seq` in the `stmts` - // original order. + /// 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, diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index d762552a3..4adc1ab36 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -110,6 +110,9 @@ impl Named for StaticPromotion { } 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) { @@ -231,7 +234,9 @@ impl StaticPromotion { control_vec }; // If length == 1 this means we have a vec[compacted_static_par], - // so we can return + // 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; } From b5d1e37a9613f71bc353c739b29e017a8ae40530 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 12 Feb 2024 17:18:15 -0500 Subject: [PATCH 12/13] inference analysis --- calyx-opt/src/analysis/inference_analysis.rs | 17 ++++-- calyx-opt/src/passes/static_promotion.rs | 55 +++++++++----------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/calyx-opt/src/analysis/inference_analysis.rs b/calyx-opt/src/analysis/inference_analysis.rs index 94269b81d..d32113c6e 100644 --- a/calyx-opt/src/analysis/inference_analysis.rs +++ b/calyx-opt/src/analysis/inference_analysis.rs @@ -205,7 +205,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); } @@ -217,15 +221,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: diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 4adc1ab36..b13c11695 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -344,37 +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); - } - // (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 { - // 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. From 1a1c1f130873fd2121d8d6f7f119e4906a200688 Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 16 Feb 2024 15:59:02 -0500 Subject: [PATCH 13/13] rw set --- calyx-opt/src/analysis/compaction_analysis.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/calyx-opt/src/analysis/compaction_analysis.rs b/calyx-opt/src/analysis/compaction_analysis.rs index 5b5e06993..bd82be62b 100644 --- a/calyx-opt/src/analysis/compaction_analysis.rs +++ b/calyx-opt/src/analysis/compaction_analysis.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use petgraph::{algo, graph::NodeIndex}; use std::collections::HashMap; -use super::ReadWriteSet; +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 @@ -19,7 +19,18 @@ pub struct CompactionAnalysis { 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) = ReadWriteSet::cont_read_write_set(comp); + 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; }