Skip to content

Commit

Permalink
Deduplicate to common ancestor
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher committed Nov 14, 2024
1 parent 852c87a commit c73cf2c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 15 deletions.
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ir/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl DominatorTree {
///
/// This function panics if either of the blocks are unreachable.
///
/// An instruction is considered to dominate itself.
/// A block is considered to dominate itself.
pub(crate) fn dominates(&mut self, block_a_id: BasicBlockId, block_b_id: BasicBlockId) -> bool {
if let Some(res) = self.cache.get(&(block_a_id, block_b_id)) {
return *res;
Expand Down
105 changes: 91 additions & 14 deletions compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ type InstructionResultCache = HashMap<Instruction, HashMap<Option<ValueId>, Resu
/// For more information see [`InstructionResultCache`].
#[derive(Default)]
struct ResultCache {
results: Vec<(BasicBlockId, Vec<ValueId>)>,
result: Option<(BasicBlockId, Vec<ValueId>)>,
}

impl Context {
Expand Down Expand Up @@ -150,7 +150,7 @@ impl Context {
fn fold_constants_into_instruction(
&mut self,
dfg: &mut DataFlowGraph,
block: BasicBlockId,
mut block: BasicBlockId,
id: InstructionId,
side_effects_enabled_var: &mut ValueId,
) {
Expand All @@ -159,11 +159,22 @@ impl Context {
let old_results = dfg.instruction_results(id).to_vec();

// If a copy of this instruction exists earlier in the block, then reuse the previous results.
if let Some(cached_results) =
if let Some(cache_result) =
self.get_cached(dfg, &instruction, *side_effects_enabled_var, block)
{
Self::replace_result_ids(dfg, &old_results, cached_results);
return;
match cache_result {
CacheResult::Cached(cached) => {
Self::replace_result_ids(dfg, &old_results, cached);
return;
}
CacheResult::NeedToHoistToCommonBlock(dominator, _cached) => {
// Just change the block to insert in the common dominator instead.
// This will only move the current instance of the instruction right now.
// When constant folding is run a second time later on, it'll catch
// that the previous instance can be deduplicated to this instance.
block = dominator;
}
}
}

// Otherwise, try inserting the instruction again to apply any optimizations using the newly resolved inputs.
Expand Down Expand Up @@ -317,13 +328,13 @@ impl Context {
}
}

fn get_cached<'a>(
&'a mut self,
fn get_cached(
&mut self,
dfg: &DataFlowGraph,
instruction: &Instruction,
side_effects_enabled_var: ValueId,
block: BasicBlockId,
) -> Option<&'a [ValueId]> {
) -> Option<CacheResult> {
let results_for_instruction = self.cached_instruction_results.get(instruction)?;

let predicate = self.use_constraint_info && instruction.requires_acir_gen_predicate(dfg);
Expand All @@ -336,7 +347,9 @@ impl Context {
impl ResultCache {
/// Records that an `Instruction` in block `block` produced the result values `results`.
fn cache(&mut self, block: BasicBlockId, results: Vec<ValueId>) {
self.results.push((block, results));
if self.result.is_none() {
self.result = Some((block, results));
}
}

/// Returns a set of [`ValueId`]s produced from a copy of this [`Instruction`] which sits
Expand All @@ -345,16 +358,24 @@ impl ResultCache {
/// We require that the cached instruction's block dominates `block` in order to avoid
/// cycles causing issues (e.g. two instructions being replaced with the results of each other
/// such that neither instruction exists anymore.)
fn get(&self, block: BasicBlockId, dom: &mut DominatorTree) -> Option<&[ValueId]> {
for (origin_block, results) in &self.results {
fn get(&self, block: BasicBlockId, dom: &mut DominatorTree) -> Option<CacheResult> {
self.result.as_ref().map(|(origin_block, results)| {
if dom.dominates(*origin_block, block) {
return Some(results);
CacheResult::Cached(results)
} else {
// Insert a copy of this instruction in the common dominator
let dominator = dom.common_dominator(*origin_block, block);
CacheResult::NeedToHoistToCommonBlock(dominator, results)
}
}
None
})
}
}

enum CacheResult<'a> {
Cached(&'a [ValueId]),
NeedToHoistToCommonBlock(BasicBlockId, &'a [ValueId]),
}

#[cfg(test)]
mod test {
use std::sync::Arc;
Expand Down Expand Up @@ -759,4 +780,60 @@ mod test {
assert_eq!(main.dfg[main.entry_block()].instructions().len(), 1);
assert_eq!(main.dfg[b1].instructions().len(), 0);
}

#[test]
fn deduplicate_across_non_dominated_blocks() {
let src = "
brillig(inline) fn main f0 {
b0(v0: u32):
v2 = lt u32 1000, v0
jmpif v2 then: b1, else: b2
b1():
v4 = add v0, u32 1
v5 = lt v0, v4
constrain v5 == u1 1
jmp b2()
b2():
v7 = lt u32 1000, v0
jmpif v7 then: b3, else: b4
b3():
v8 = add v0, u32 1
v9 = lt v0, v8
constrain v9 == u1 1
jmp b4()
b4():
return
}
";
let ssa = Ssa::from_str(src).unwrap();

// v4 has been hoisted, although:
// - v5 has not yet been removed since it was encountered earlier in the program
// - v8 hasn't been recognized as a duplicate of v6 yet since they still reference v4 and
// v5 respectively
let expected = "
brillig(inline) fn main f0 {
b0(v0: u32):
v2 = lt u32 1000, v0
v4 = add v0, u32 1
jmpif v2 then: b1, else: b2
b1():
v5 = add v0, u32 1
v6 = lt v0, v5
constrain v6 == u1 1
jmp b2()
b2():
jmpif v2 then: b3, else: b4
b3():
v8 = lt v0, v4
constrain v8 == u1 1
jmp b4()
b4():
return
}
";

let ssa = ssa.fold_constants_using_constraints();
assert_normalized_ssa_equals(ssa, expected);
}
}

0 comments on commit c73cf2c

Please sign in to comment.