Skip to content

Commit f991f44

Browse files
committed
Add method to decide if a loop is small
1 parent 53720e6 commit f991f44

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

compiler/noirc_evaluator/src/ssa/opt/unrolling.rs

+54
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,29 @@ use crate::{
4141
};
4242
use fxhash::FxHashMap as HashMap;
4343

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+
4467
impl Ssa {
4568
/// Loop unrolling can return errors, since ACIR functions need to be fully unrolled.
4669
/// 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 {
565588
})
566589
.sum()
567590
}
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+
}
568612
}
569613

570614
/// Return the induction value of the current iteration of the loop, from the given block's jmp arguments.
@@ -960,6 +1004,16 @@ mod tests {
9601004
assert_eq!(all, 7);
9611005
}
9621006

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+
9631017
/// Simple test loop:
9641018
/// ```text
9651019
/// unconstrained fn main(sum: u32) {

0 commit comments

Comments
 (0)