Skip to content

Commit

Permalink
feat: refactor SSA passes to run on individual functions (#6072)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

~Resolves #5839

Refactors passes that only act on individual functions into `impl
Function { fn do_pass() }`

## Summary\*



## Additional Context



## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
michaeljklein authored Oct 1, 2024
1 parent 7077b01 commit 85c502c
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 189 deletions.
49 changes: 28 additions & 21 deletions compiler/noirc_evaluator/src/ssa/opt/array_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::ssa::{
ir::{
basic_block::BasicBlockId,
dfg::DataFlowGraph,
function::Function,
instruction::{Instruction, InstructionId, TerminatorInstruction},
types::Type::{Array, Slice},
value::ValueId,
Expand All @@ -17,32 +18,38 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn array_set_optimization(mut self) -> Self {
for func in self.functions.values_mut() {
let reachable_blocks = func.reachable_blocks();

if !func.runtime().is_entry_point() {
assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization");
}
let mut array_to_last_use = HashMap::default();
let mut instructions_to_update = HashSet::default();
let mut arrays_from_load = HashSet::default();

for block in reachable_blocks.iter() {
analyze_last_uses(
&func.dfg,
*block,
&mut array_to_last_use,
&mut instructions_to_update,
&mut arrays_from_load,
);
}
for block in reachable_blocks {
make_mutable(&mut func.dfg, block, &instructions_to_update);
}
func.array_set_optimization();
}
self
}
}

impl Function {
pub(crate) fn array_set_optimization(&mut self) {
let reachable_blocks = self.reachable_blocks();

if !self.runtime().is_entry_point() {
assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization");
}
let mut array_to_last_use = HashMap::default();
let mut instructions_to_update = HashSet::default();
let mut arrays_from_load = HashSet::default();

for block in reachable_blocks.iter() {
analyze_last_uses(
&self.dfg,
*block,
&mut array_to_last_use,
&mut instructions_to_update,
&mut arrays_from_load,
);
}
for block in reachable_blocks {
make_mutable(&mut self.dfg, block, &instructions_to_update);
}
}
}

/// Builds the set of ArraySet instructions that can be made mutable
/// because their input value is unused elsewhere afterward.
fn analyze_last_uses(
Expand Down
10 changes: 8 additions & 2 deletions compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn as_slice_optimization(mut self) -> Self {
for func in self.functions.values_mut() {
let known_slice_lengths = known_slice_lengths(func);
replace_known_slice_lengths(func, known_slice_lengths);
func.as_slice_optimization();
}
self
}
}

impl Function {
pub(crate) fn as_slice_optimization(&mut self) {
let known_slice_lengths = known_slice_lengths(self);
replace_known_slice_lengths(self, known_slice_lengths);
}
}

fn known_slice_lengths(func: &Function) -> HashMap<InstructionId, usize> {
let mut known_slice_lengths = HashMap::default();
for block_id in func.reachable_blocks() {
Expand Down
33 changes: 21 additions & 12 deletions compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,31 @@ impl Ssa {
mut self,
) -> Result<Ssa, RuntimeError> {
for function in self.functions.values_mut() {
for block in function.reachable_blocks() {
// Unfortunately we can't just use instructions.retain(...) here since
// check_instruction can also return an error
let instructions = function.dfg[block].take_instructions();
let mut filtered_instructions = Vec::with_capacity(instructions.len());
function.evaluate_static_assert_and_assert_constant()?;
}
Ok(self)
}
}

for instruction in instructions {
if check_instruction(function, instruction)? {
filtered_instructions.push(instruction);
}
}
impl Function {
pub(crate) fn evaluate_static_assert_and_assert_constant(
&mut self,
) -> Result<(), RuntimeError> {
for block in self.reachable_blocks() {
// Unfortunately we can't just use instructions.retain(...) here since
// check_instruction can also return an error
let instructions = self.dfg[block].take_instructions();
let mut filtered_instructions = Vec::with_capacity(instructions.len());

*function.dfg[block].instructions_mut() = filtered_instructions;
for instruction in instructions {
if check_instruction(self, instruction)? {
filtered_instructions.push(instruction);
}
}

*self.dfg[block].instructions_mut() = filtered_instructions;
}
Ok(self)
Ok(())
}
}

Expand Down
28 changes: 15 additions & 13 deletions compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn fold_constants(mut self) -> Ssa {
for function in self.functions.values_mut() {
constant_fold(function, false);
function.constant_fold(false);
}
self
}
Expand All @@ -57,25 +57,27 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn fold_constants_using_constraints(mut self) -> Ssa {
for function in self.functions.values_mut() {
constant_fold(function, true);
function.constant_fold(true);
}
self
}
}

/// The structure of this pass is simple:
/// Go through each block and re-insert all instructions.
fn constant_fold(function: &mut Function, use_constraint_info: bool) {
let mut context = Context { use_constraint_info, ..Default::default() };
context.block_queue.push(function.entry_block());
impl Function {
/// The structure of this pass is simple:
/// Go through each block and re-insert all instructions.
pub(crate) fn constant_fold(&mut self, use_constraint_info: bool) {
let mut context = Context { use_constraint_info, ..Default::default() };
context.block_queue.push(self.entry_block());

while let Some(block) = context.block_queue.pop() {
if context.visited_blocks.contains(&block) {
continue;
}
while let Some(block) = context.block_queue.pop() {
if context.visited_blocks.contains(&block) {
continue;
}

context.visited_blocks.insert(block);
context.fold_constants_in_block(function, block);
context.visited_blocks.insert(block);
context.fold_constants_in_block(self, block);
}
}
}

Expand Down
60 changes: 31 additions & 29 deletions compiler/noirc_evaluator/src/ssa/opt/die.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,46 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn dead_instruction_elimination(mut self) -> Ssa {
for function in self.functions.values_mut() {
dead_instruction_elimination(function, true);
function.dead_instruction_elimination(true);
}
self
}
}

/// Removes any unused instructions in the reachable blocks of the given function.
///
/// The blocks of the function are iterated in post order, such that any blocks containing
/// instructions that reference results from an instruction in another block are evaluated first.
/// If we did not iterate blocks in this order we could not safely say whether or not the results
/// of its instructions are needed elsewhere.
fn dead_instruction_elimination(function: &mut Function, insert_out_of_bounds_checks: bool) {
let mut context = Context::default();
for call_data in &function.dfg.data_bus.call_data {
context.mark_used_instruction_results(&function.dfg, call_data.array_id);
}
impl Function {
/// Removes any unused instructions in the reachable blocks of the given function.
///
/// The blocks of the function are iterated in post order, such that any blocks containing
/// instructions that reference results from an instruction in another block are evaluated first.
/// If we did not iterate blocks in this order we could not safely say whether or not the results
/// of its instructions are needed elsewhere.
pub(crate) fn dead_instruction_elimination(&mut self, insert_out_of_bounds_checks: bool) {
let mut context = Context::default();
for call_data in &self.dfg.data_bus.call_data {
context.mark_used_instruction_results(&self.dfg, call_data.array_id);
}

let mut inserted_out_of_bounds_checks = false;
let mut inserted_out_of_bounds_checks = false;

let blocks = PostOrder::with_function(function);
for block in blocks.as_slice() {
inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block(
function,
*block,
insert_out_of_bounds_checks,
);
}
let blocks = PostOrder::with_function(self);
for block in blocks.as_slice() {
inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block(
self,
*block,
insert_out_of_bounds_checks,
);
}

// If we inserted out of bounds check, let's run the pass again with those new
// instructions (we don't want to remove those checks, or instructions that are
// dependencies of those checks)
if inserted_out_of_bounds_checks {
dead_instruction_elimination(function, false);
return;
}
// If we inserted out of bounds check, let's run the pass again with those new
// instructions (we don't want to remove those checks, or instructions that are
// dependencies of those checks)
if inserted_out_of_bounds_checks {
self.dead_instruction_elimination(false);
return;
}

context.remove_rc_instructions(&mut function.dfg);
context.remove_rc_instructions(&mut self.dfg);
}
}

/// Per function context for tracking unused values and which instructions to remove.
Expand Down
14 changes: 10 additions & 4 deletions compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,22 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn mem2reg(mut self) -> Ssa {
for function in self.functions.values_mut() {
let mut context = PerFunctionContext::new(function);
context.mem2reg();
context.remove_instructions();
context.update_data_bus();
function.mem2reg();
}

self
}
}

impl Function {
pub(crate) fn mem2reg(&mut self) {
let mut context = PerFunctionContext::new(self);
context.mem2reg();
context.remove_instructions();
context.update_data_bus();
}
}

struct PerFunctionContext<'f> {
cfg: ControlFlowGraph,
post_order: PostOrder,
Expand Down
40 changes: 21 additions & 19 deletions compiler/noirc_evaluator/src/ssa/opt/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn remove_paired_rc(mut self) -> Ssa {
for function in self.functions.values_mut() {
remove_paired_rc(function);
function.remove_paired_rc();
}
self
}
Expand All @@ -44,26 +44,28 @@ pub(crate) struct RcInstruction {
pub(crate) possibly_mutated: bool,
}

/// This function is very simplistic for now. It takes advantage of the fact that dec_rc
/// instructions are currently issued only at the end of a function for parameters and will
/// only check the first and last block for inc & dec rc instructions to be removed. The rest
/// of the function is still checked for array_set instructions.
///
/// This restriction lets this function largely ignore merging intermediate results from other
/// blocks and handling loops.
fn remove_paired_rc(function: &mut Function) {
// `dec_rc` is only issued for parameters currently so we can speed things
// up a bit by skipping any functions without them.
if !contains_array_parameter(function) {
return;
}
impl Function {
/// This function is very simplistic for now. It takes advantage of the fact that dec_rc
/// instructions are currently issued only at the end of a function for parameters and will
/// only check the first and last block for inc & dec rc instructions to be removed. The rest
/// of the function is still checked for array_set instructions.
///
/// This restriction lets this function largely ignore merging intermediate results from other
/// blocks and handling loops.
pub(crate) fn remove_paired_rc(&mut self) {
// `dec_rc` is only issued for parameters currently so we can speed things
// up a bit by skipping any functions without them.
if !contains_array_parameter(self) {
return;
}

let mut context = Context::default();
let mut context = Context::default();

context.find_rcs_in_entry_block(function);
context.scan_for_array_sets(function);
let to_remove = context.find_rcs_to_remove(function);
remove_instructions(to_remove, function);
context.find_rcs_in_entry_block(self);
context.scan_for_array_sets(self);
let to_remove = context.find_rcs_to_remove(self);
remove_instructions(to_remove, self);
}
}

fn contains_array_parameter(function: &mut Function) -> bool {
Expand Down
28 changes: 17 additions & 11 deletions compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,30 @@ impl Ssa {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn remove_bit_shifts(mut self) -> Ssa {
for function in self.functions.values_mut() {
remove_bit_shifts(function);
function.remove_bit_shifts();
}
self
}
}

/// The structure of this pass is simple:
/// Go through each block and re-insert all instructions.
fn remove_bit_shifts(function: &mut Function) {
if let RuntimeType::Brillig = function.runtime() {
return;
}
impl Function {
/// The structure of this pass is simple:
/// Go through each block and re-insert all instructions.
pub(crate) fn remove_bit_shifts(&mut self) {
if let RuntimeType::Brillig = self.runtime() {
return;
}

let block = function.entry_block();
let mut context =
Context { function, new_instructions: Vec::new(), block, call_stack: CallStack::default() };
let block = self.entry_block();
let mut context = Context {
function: self,
new_instructions: Vec::new(),
block,
call_stack: CallStack::default(),
};

context.remove_bit_shifts();
context.remove_bit_shifts();
}
}

struct Context<'f> {
Expand Down
Loading

0 comments on commit 85c502c

Please sign in to comment.