1use crate::analysis::{
2 CompactionAnalysis, InferenceAnalysis, PromotionAnalysis,
3};
4use crate::traversal::{
5 Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult,
6 Visitor,
7};
8use calyx_frontend::SetAttr;
9use calyx_ir::{self as ir, BoolAttr, LibrarySignatures};
10use calyx_utils::CalyxResult;
11use ir::GetAttributes;
12use itertools::Itertools;
13use std::num::NonZeroU64;
14use std::rc::Rc;
15
16const APPROX_ENABLE_SIZE: u64 = 1;
17const APPROX_IF_SIZE: u64 = 3;
18const APPROX_WHILE_REPEAT_SIZE: u64 = 3;
19
20pub struct StaticPromotion {
33 inference_analysis: InferenceAnalysis,
36 promotion_analysis: PromotionAnalysis,
39 compaction_analysis: CompactionAnalysis,
41 threshold: u64,
43 if_diff_limit: Option<u64>,
45 cycle_limit: Option<u64>,
47 compaction: bool,
49}
50
51impl ConstructVisitor for StaticPromotion {
54 fn from(ctx: &ir::Context) -> CalyxResult<Self> {
55 let opts = Self::get_opts(ctx);
56 Ok(StaticPromotion {
57 inference_analysis: InferenceAnalysis::from_ctx(ctx),
58 promotion_analysis: PromotionAnalysis::default(),
59 compaction_analysis: CompactionAnalysis::default(),
60 threshold: opts["threshold"].pos_num().unwrap(),
61 if_diff_limit: opts["if-diff-limit"].pos_num(),
62 cycle_limit: opts["cycle-limit"].pos_num(),
63 compaction: opts["compaction"].bool(),
64 })
65 }
66
67 fn clear_data(&mut self) {
69 self.promotion_analysis = PromotionAnalysis::default();
70 self.compaction_analysis = CompactionAnalysis::default();
71 }
72}
73
74impl Named for StaticPromotion {
75 fn name() -> &'static str {
76 "static-promotion"
77 }
78
79 fn description() -> &'static str {
80 "promote dynamic control programs to static when possible"
81 }
82
83 fn opts() -> Vec<PassOpt> {
84 vec![
85 PassOpt::new(
86 "threshold",
87 "minimum number of groups needed to promote a dynamic control program to static",
88 ParseVal::Num(1),
89 PassOpt::parse_num,
90 ),
91 PassOpt::new(
92 "cycle-limit",
93 "maximum number of cycles to promote a dynamic control program to static",
94 ParseVal::Num(33554432),
95 PassOpt::parse_num,
96 ),
97 PassOpt::new(
98 "if-diff-limit",
99 "the maximum difference between if branches that we tolerate for promotion",
100 ParseVal::Num(1),
101 PassOpt::parse_num,
102 ),
103 PassOpt::new(
104 "compaction",
105 "Whether to perform compaction. True by Default ",
106 ParseVal::Bool(true),
107 PassOpt::parse_bool,
108 ),
109 ]
110 }
111}
112
113impl StaticPromotion {
114 fn remove_large_promotables(&self, c: &mut ir::Control) {
118 if let Some(pr) = c.get_attribute(ir::NumAttr::Promotable) {
119 if !self.within_cycle_limit(pr) {
120 c.get_mut_attributes().remove(ir::NumAttr::Promotable)
121 }
122 }
123 }
124
125 fn within_cycle_limit(&self, latency: u64) -> bool {
126 if self.cycle_limit.is_none() {
127 return true;
128 }
129 latency < self.cycle_limit.unwrap()
130 }
131
132 fn within_if_diff_limit(&self, diff: u64) -> bool {
133 if self.if_diff_limit.is_none() {
134 return true;
135 }
136 diff <= self.if_diff_limit.unwrap()
137 }
138
139 fn fits_heuristics(&self, c: &ir::Control) -> bool {
140 let approx_size = Self::approx_size(c);
141 let latency = PromotionAnalysis::get_inferred_latency(c);
142 self.within_cycle_limit(latency) && approx_size > self.threshold
143 }
144
145 fn approx_size_static(sc: &ir::StaticControl, promoted: bool) -> u64 {
146 if !(sc.get_attributes().has(ir::BoolAttr::Promoted) || promoted) {
147 return APPROX_ENABLE_SIZE;
148 }
149 match sc {
150 ir::StaticControl::Empty(_) => 0,
151 ir::StaticControl::Enable(_) | ir::StaticControl::Invoke(_) => {
152 APPROX_ENABLE_SIZE
153 }
154 ir::StaticControl::Repeat(ir::StaticRepeat { body, .. }) => {
155 Self::approx_size_static(body, true) + APPROX_WHILE_REPEAT_SIZE
156 }
157 ir::StaticControl::If(ir::StaticIf {
158 tbranch, fbranch, ..
159 }) => {
160 Self::approx_size_static(tbranch, true)
161 + Self::approx_size_static(fbranch, true)
162 + APPROX_IF_SIZE
163 }
164 ir::StaticControl::Par(ir::StaticPar { stmts, .. })
165 | ir::StaticControl::Seq(ir::StaticSeq { stmts, .. }) => stmts
166 .iter()
167 .map(|stmt| Self::approx_size_static(stmt, true))
168 .sum(),
169 }
170 }
171
172 fn approx_size(c: &ir::Control) -> u64 {
175 match c {
176 ir::Control::Empty(_) => 0,
177 ir::Control::Enable(_) | ir::Control::Invoke(_) => {
178 APPROX_ENABLE_SIZE
179 }
180 ir::Control::Seq(ir::Seq { stmts, .. })
181 | ir::Control::Par(ir::Par { stmts, .. }) => {
182 stmts.iter().map(Self::approx_size).sum()
183 }
184 ir::Control::Repeat(ir::Repeat { body, .. })
185 | ir::Control::While(ir::While { body, .. }) => {
186 Self::approx_size(body) + APPROX_WHILE_REPEAT_SIZE
187 }
188 ir::Control::If(ir::If {
189 tbranch, fbranch, ..
190 }) => {
191 Self::approx_size(tbranch)
192 + Self::approx_size(fbranch)
193 + APPROX_IF_SIZE
194 }
195 ir::Control::Static(sc) => Self::approx_size_static(sc, false),
196 ir::Control::FSMEnable(_) => {
197 todo!("should not encounter fsm nodes")
198 }
199 }
200 }
201
202 fn approx_control_vec_size(v: &[ir::Control]) -> u64 {
205 v.iter().map(Self::approx_size).sum()
206 }
207
208 fn promote_seq_heuristic(
209 &mut self,
210 builder: &mut ir::Builder,
211 mut control_vec: Vec<ir::Control>,
212 ) -> Vec<ir::Control> {
213 if control_vec.is_empty() {
214 vec![]
216 } else if control_vec.len() == 1 {
217 let mut stmt = control_vec.pop().unwrap();
220 if self.fits_heuristics(&stmt) {
221 vec![ir::Control::Static(
222 self.promotion_analysis
223 .convert_to_static(&mut stmt, builder),
224 )]
225 } else {
226 vec![stmt]
227 }
228 } else {
229 let mut possibly_compacted_ctrl = if self.compaction {
230 self.compaction_analysis.compact_control_vec(
232 control_vec,
233 &mut self.promotion_analysis,
234 builder,
235 )
236 } else {
237 control_vec
239 };
240 if possibly_compacted_ctrl.len() == 1 {
245 return possibly_compacted_ctrl;
246 }
247 if Self::approx_control_vec_size(&possibly_compacted_ctrl)
250 <= self.threshold
251 {
252 return possibly_compacted_ctrl;
254 } else if !self.within_cycle_limit(
255 possibly_compacted_ctrl
256 .iter()
257 .map(PromotionAnalysis::get_inferred_latency)
258 .sum(),
259 ) {
260 let right = possibly_compacted_ctrl
262 .split_off(possibly_compacted_ctrl.len() / 2);
263 let mut left_res = self
264 .promote_seq_heuristic(builder, possibly_compacted_ctrl);
265 let right_res = self.promote_seq_heuristic(builder, right);
266 left_res.extend(right_res);
267 return left_res;
268 }
269 let s_seq_stmts = self
271 .promotion_analysis
272 .convert_vec_to_static(builder, possibly_compacted_ctrl);
273 let latency = s_seq_stmts.iter().map(|sc| sc.get_latency()).sum();
274 let sseq = ir::Control::Static(ir::StaticControl::seq(
275 s_seq_stmts,
276 latency,
277 ));
278 vec![sseq]
279 }
280 }
281
282 fn promote_vec_par_heuristic(
288 &mut self,
289 builder: &mut ir::Builder,
290 mut control_vec: Vec<ir::Control>,
291 ) -> Vec<ir::Control> {
292 if control_vec.is_empty() {
293 return vec![];
295 } else if control_vec.len() == 1 {
296 return vec![control_vec.pop().unwrap()];
297 } else if Self::approx_control_vec_size(&control_vec) <= self.threshold
298 {
299 return control_vec;
301 } else if !self.within_cycle_limit(
302 control_vec
303 .iter()
304 .map(PromotionAnalysis::get_inferred_latency)
305 .max()
306 .unwrap_or_else(|| unreachable!("Empty Par Block")),
307 ) {
308 let (index, _) = control_vec
311 .iter()
312 .enumerate()
313 .max_by_key(|&(_, c)| Self::approx_size(c))
314 .unwrap();
315 let largest_thread = control_vec.remove(index);
317 let mut left = self.promote_vec_par_heuristic(builder, control_vec);
318 left.push(largest_thread);
319 return left;
320 }
321 let s_par_stmts = self
323 .promotion_analysis
324 .convert_vec_to_static(builder, control_vec);
325 let latency = s_par_stmts
326 .iter()
327 .map(|sc| sc.get_latency())
328 .max()
329 .unwrap_or_else(|| unreachable!("empty par block"));
330 let spar =
331 ir::Control::Static(ir::StaticControl::par(s_par_stmts, latency));
332 vec![spar]
333 }
334}
335
336impl Visitor for StaticPromotion {
337 fn iteration_order() -> Order {
340 Order::Post
341 }
342
343 fn finish(
344 &mut self,
345 comp: &mut ir::Component,
346 _lib: &LibrarySignatures,
347 _comps: &[ir::Component],
348 ) -> VisResult {
349 if comp.name != "main" {
350 let comp_sig = comp.signature.borrow();
351 if comp.control.borrow().is_static() {
352 if !comp.is_static() {
354 comp.attributes.insert(ir::BoolAttr::Promoted, 1);
357 }
358 let new_latency = NonZeroU64::new(
360 comp.control.borrow().get_latency().unwrap(),
361 )
362 .unwrap();
363 comp.latency = Some(new_latency);
365 self.inference_analysis
367 .adjust_component((comp.name, new_latency.into()));
368 } else if !comp.control.borrow().is_empty() {
369 self.inference_analysis.remove_component(comp.name);
375 };
376
377 let go_ports =
378 comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec();
379 for go_port in go_ports {
383 go_port
384 .borrow_mut()
385 .attributes
386 .remove(ir::NumAttr::Promotable);
387 }
388 }
389 InferenceAnalysis::remove_promotable_attribute(
393 &mut comp.control.borrow_mut(),
394 );
395 Ok(Action::Continue)
396 }
397
398 fn start(
399 &mut self,
400 comp: &mut ir::Component,
401 _sigs: &LibrarySignatures,
402 _comps: &[ir::Component],
403 ) -> VisResult {
404 self.inference_analysis.fixup_timing(comp);
407 self.compaction_analysis.update_cont_read_writes(comp);
409 Ok(Action::Continue)
410 }
411
412 fn enable(
413 &mut self,
414 s: &mut ir::Enable,
415 comp: &mut ir::Component,
416 sigs: &LibrarySignatures,
417 _comps: &[ir::Component],
418 ) -> VisResult {
419 let mut builder = ir::Builder::new(comp, sigs);
420 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
421 if self.within_cycle_limit(latency)
424 && (APPROX_ENABLE_SIZE > self.threshold)
425 {
426 return Ok(Action::change(ir::Control::Static(
427 self.promotion_analysis
428 .convert_enable_to_static(s, &mut builder),
429 )));
430 }
431 }
432 Ok(Action::Continue)
433 }
434
435 fn invoke(
436 &mut self,
437 s: &mut ir::Invoke,
438 _comp: &mut ir::Component,
439 _sigs: &LibrarySignatures,
440 _comps: &[ir::Component],
441 ) -> VisResult {
442 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
443 if self.within_cycle_limit(latency)
445 && (APPROX_ENABLE_SIZE > self.threshold)
446 {
447 return Ok(Action::change(ir::Control::Static(
448 self.promotion_analysis.convert_invoke_to_static(s),
449 )));
450 }
451 }
452 Ok(Action::Continue)
453 }
454
455 fn finish_seq(
456 &mut self,
457 s: &mut ir::Seq,
458 comp: &mut ir::Component,
459 sigs: &LibrarySignatures,
460 _comps: &[ir::Component],
461 ) -> VisResult {
462 self.inference_analysis.fixup_seq(s);
463 s.stmts
466 .iter_mut()
467 .for_each(|c| self.remove_large_promotables(c));
468
469 let mut builder = ir::Builder::new(comp, sigs);
470 let old_stmts = std::mem::take(&mut s.stmts);
471 let mut new_stmts: Vec<ir::Control> = Vec::new();
472 let mut cur_vec: Vec<ir::Control> = Vec::new();
473 for stmt in old_stmts {
474 if PromotionAnalysis::can_be_promoted(&stmt) {
475 cur_vec.push(stmt);
476 } else {
477 let possibly_promoted_stmts =
479 self.promote_seq_heuristic(&mut builder, cur_vec);
480 new_stmts.extend(possibly_promoted_stmts);
481 new_stmts.push(stmt);
483 cur_vec = Vec::new();
485 }
486 }
487 new_stmts.extend(self.promote_seq_heuristic(&mut builder, cur_vec));
488 let mut new_ctrl = if new_stmts.len() == 1 {
489 new_stmts.pop().unwrap()
490 } else {
491 ir::Control::Seq(ir::Seq {
492 stmts: new_stmts,
493 attributes: ir::Attributes::default(),
494 })
495 };
496 new_ctrl
497 .get_mut_attributes()
498 .copy_from_set(&s.attributes, vec![SetAttr::Pos]);
499 self.inference_analysis.fixup_ctrl(&mut new_ctrl);
500
501 if s.get_attributes().has(BoolAttr::Fast) {
503 new_ctrl.get_mut_attributes().insert(BoolAttr::Fast, 1);
504 }
505
506 Ok(Action::change(new_ctrl))
507 }
508
509 fn finish_par(
510 &mut self,
511 s: &mut ir::Par,
512 comp: &mut ir::Component,
513 sigs: &LibrarySignatures,
514 _comps: &[ir::Component],
515 ) -> VisResult {
516 self.inference_analysis.fixup_par(s);
517
518 let mut builder = ir::Builder::new(comp, sigs);
519 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
521 let approx_size: u64 = s.stmts.iter().map(Self::approx_size).sum();
522 if approx_size <= self.threshold {
523 return Ok(Action::Continue);
525 } else if self.within_cycle_limit(latency) {
526 let spar = ir::Control::Static(ir::StaticControl::par(
528 self.promotion_analysis.convert_vec_to_static(
529 &mut builder,
530 std::mem::take(&mut s.stmts),
531 ),
532 latency,
533 ));
534 return Ok(Action::change(spar));
535 }
536 }
537 let mut new_stmts: Vec<ir::Control> = Vec::new();
538 let (s_stmts, d_stmts): (Vec<ir::Control>, Vec<ir::Control>) = s
548 .stmts
549 .drain(..)
550 .partition(PromotionAnalysis::can_be_promoted);
551 new_stmts.extend(self.promote_vec_par_heuristic(&mut builder, s_stmts));
552 new_stmts.extend(d_stmts);
553 let mut new_par = ir::Control::Par(ir::Par {
554 stmts: new_stmts,
555 attributes: ir::Attributes::default(),
556 });
557 new_par
559 .get_mut_attributes()
560 .copy_from_set(&s.attributes, vec![SetAttr::Pos]);
561 Ok(Action::change(new_par))
562 }
563
564 fn finish_if(
565 &mut self,
566 s: &mut ir::If,
567 comp: &mut ir::Component,
568 sigs: &LibrarySignatures,
569 _comps: &[ir::Component],
570 ) -> VisResult {
571 self.inference_analysis.fixup_if(s);
572 let mut builder = ir::Builder::new(comp, sigs);
573 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
574 let approx_size_if = Self::approx_size(&s.tbranch)
575 + Self::approx_size(&s.fbranch)
576 + APPROX_IF_SIZE;
577 let branch_diff = PromotionAnalysis::get_inferred_latency(
578 &s.tbranch,
579 )
580 .abs_diff(PromotionAnalysis::get_inferred_latency(&s.fbranch));
581 if approx_size_if > self.threshold
582 && self.within_cycle_limit(latency)
583 && self.within_if_diff_limit(branch_diff)
584 {
585 let static_tbranch = self
587 .promotion_analysis
588 .convert_to_static(&mut s.tbranch, &mut builder);
589 let static_fbranch = self
590 .promotion_analysis
591 .convert_to_static(&mut s.fbranch, &mut builder);
592 return Ok(Action::change(ir::Control::Static(
593 ir::StaticControl::static_if(
594 Rc::clone(&s.port),
595 Box::new(static_tbranch),
596 Box::new(static_fbranch),
597 latency,
598 ),
599 )));
600 }
601 if !(self.within_cycle_limit(latency)) {
607 s.attributes.remove(ir::NumAttr::Promotable);
608 }
609 }
610 Ok(Action::Continue)
611 }
612
613 fn finish_while(
615 &mut self,
616 s: &mut ir::While,
617 comp: &mut ir::Component,
618 sigs: &LibrarySignatures,
619 _comps: &[ir::Component],
620 ) -> VisResult {
621 self.inference_analysis.fixup_while(s);
622
623 let mut builder = ir::Builder::new(comp, sigs);
624 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
626 let approx_size =
627 Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE;
628 if approx_size > self.threshold && self.within_cycle_limit(latency)
630 {
631 let sc = self
633 .promotion_analysis
634 .convert_to_static(&mut s.body, &mut builder);
635 let static_repeat = ir::StaticControl::repeat(
636 s.attributes.get(ir::NumAttr::Bound).unwrap_or_else(|| {
637 unreachable!(
638 "Unbounded loop has has @promotable attribute"
639 )
640 }),
641 latency,
642 Box::new(sc),
643 );
644 return Ok(Action::Change(Box::new(ir::Control::Static(
645 static_repeat,
646 ))));
647 }
648 if !(self.within_cycle_limit(latency)) {
654 s.attributes.remove(ir::NumAttr::Promotable);
655 }
656 }
657 Ok(Action::Continue)
658 }
659
660 fn finish_repeat(
662 &mut self,
663 s: &mut ir::Repeat,
664 comp: &mut ir::Component,
665 sigs: &LibrarySignatures,
666 _comps: &[ir::Component],
667 ) -> VisResult {
668 self.inference_analysis.fixup_repeat(s);
669
670 let mut builder = ir::Builder::new(comp, sigs);
671 if let Some(latency) = s.attributes.get(ir::NumAttr::Promotable) {
672 let approx_size =
673 Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE;
674 if approx_size > self.threshold && self.within_cycle_limit(latency)
675 {
676 let sc = self
678 .promotion_analysis
679 .convert_to_static(&mut s.body, &mut builder);
680 let static_repeat = ir::StaticControl::repeat(
681 s.num_repeats,
682 latency,
683 Box::new(sc),
684 );
685 return Ok(Action::Change(Box::new(ir::Control::Static(
686 static_repeat,
687 ))));
688 }
689 if !(self.within_cycle_limit(latency)) {
695 s.attributes.remove(ir::NumAttr::Promotable);
696 }
697 }
698 Ok(Action::Continue)
699 }
700}