chore: array_set_window_optimization#11773
Conversation
The DFG simplifier was rewriting predicate-dependent ArraySet instructions into predicate-independent MakeArray when the base array and index were constants. This erased the EnableSideEffectsIf guard that ACIR relies on, making conditional writes unconditional and allowing incorrect witness values to satisfy constraints. Remove the ArraySet→MakeArray constant fold since the simplifier has no access to the current side-effects predicate and cannot determine whether the fold is safe.
…_set NOIR-17 was a false positive — the simplifier already correctly refuses to optimize array_get through a same-index predicated array_set when it lacks predicate context (side_effects = None). Add a test to ensure this behavior is preserved.
Brillig functions do not use side-effects predicates, so the ArraySet-to-MakeArray simplification is safe there. Guard the optimization with a runtime check instead of disabling it entirely.
…dedicated pass Move the constant-array ArraySet folding out of the simplifier (which lacks predicate context) into the array_set_optimization pass. Extract mutable_array_set as a separate module and fix test expectations.
Co-authored-by: Akosh Farkash <aakoshh@gmail.com>
|
I looked at why the tests time out. The cargo run -q -p nargo_cli -- test -Zenums test_identity --benchmark-codegen --exactOn while on this branch: I'm guessing that the way this pass replaces 1 array set with N-1 array_get and a make_array makes a lot of work for mem2reg. The arrays in this test have ~1000 elements. The |
compiler/noirc_evaluator/src/ssa/opt/array_set_window_optimization.rs
Outdated
Show resolved
Hide resolved
|
I tried it this way and then the test stays fast, but at the same time all self.simple_optimization(|context| {
let inst_id = context.instruction_id;
if !candidates.contains(&inst_id) {
return;
}
let Instruction::ArraySet { index, value, .. } = *context.instruction() else {
unreachable!("candidate must be an ArraySet instruction");
};
let Some(const_index) =
context.dfg.get_numeric_constant(index).and_then(|v| v.try_to_u32())
else {
unreachable!("candidate ArraySet index must be a constant u32");
};
// Only handle cases where we know the elements at compile time. Otherwise we would have to insert `array_get`
// for every other element to re-pack them as a a `make_array`, which could blow up the SSA, making subsequent
// passes work exponentially harder.
let Some((mut elements, array_type)) = context.dfg.get_array_constant(value) else {
return;
};
// Only replace safe index.
if elements.len() <= const_index as usize {
return;
}
// Remove the array_set; we will emit replacement instructions instead.
context.remove_current_instruction();
elements[const_index as usize] = value;
let make_array = Instruction::MakeArray { elements, typ: array_type.clone() };
let new_result = context.insert_instruction(make_array, Some(vec![array_type])).first();
let [old_result] = context.dfg.instruction_result(inst_id);
context.replace_value(old_result, new_result);
}); |
|
@aakoshh Thank you for investigating this! I changed the optimization to run on arrays up to 64 elements. Maybe with 16 it would work just as well... maybe it's only needed for this poseidon case, not sure, in which case I think we'd only need to look at arrays of 4 elements? We could even only apply this optimization on chains of array_set + poseidon but I don't know if it would be too specific then and for other programs it would regress. Let's see if the circuits are still optimized and CI passes now... |
|
That seems to have worked. |
compiler/noirc_evaluator/src/ssa/opt/array_set_window_optimization.rs
Outdated
Show resolved
Hide resolved
aakoshh
left a comment
There was a problem hiding this comment.
Nice, the benchmarks show that it does rein back the 3% opcode increase incurred on the rollup contracts in aztec-packages 🎉
…tion.rs Co-authored-by: Akosh Farkash <aakoshh@gmail.com>
Description
Problem
Trying out a new optimization on top of #11659 to see if it solves the regression.
Summary
If this works (it removes the regression) I'll open a separate PR with just this new SSA pass so we could merge it before #11659.
Additional Context
I wrote this with the help of Claude. I reviewed the initial code but then I kept adding requirements so I need to review the final code, but I wanted to push this to see if it fixes the regression: it seems it doesn't. At one point I think it did but then I kept refining the logic so maybe then it regressed again as it can't be optimized as much as I thought.
User Documentation
Check one:
PR Checklist
cargo fmton default settings.