1use crate::analysis::{
2 CompactionAnalysis, InferenceAnalysis, PromotionAnalysis,
3};
4use crate::traversal::{
5 Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult,
6 Visitor,
7};
8use calyx_ir::{self as ir, LibrarySignatures};
9use calyx_utils::CalyxResult;
10use ir::GetAttributes;
11use itertools::Itertools;
12use std::num::NonZeroU64;
13use std::rc::Rc;
14
15const APPROX_ENABLE_SIZE: u64 = 1;
16const APPROX_IF_SIZE: u64 = 3;
17const APPROX_WHILE_REPEAT_SIZE: u64 = 3;
18
19pub struct StaticPromotion {
32 inference_analysis: InferenceAnalysis,
35 promotion_analysis: PromotionAnalysis,
38 compaction_analysis: CompactionAnalysis,
40 threshold: u64,
42 if_diff_limit: Option<u64>,
44 cycle_limit: Option<u64>,
46 compaction: bool,
48}
49
50impl ConstructVisitor for StaticPromotion {
53 fn from(ctx: &ir::Context) -> CalyxResult<Self> {
54 let opts = Self::get_opts(ctx);
55 Ok(StaticPromotion {
56 inference_analysis: InferenceAnalysis::from_ctx(ctx),
57 promotion_analysis: PromotionAnalysis::default(),
58 compaction_analysis: CompactionAnalysis::default(),
59 threshold: opts["threshold"].pos_num().unwrap(),
60 if_diff_limit: opts["if-diff-limit"].pos_num(),
61 cycle_limit: opts["cycle-limit"].pos_num(),
62 compaction: opts["compaction"].bool(),
63 })
64 }
65
66 fn clear_data(&mut self) {
68 self.promotion_analysis = PromotionAnalysis::default();
69 self.compaction_analysis = CompactionAnalysis::default();
70 }
71}
72
73impl Named for StaticPromotion {
74 fn name() -> &'static str {
75 "static-promotion"
76 }
77
78 fn description() -> &'static str {
79 "promote dynamic control programs to static when possible"
80 }
81
82 fn opts() -> Vec<PassOpt> {
83 vec![
84 PassOpt::new(
85 "threshold",
86 "minimum number of groups needed to promote a dynamic control program to static",
87 ParseVal::Num(1),
88 PassOpt::parse_num,
89 ),
90 PassOpt::new(
91 "cycle-limit",
92 "maximum number of cycles to promote a dynamic control program to static",
93 ParseVal::Num(33554432),
94 PassOpt::parse_num,
95 ),
96 PassOpt::new(
97 "if-diff-limit",
98 "the maximum difference between if branches that we tolerate for promotion",
99 ParseVal::Num(1),
100 PassOpt::parse_num,
101 ),
102 PassOpt::new(
103 "compaction",
104 "Whether to perform compaction. True by Default ",
105 ParseVal::Bool(true),
106 PassOpt::parse_bool,
107 )
108 ]
109 }
110}
111
112impl StaticPromotion {
113 fn remove_large_promotables(&self, c: &mut ir::Control) {
117 if let Some(pr) = c.get_attribute(ir::NumAttr::Promotable) {
118 if !self.within_cycle_limit(pr) {
119 c.get_mut_attributes().remove(ir::NumAttr::Promotable)
120 }
121 }
122 }
123
124 fn within_cycle_limit(&self, latency: u64) -> bool {
125 if self.cycle_limit.is_none() {
126 return true;
127 }
128 latency < self.cycle_limit.unwrap()
129 }
130
131 fn within_if_diff_limit(&self, diff: u64) -> bool {
132 if self.if_diff_limit.is_none() {
133 return true;
134 }
135 diff <= self.if_diff_limit.unwrap()
136 }
137
138 fn fits_heuristics(&self, c: &ir::Control) -> bool {
139 let approx_size = Self::approx_size(c);
140 let latency = PromotionAnalysis::get_inferred_latency(c);
141 self.within_cycle_limit(latency) && approx_size > self.threshold
142 }
143
144 fn approx_size_static(sc: &ir::StaticControl, promoted: bool) -> u64 {
145 if !(sc.get_attributes().has(ir::BoolAttr::Promoted) || promoted) {
146 return APPROX_ENABLE_SIZE;
147 }
148 match sc {
149 ir::StaticControl::Empty(_) => 0,
150 ir::StaticControl::Enable(_) | ir::StaticControl::Invoke(_) => {
151 APPROX_ENABLE_SIZE
152 }
153 ir::StaticControl::Repeat(ir::StaticRepeat { body, .. }) => {
154 Self::approx_size_static(body, true) + APPROX_WHILE_REPEAT_SIZE
155 }
156 ir::StaticControl::If(ir::StaticIf {
157 tbranch, fbranch, ..
158 }) => {
159 Self::approx_size_static(tbranch, true)
160 + Self::approx_size_static(fbranch, true)
161 + APPROX_IF_SIZE
162 }
163 ir::StaticControl::Par(ir::StaticPar { stmts, .. })
164 | ir::StaticControl::Seq(ir::StaticSeq { stmts, .. }) => stmts
165 .iter()
166 .map(|stmt| Self::approx_size_static(stmt, true))
167 .sum(),
168 }
169 }
170
171 fn approx_size(c: &ir::Control) -> u64 {
174 match c {
175 ir::Control::Empty(_) => 0,
176 ir::Control::Enable(_) | ir::Control::Invoke(_) => {
177 APPROX_ENABLE_SIZE
178 }
179 ir::Control::Seq(ir::Seq { stmts, .. })
180 | ir::Control::Par(ir::Par { stmts, .. }) => {
181 stmts.iter().map(Self::approx_size).sum()
182 }
183 ir::Control::Repeat(ir::Repeat { body, .. })
184 | ir::Control::While(ir::While { body, .. }) => {
185 Self::approx_size(body) + APPROX_WHILE_REPEAT_SIZE
186 }
187 ir::Control::If(ir::If {
188 tbranch, fbranch, ..
189 }) => {
190 Self::approx_size(tbranch)
191 + Self::approx_size(fbranch)
192 + APPROX_IF_SIZE
193 }
194 ir::Control::Static(sc) => Self::approx_size_static(sc, false),
195 }
196 }
197
198 fn approx_control_vec_size(v: &[ir::Control]) -> u64 {
201 v.iter().map(Self::approx_size).sum()
202 }
203
204 fn promote_seq_heuristic(
205 &mut self,
206 builder: &mut ir::Builder,
207 mut control_vec: Vec<ir::Control>,
208 ) -> Vec<ir::Control> {
209 if control_vec.is_empty() {
210 vec![]
212 } else if control_vec.len() == 1 {
213 let mut stmt = control_vec.pop().unwrap();
216 if self.fits_heuristics(&stmt) {
217 vec![ir::Control::Static(
218 self.promotion_analysis
219 .convert_to_static(&mut stmt, builder),
220 )]
221 } else {
222 vec![stmt]
223 }
224 } else {
225 let mut possibly_compacted_ctrl = if self.compaction {
226 self.compaction_analysis.compact_control_vec(
228 control_vec,
229 &mut self.promotion_analysis,
230 builder,
231 )
232 } else {
233 control_vec
235 };
236 if possibly_compacted_ctrl.len() == 1 {
241 return possibly_compacted_ctrl;
242 }
243 if Self::approx_control_vec_size(&possibly_compacted_ctrl)
246 <= self.threshold
247 {
248 return possibly_compacted_ctrl;
250 } else if !self.within_cycle_limit(
251 possibly_compacted_ctrl
252 .iter()
253 .map(PromotionAnalysis::get_inferred_latency)
254 .sum(),
255 ) {
256 let right = possibly_compacted_ctrl
258 .split_off(possibly_compacted_ctrl.len() / 2);
259 let mut left_res = self
260 .promote_seq_heuristic(builder, possibly_compacted_ctrl);
261 let right_res = self.promote_seq_heuristic(builder, right);
262 left_res.extend(right_res);
263 return left_res;
264 }
265 let s_seq_stmts = self
267 .promotion_analysis
268 .convert_vec_to_static(builder, possibly_compacted_ctrl);
269 let latency = s_seq_stmts.iter().map(|sc| sc.get_latency()).sum();
270 let sseq = ir::Control::Static(ir::StaticControl::seq(
271 s_seq_stmts,
272 latency,
273 ));
274 vec![sseq]
275 }
276 }
277
278 fn promote_vec_par_heuristic(
284 &mut self,
285 builder: &mut ir::Builder,
286 mut control_vec: Vec<ir::Control>,
287 ) -> Vec<ir::Control> {
288 if control_vec.is_empty() {
289 return vec![];
291 } else if control_vec.len() == 1 {
292 return vec![control_vec.pop().unwrap()];
293 } else if Self::approx_control_vec_size(&control_vec) <= self.threshold
294 {
295 return control_vec;
297 } else if !self.within_cycle_limit(
298 control_vec
299 .iter()
300 .map(PromotionAnalysis::get_inferred_latency)
301 .max()
302 .unwrap_or_else(|| unreachable!("Empty Par Block")),
303 ) {
304 let (index, _) = control_vec
307 .iter()
308 .enumerate()
309 .max_by_key(|&(_, c)| Self::approx_size(c))
310 .unwrap();
311 let largest_thread = control_vec.remove(index);
313 let mut left = self.promote_vec_par_heuristic(builder, control_vec);
314 left.push(largest_thread);
315 return left;
316 }
317 let s_par_stmts = self
319 .promotion_analysis
320 .convert_vec_to_static(builder, control_vec);
321 let latency = s_par_stmts
322 .iter()
323 .map(|sc| sc.get_latency())
324 .max()
325 .unwrap_or_else(|| unreachable!("empty par block"));
326 let spar =
327 ir::Control::Static(ir::StaticControl::par(s_par_stmts, latency));
328 vec![spar]
329 }
330}
331
332impl Visitor for StaticPromotion {
333 fn iteration_order() -> Order {
336 Order::Post
337 }
338
339 fn finish(
340 &mut self,
341 comp: &mut ir::Component,
342 _lib: &LibrarySignatures,
343 _comps: &[ir::Component],
344 ) -> VisResult {
345 if comp.name != "main" {
346 let comp_sig = comp.signature.borrow();
347 if comp.control.borrow().is_static() {
348 if !comp.is_static() {
350 comp.attributes.insert(ir::BoolAttr::Promoted, 1);
353 }
354 let new_latency = NonZeroU64::new(
356 comp.control.borrow().get_latency().unwrap(),
357 )
358 .unwrap();
359 comp.latency = Some(new_latency);
361 self.inference_analysis
363 .adjust_component((comp.name, new_latency.into()));
364 } else if !comp.control.borrow().is_empty() {
365 self.inference_analysis.remove_component(comp.name);
371 };
372
373 let go_ports =
374 comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec();
375 for go_port in go_ports {
379 go_port
380 .borrow_mut()
381 .attributes
382 .remove(ir::NumAttr::Promotable);
383 }
384 }
385 InferenceAnalysis::remove_promotable_attribute(
389 &mut comp.control.borrow_mut(),
390 );
391 Ok(Action::Continue)
392 }
393
394 fn start(
395 &mut self,
396 comp: &mut ir::Component,
397 _sigs: &LibrarySignatures,
398 _comps: &[ir::Component],
399 ) -> VisResult {
400 self.inference_analysis.fixup_timing(comp);
403 self.compaction_analysis.update_cont_read_writes(comp);
405 Ok(Action::Continue)
406 }
407
408 fn enable(
409 &mut self,
410 s: &mut ir::Enable,
411 comp: &mut ir::Component,
412 sigs: &LibrarySignatures,
413 _comps: &[ir::Component],
414 ) -> VisResult {
415 let mut builder = ir::Builder::new(comp, sigs);
416 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
417 if self.within_cycle_limit(latency)
420 && (APPROX_ENABLE_SIZE > self.threshold)
421 {
422 return Ok(Action::change(ir::Control::Static(
423 self.promotion_analysis
424 .convert_enable_to_static(s, &mut builder),
425 )));
426 }
427 }
428 Ok(Action::Continue)
429 }
430
431 fn invoke(
432 &mut self,
433 s: &mut ir::Invoke,
434 _comp: &mut ir::Component,
435 _sigs: &LibrarySignatures,
436 _comps: &[ir::Component],
437 ) -> VisResult {
438 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
439 if self.within_cycle_limit(latency)
441 && (APPROX_ENABLE_SIZE > self.threshold)
442 {
443 return Ok(Action::change(ir::Control::Static(
444 self.promotion_analysis.convert_invoke_to_static(s),
445 )));
446 }
447 }
448 Ok(Action::Continue)
449 }
450
451 fn finish_seq(
452 &mut self,
453 s: &mut ir::Seq,
454 comp: &mut ir::Component,
455 sigs: &LibrarySignatures,
456 _comps: &[ir::Component],
457 ) -> VisResult {
458 self.inference_analysis.fixup_seq(s);
459 s.stmts
462 .iter_mut()
463 .for_each(|c| self.remove_large_promotables(c));
464
465 let mut builder = ir::Builder::new(comp, sigs);
466 let old_stmts = std::mem::take(&mut s.stmts);
467 let mut new_stmts: Vec<ir::Control> = Vec::new();
468 let mut cur_vec: Vec<ir::Control> = Vec::new();
469 for stmt in old_stmts {
470 if PromotionAnalysis::can_be_promoted(&stmt) {
471 cur_vec.push(stmt);
472 } else {
473 let possibly_promoted_stmts =
475 self.promote_seq_heuristic(&mut builder, cur_vec);
476 new_stmts.extend(possibly_promoted_stmts);
477 new_stmts.push(stmt);
479 cur_vec = Vec::new();
481 }
482 }
483 new_stmts.extend(self.promote_seq_heuristic(&mut builder, cur_vec));
484 let mut new_ctrl = if new_stmts.len() == 1 {
485 new_stmts.pop().unwrap()
486 } else {
487 ir::Control::Seq(ir::Seq {
488 stmts: new_stmts,
489 attributes: ir::Attributes::default(),
490 })
491 };
492 self.inference_analysis.fixup_ctrl(&mut new_ctrl);
493 Ok(Action::change(new_ctrl))
494 }
495
496 fn finish_par(
497 &mut self,
498 s: &mut ir::Par,
499 comp: &mut ir::Component,
500 sigs: &LibrarySignatures,
501 _comps: &[ir::Component],
502 ) -> VisResult {
503 self.inference_analysis.fixup_par(s);
504
505 let mut builder = ir::Builder::new(comp, sigs);
506 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
508 let approx_size: u64 = s.stmts.iter().map(Self::approx_size).sum();
509 if approx_size <= self.threshold {
510 return Ok(Action::Continue);
512 } else if self.within_cycle_limit(latency) {
513 let spar = ir::Control::Static(ir::StaticControl::par(
515 self.promotion_analysis.convert_vec_to_static(
516 &mut builder,
517 std::mem::take(&mut s.stmts),
518 ),
519 latency,
520 ));
521 return Ok(Action::change(spar));
522 }
523 }
524 let mut new_stmts: Vec<ir::Control> = Vec::new();
525 let (s_stmts, d_stmts): (Vec<ir::Control>, Vec<ir::Control>) = s
535 .stmts
536 .drain(..)
537 .partition(PromotionAnalysis::can_be_promoted);
538 new_stmts.extend(self.promote_vec_par_heuristic(&mut builder, s_stmts));
539 new_stmts.extend(d_stmts);
540 let new_par = ir::Control::Par(ir::Par {
541 stmts: new_stmts,
542 attributes: ir::Attributes::default(),
543 });
544 Ok(Action::change(new_par))
545 }
546
547 fn finish_if(
548 &mut self,
549 s: &mut ir::If,
550 comp: &mut ir::Component,
551 sigs: &LibrarySignatures,
552 _comps: &[ir::Component],
553 ) -> VisResult {
554 self.inference_analysis.fixup_if(s);
555 let mut builder = ir::Builder::new(comp, sigs);
556 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
557 let approx_size_if = Self::approx_size(&s.tbranch)
558 + Self::approx_size(&s.fbranch)
559 + APPROX_IF_SIZE;
560 let branch_diff = PromotionAnalysis::get_inferred_latency(
561 &s.tbranch,
562 )
563 .abs_diff(PromotionAnalysis::get_inferred_latency(&s.fbranch));
564 if approx_size_if > self.threshold
565 && self.within_cycle_limit(latency)
566 && self.within_if_diff_limit(branch_diff)
567 {
568 let static_tbranch = self
570 .promotion_analysis
571 .convert_to_static(&mut s.tbranch, &mut builder);
572 let static_fbranch = self
573 .promotion_analysis
574 .convert_to_static(&mut s.fbranch, &mut builder);
575 return Ok(Action::change(ir::Control::Static(
576 ir::StaticControl::static_if(
577 Rc::clone(&s.port),
578 Box::new(static_tbranch),
579 Box::new(static_fbranch),
580 latency,
581 ),
582 )));
583 }
584 if !(self.within_cycle_limit(latency)) {
590 s.attributes.remove(ir::NumAttr::Promotable);
591 }
592 }
593 Ok(Action::Continue)
594 }
595
596 fn finish_while(
598 &mut self,
599 s: &mut ir::While,
600 comp: &mut ir::Component,
601 sigs: &LibrarySignatures,
602 _comps: &[ir::Component],
603 ) -> VisResult {
604 self.inference_analysis.fixup_while(s);
605
606 let mut builder = ir::Builder::new(comp, sigs);
607 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
609 let approx_size =
610 Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE;
611 if approx_size > self.threshold && self.within_cycle_limit(latency)
613 {
614 let sc = self
616 .promotion_analysis
617 .convert_to_static(&mut s.body, &mut builder);
618 let static_repeat = ir::StaticControl::repeat(
619 s.attributes.get(ir::NumAttr::Bound).unwrap_or_else(|| {
620 unreachable!(
621 "Unbounded loop has has @promotable attribute"
622 )
623 }),
624 latency,
625 Box::new(sc),
626 );
627 return Ok(Action::Change(Box::new(ir::Control::Static(
628 static_repeat,
629 ))));
630 }
631 if !(self.within_cycle_limit(latency)) {
637 s.attributes.remove(ir::NumAttr::Promotable);
638 }
639 }
640 Ok(Action::Continue)
641 }
642
643 fn finish_repeat(
645 &mut self,
646 s: &mut ir::Repeat,
647 comp: &mut ir::Component,
648 sigs: &LibrarySignatures,
649 _comps: &[ir::Component],
650 ) -> VisResult {
651 self.inference_analysis.fixup_repeat(s);
652
653 let mut builder = ir::Builder::new(comp, sigs);
654 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
655 let approx_size =
656 Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE;
657 if approx_size > self.threshold && self.within_cycle_limit(latency)
658 {
659 let sc = self
661 .promotion_analysis
662 .convert_to_static(&mut s.body, &mut builder);
663 let static_repeat = ir::StaticControl::repeat(
664 s.num_repeats,
665 latency,
666 Box::new(sc),
667 );
668 return Ok(Action::Change(Box::new(ir::Control::Static(
669 static_repeat,
670 ))));
671 }
672 if !(self.within_cycle_limit(latency)) {
678 s.attributes.remove(ir::NumAttr::Promotable);
679 }
680 }
681 Ok(Action::Continue)
682 }
683}