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): Loop invariant code motion #6563

Merged
merged 18 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub(crate) fn optimize_into_acir(
"After `static_assert` and `assert_constant`:",
)?
.try_run_pass(Ssa::unroll_loops_iteratively, "After Unrolling:")?
// We unroll small Brillig loops based off a comparison between the number of unrolled instructions
// and the loop overhead. Loop invariant code motion can affect this calculation so we run it after unrolling.
.run_pass(Ssa::loop_invariant_code_motion, "After Loop Invariant Code Motion:")
vezenovm marked this conversation as resolved.
Show resolved Hide resolved
.run_pass(Ssa::simplify_cfg, "After Simplifying (2nd):")
.run_pass(Ssa::flatten_cfg, "After Flattening:")
.run_pass(Ssa::remove_bit_shifts, "After Removing Bit Shifts:")
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub(crate) struct FunctionInserter<'f> {
///
/// This is optional since caching arrays relies on the inserter inserting strictly
/// in control-flow order. Otherwise, if arrays later in the program are cached first,
/// they may be refered to by instructions earlier in the program.
/// they may be referred to by instructions earlier in the program.
array_cache: Option<ArrayCache>,

/// If this pass is loop unrolling, store the block before the loop to optionally
Expand Down
87 changes: 43 additions & 44 deletions compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl Context {
{
match cache_result {
CacheResult::Cached(cached) => {
Self::replace_result_ids(dfg, &old_results, cached);
replace_result_ids(dfg, &old_results, cached);
return;
}
CacheResult::NeedToHoistToCommonBlock(dominator, _cached) => {
Expand All @@ -178,9 +178,9 @@ impl Context {
}

// Otherwise, try inserting the instruction again to apply any optimizations using the newly resolved inputs.
let new_results = Self::push_instruction(id, instruction.clone(), &old_results, block, dfg);
let new_results = push_instruction(id, instruction.clone(), &old_results, block, dfg);

Self::replace_result_ids(dfg, &old_results, &new_results);
replace_result_ids(dfg, &old_results, &new_results);

self.cache_instruction(
instruction.clone(),
Expand Down Expand Up @@ -227,36 +227,6 @@ impl Context {
.map_values(|value_id| resolve_cache(dfg, constraint_simplification_mapping, value_id))
}

/// Pushes a new [`Instruction`] into the [`DataFlowGraph`] which applies any optimizations
/// based on newly resolved values for its inputs.
///
/// This may result in the [`Instruction`] being optimized away or replaced with a constant value.
fn push_instruction(
id: InstructionId,
instruction: Instruction,
old_results: &[ValueId],
block: BasicBlockId,
dfg: &mut DataFlowGraph,
) -> Vec<ValueId> {
let ctrl_typevars = instruction
.requires_ctrl_typevars()
.then(|| vecmap(old_results, |result| dfg.type_of_value(*result)));

let call_stack = dfg.get_call_stack(id);
let new_results =
match dfg.insert_instruction_and_results(instruction, block, ctrl_typevars, call_stack)
{
InsertInstructionResult::SimplifiedTo(new_result) => vec![new_result],
InsertInstructionResult::SimplifiedToMultiple(new_results) => new_results,
InsertInstructionResult::Results(_, new_results) => new_results.to_vec(),
InsertInstructionResult::InstructionRemoved => vec![],
};
// Optimizations while inserting the instruction should not change the number of results.
assert_eq!(old_results.len(), new_results.len());

new_results
}

fn cache_instruction(
&mut self,
instruction: Instruction,
Expand Down Expand Up @@ -317,17 +287,6 @@ impl Context {
self.constraint_simplification_mappings.entry(side_effects_enabled_var).or_default()
}

/// Replaces a set of [`ValueId`]s inside the [`DataFlowGraph`] with another.
fn replace_result_ids(
dfg: &mut DataFlowGraph,
old_results: &[ValueId],
new_results: &[ValueId],
) {
for (old_result, new_result) in old_results.iter().zip(new_results) {
dfg.set_value_from_id(*old_result, *new_result);
}
}

fn get_cached(
&mut self,
dfg: &DataFlowGraph,
Expand All @@ -344,6 +303,46 @@ impl Context {
}
}

/// Pushes a new [`Instruction`] into the [`DataFlowGraph`] which applies any optimizations
/// based on newly resolved values for its inputs.
///
/// This may result in the [`Instruction`] being optimized away or replaced with a constant value.
pub(super) fn push_instruction(
id: InstructionId,
instruction: Instruction,
old_results: &[ValueId],
block: BasicBlockId,
dfg: &mut DataFlowGraph,
) -> Vec<ValueId> {
let ctrl_typevars = instruction
.requires_ctrl_typevars()
.then(|| vecmap(old_results, |result| dfg.type_of_value(*result)));

let call_stack = dfg.get_call_stack(id);
let new_results =
match dfg.insert_instruction_and_results(instruction, block, ctrl_typevars, call_stack) {
InsertInstructionResult::SimplifiedTo(new_result) => vec![new_result],
InsertInstructionResult::SimplifiedToMultiple(new_results) => new_results,
InsertInstructionResult::Results(_, new_results) => new_results.to_vec(),
InsertInstructionResult::InstructionRemoved => vec![],
};
// Optimizations while inserting the instruction should not change the number of results.
assert_eq!(old_results.len(), new_results.len());

new_results
}

/// Replaces a set of [`ValueId`]s inside the [`DataFlowGraph`] with another.
pub(super) fn replace_result_ids(
dfg: &mut DataFlowGraph,
old_results: &[ValueId],
new_results: &[ValueId],
) {
for (old_result, new_result) in old_results.iter().zip(new_results) {
dfg.set_value_from_id(*old_result, *new_result);
}
}

impl ResultCache {
/// Records that an `Instruction` in block `block` produced the result values `results`.
fn cache(&mut self, block: BasicBlockId, results: Vec<ValueId>) {
Expand Down
Loading
Loading