Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssa): Hoist MakeArray instructions during loop invariant code motion #6782

Merged
merged 11 commits into from
Dec 12, 2024
116 changes: 116 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! We also check that we are not hoisting instructions with side effects.
use acvm::{acir::AcirField, FieldElement};
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use iter_extended::vecmap;

use crate::ssa::{
ir::{
Expand Down Expand Up @@ -113,6 +114,28 @@ impl<'f> LoopInvariantContext<'f> {

if hoist_invariant {
self.inserter.push_instruction(instruction_id, pre_header);

// We track whether we may mutate MakeArray instructions before we deduplicate
// them but we still need to issue an extra inc_rc in case they're mutated afterward.
jfecher marked this conversation as resolved.
Show resolved Hide resolved
if matches!(
self.inserter.function.dfg[instruction_id],
Instruction::MakeArray { .. }
) {
let results =
self.inserter.function.dfg.instruction_results(instruction_id).to_vec();
let results = vecmap(results, |value| self.inserter.resolve(value));
assert_eq!(
results.len(),
1,
"ICE: We expect only a single result from an `Instruction::MakeArray`"
);
jfecher marked this conversation as resolved.
Show resolved Hide resolved
let inc_rc = Instruction::IncrementRc { value: results[0] };
let call_stack = self.inserter.function.dfg.get_call_stack(instruction_id);
self.inserter
.function
.dfg
.insert_instruction_and_results(inc_rc, *block, None, call_stack);
}
} else {
self.inserter.push_instruction(instruction_id, *block);
}
Expand Down Expand Up @@ -190,6 +213,7 @@ impl<'f> LoopInvariantContext<'f> {
});

let can_be_deduplicated = instruction.can_be_deduplicated(self.inserter.function, false)
|| matches!(instruction, Instruction::MakeArray { .. })
|| self.can_be_deduplicated_from_upper_bound(&instruction);

is_loop_invariant && can_be_deduplicated
Expand Down Expand Up @@ -559,4 +583,96 @@ mod test {
let ssa = ssa.loop_invariant_code_motion();
assert_normalized_ssa_equals(ssa, expected);
}

#[test]
fn insert_inc_rc_when_moving_make_array() {
// SSA for the following program:
//
// unconstrained fn main(x: u32, y: u32) {
// let mut a1 = [1, 2, 3, 4, 5];
// a1[x] = 64;
// for i in 0 .. 5 {
// let mut a2 = [1, 2, 3, 4, 5];
// a2[y + i] = 128;
// foo(a2);
// }
// foo(a1);
// }
//
// We want to make sure move a loop invariant make_array instruction,
// to account for whether that array has been marked as mutable.
// To do so, we increment the reference counter on the array we are moving.
// In the SSA below, we want to move `v42` out of the loop.
let src = "
brillig(inline) fn main f0 {
b0(v0: u32, v1: u32):
v8 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5]
v9 = allocate -> &mut [Field; 5]
v11 = array_set v8, index v0, value Field 64
v13 = add v0, u32 1
store v11 at v9
jmp b1(u32 0)
b1(v2: u32):
v16 = lt v2, u32 5
jmpif v16 then: b3, else: b2
b2():
v17 = load v9 -> [Field; 5]
call f1(v17)
return
b3():
v19 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5]
v20 = allocate -> &mut [Field; 5]
v21 = add v1, v2
v23 = array_set v19, index v21, value Field 128
call f1(v23)
v25 = add v2, u32 1
jmp b1(v25)
}

brillig(inline) fn foo f1 {
b0(v0: [Field; 5]):
return
}
";

let ssa = Ssa::from_str(src).unwrap();

// We expect the `make_array` at the top of `b3` to be replaced with an `inc_rc`
// of the newly hoisted `make_array` at the end of `b0`.
let expected = "
brillig(inline) fn main f0 {
b0(v0: u32, v1: u32):
v32 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5]
v33 = allocate -> &mut [Field; 5]
v34 = array_set v32, index v0, value Field 64
v35 = add v0, u32 1
store v34 at v33
v36 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5]
jmp b1(u32 0)
b1(v2: u32):
v37 = lt v2, u32 5
jmpif v37 then: b3, else: b2
b2():
v38 = load v33 -> [Field; 5]
call f1(v38)
return
b3():
inc_rc v36
v39 = allocate -> &mut [Field; 5]
v40 = add v1, v2
v41 = array_set v36, index v40, value Field 128
call f1(v41)
v42 = add v2, u32 1
jmp b1(v42)
}

brillig(inline) fn foo f1 {
b0(v0: [Field; 5]):
return
}
";

let ssa = ssa.loop_invariant_code_motion();
assert_normalized_ssa_equals(ssa, expected);
}
}
2 changes: 0 additions & 2 deletions test_programs/gates_report_brillig_execution.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ excluded_dirs=(
"double_verify_nested_proof"
"overlapping_dep_and_mod"
"comptime_println"
# Takes a very long time to execute as large loops do not get simplified.
"regression_4709"
jfecher marked this conversation as resolved.
Show resolved Hide resolved
# bit sizes for bigint operation doesn't match up.
"bigint"
# Expected to fail as test asserts on which runtime it is in.
Expand Down
4 changes: 1 addition & 3 deletions tooling/nargo_cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@

/// Some tests are explicitly ignored in brillig due to them failing.
/// These should be fixed and removed from this list.
const IGNORED_BRILLIG_TESTS: [&str; 11] = [
// Takes a very long time to execute as large loops do not get simplified.
"regression_4709",
const IGNORED_BRILLIG_TESTS: [&str; 10] = [
// bit sizes for bigint operation doesn't match up.
"bigint",
// ICE due to looking for function which doesn't exist.
Expand Down Expand Up @@ -182,7 +180,7 @@
// We need to isolate test cases in the same group, otherwise they overwrite each other's artifacts.
// On CI we use `cargo nextest`, which runs tests in different processes; for this we use a file lock.
// Locally we might be using `cargo test`, which run tests in the same process; in this case the file lock
// wouldn't work, becuase the process itself has the lock, and it looks like it can have N instances without

Check warning on line 183 in tooling/nargo_cli/build.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (becuase)
// any problems; for this reason we also use a `Mutex`.
let mutex_name = format! {"TEST_MUTEX_{}", test_name.to_uppercase()};
write!(
Expand Down
Loading