Skip to content
Open
Show file tree
Hide file tree
Changes from all 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: 1 addition & 2 deletions compiler/noirc_evaluator/src/ssa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,7 @@ pub fn primary_passes(options: &SsaEvaluatorOptions) -> Vec<SsaPass<'_>> {
SsaPass::new(Ssa::array_set_optimization, "ArraySet optimization"),
SsaPass::new(Ssa::array_get_optimization, "ArrayGet optimization"),
SsaPass::new(Ssa::load_store_forwarding, "Load Store Forwarding"),
SsaPass::new(Ssa::dead_instruction_elimination, "Dead Instruction Elimination"),
SsaPass::new(Ssa::mem2reg, "Mem2Reg"),
SsaPass::new(Ssa::dead_instruction_elimination_and_mem2reg, "DIE and Mem2Reg"),
SsaPass::new(Ssa::brillig_entry_point_analysis, "Brillig Entry Point Analysis")
// Remove any potentially unnecessary duplication from the Brillig entry point analysis.
.and_then(Ssa::remove_unreachable_functions),
Expand Down
58 changes: 58 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! When a variable is stored in multiple blocks, this pass places block parameters only at the
//! iterated dominance frontier (IDF) of those definition sites. This is the minimal set of
//! blocks where values from different control-flow paths could merge, following the standard
//! algorithm from Cytron et al. (1991). For variables stored in a single block, the dominance

Check warning on line 8 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Cytron)

Check warning on line 8 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Cytron)
//! frontier is often empty, meaning no block parameters are needed at all — the value simply
//! propagates to all loads.
//!
Expand Down Expand Up @@ -43,6 +43,39 @@
self
}

/// Alternate DIE and mem2reg until no `allocate` remains or a round removes none.
///
/// Mem2reg cannot promote a reference held in a `make_array`, even an unused one, and only
/// DIE removes unused arrays. Each round therefore unwraps one level of a nested aggregate
/// of references such as `&[&[&x]]`.
#[tracing::instrument(level = "trace", skip_all)]
pub(crate) fn dead_instruction_elimination_and_mem2reg(mut self) -> Ssa {
let count_allocates = |ssa: &Ssa| -> usize {
ssa.functions
.values()
.flat_map(|function| {
function.reachable_blocks().into_iter().map(move |b| (function, b))
})
.map(|(function, block)| {
function.dfg[block]
.instructions()
.iter()
.filter(|id| matches!(function.dfg[**id], Instruction::Allocate))
.count()
})
.sum()
};
let mut allocates = count_allocates(&self);
loop {
self = self.dead_instruction_elimination().mem2reg();
let remaining = count_allocates(&self);
if remaining == 0 || remaining == allocates {
return self;
}
allocates = remaining;
}
}

/// Run mem2reg only on Brillig functions.
#[tracing::instrument(level = "trace", skip_all)]
pub(crate) fn mem2reg_brillig(mut self) -> Ssa {
Expand Down Expand Up @@ -121,7 +154,7 @@
// A variable declared in block D is visible at block B iff D dominates B.
// Instead of checking dominates() for each (variable, block) pair — O(blocks × variables) —
// we inherit the visible set from the immediate dominator: O(blocks) tree walk.
// This completes the Cytron-style SSA construction (the IDF placement above is phase 1;

Check warning on line 157 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Cytron)

Check warning on line 157 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Cytron)
// this visibility propagation replaces the per-variable dominance checks in phase 2).
let visible_vars = compute_visible_vars(&blocks, &variables, &dom_tree);

Expand Down Expand Up @@ -227,19 +260,19 @@
blocks: &[BasicBlockId],
variables: &BTreeMap<ValueId, BasicBlockId>,
dom_tree: &DominatorTree,
) -> HashMap<BasicBlockId, imbl::OrdMap<ValueId, BasicBlockId>> {

Check warning on line 263 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)

Check warning on line 263 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)
// Group variables by their declaration block
let mut vars_by_decl_block: HashMap<BasicBlockId, Vec<ValueId>> = HashMap::default();
for (var, decl_block) in variables {
vars_by_decl_block.entry(*decl_block).or_default().push(*var);
}

let mut visible: HashMap<BasicBlockId, imbl::OrdMap<ValueId, BasicBlockId>> =

Check warning on line 270 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)

Check warning on line 270 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)
HashMap::default();
for &block in blocks {
let mut vars = match dom_tree.immediate_dominator(block) {
Some(idom) => visible[&idom].clone(),
None => imbl::OrdMap::new(),

Check warning on line 275 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)

Check warning on line 275 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)
};
if let Some(declared_here) = vars_by_decl_block.get(&block) {
for var in declared_here {
Expand All @@ -257,7 +290,7 @@
/// For all other blocks, the entry value is inherited from the predecessor's exit state.
fn add_block_params_and_find_exit_states(
blocks: &[BasicBlockId],
visible_vars: &HashMap<BasicBlockId, imbl::OrdMap<ValueId, BasicBlockId>>,

Check warning on line 293 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)

Check warning on line 293 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)
param_locations: &ParamLocations,
inserter: &mut FunctionInserter,
block_states: &mut BlockStates,
Expand Down Expand Up @@ -287,7 +320,7 @@
/// - If this block is in the variable's IDF: add a fresh block parameter
/// - Otherwise: inherit the value from a visited predecessor's exit state
fn compute_entry_state(
visible_vars: &imbl::OrdMap<ValueId, BasicBlockId>,

Check warning on line 323 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)

Check warning on line 323 in compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (imbl)
param_locations: &ParamLocations,
block: BasicBlockId,
dfg: &mut DataFlowGraph,
Expand Down Expand Up @@ -1561,6 +1594,31 @@
");
}

#[test]
fn die_and_mem2reg_promotes_nested_unused_references() {
let src = "
acir(inline) pure fn main f0 {
b0():
v0 = allocate -> &mut Field
store Field 1 at v0
v1 = make_array [v0] : [&mut Field; 1]
v2 = allocate -> &mut [&mut Field; 1]
store v1 at v2
v3 = make_array [v2] : [&mut [&mut Field; 1]; 1]
return
}
";

let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.dead_instruction_elimination_and_mem2reg();
assert_ssa_snapshot!(ssa, @r"
acir(inline) pure fn main f0 {
b0():
return
}
");
}

#[test]
fn regression_11482() {
// An allocate with no stores (an orphan, as earlier passes can leave behind) must be
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "nested_unused_references"
type = "bin"
authors = [""]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Each `&` here is an allocation held inside the array one level up. None of the arrays are
// read, so every allocation must be removed before ACIR generation.
fn main() {
let _nested = &[&[&[&1]]];
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading