@@ -41,6 +41,29 @@ use crate::{
41
41
} ;
42
42
use fxhash:: FxHashMap as HashMap ;
43
43
44
+ /// Number of instructions which are complete loop boilerplate,
45
+ /// the ones facilitating jumps and the increment of the loop
46
+ /// variable.
47
+ ///
48
+ /// All the instructions in the following example are boilerplate:
49
+ /// ```text
50
+ /// brillig(inline) fn main f0 {
51
+ /// b0(v0: u32):
52
+ /// ...
53
+ /// jmp b1(u32 0)
54
+ /// b1(v1: u32):
55
+ /// v5 = lt v1, u32 4
56
+ /// jmpif v5 then: b3, else: b2
57
+ /// b3():
58
+ /// ...
59
+ /// v11 = add v1, u32 1
60
+ /// jmp b1(v11)
61
+ /// b2():
62
+ /// ...
63
+ /// }
64
+ /// ```
65
+ const LOOP_BOILERPLATE_COUNT : usize = 5 ;
66
+
44
67
impl Ssa {
45
68
/// Loop unrolling can return errors, since ACIR functions need to be fully unrolled.
46
69
/// This meta-pass will keep trying to unroll loops and simplifying the SSA until no more errors are found.
@@ -565,6 +588,27 @@ impl Loop {
565
588
} )
566
589
. sum ( )
567
590
}
591
+
592
+ /// Decide if this loop is small enough that it can be inlined in a way that the number
593
+ /// of unrolled instructions times the number of iterations would result in smaller bytecode
594
+ /// than if we keep the loops with their overheads.
595
+ fn is_small_loop ( & self , function : & Function , cfg : & ControlFlowGraph ) -> bool {
596
+ let Ok ( Some ( ( lower, upper) ) ) = self . get_const_bounds ( function, cfg) else {
597
+ return false ;
598
+ } ;
599
+ let Some ( lower) = lower. try_to_u64 ( ) else {
600
+ return false ;
601
+ } ;
602
+ let Some ( upper) = upper. try_to_u64 ( ) else {
603
+ return false ;
604
+ } ;
605
+ let num_iterations = ( upper - lower) as usize ;
606
+ let refs = self . find_pre_header_reference_values ( function, cfg) ;
607
+ let ( loads, stores) = self . count_loads_and_stores ( function, & refs) ;
608
+ let all_instructions = self . count_all_instructions ( function) ;
609
+ let useful_instructions = all_instructions - loads - stores - LOOP_BOILERPLATE_COUNT ;
610
+ useful_instructions * num_iterations < all_instructions
611
+ }
568
612
}
569
613
570
614
/// Return the induction value of the current iteration of the loop, from the given block's jmp arguments.
@@ -960,6 +1004,16 @@ mod tests {
960
1004
assert_eq ! ( all, 7 ) ;
961
1005
}
962
1006
1007
+ #[ test]
1008
+ fn test_is_small_loop ( ) {
1009
+ let ssa = brillig_unroll_test_case ( ) ;
1010
+ let function = ssa. main ( ) ;
1011
+ let mut loops = Loops :: find_all ( function) ;
1012
+ let loop0 = loops. yet_to_unroll . pop ( ) . unwrap ( ) ;
1013
+
1014
+ assert ! ( loop0. is_small_loop( function, & loops. cfg) ) ;
1015
+ }
1016
+
963
1017
/// Simple test loop:
964
1018
/// ```text
965
1019
/// unconstrained fn main(sum: u32) {
0 commit comments