Skip to content

Commit b2653c0

Browse files
committed
fix(ssa): Track all local allocations during flattening (noir-lang/noir#6619)
feat(comptime): Implement blackbox functions in comptime interpreter (noir-lang/noir#6551) chore: derive PartialEq and Hash for FieldElement (noir-lang/noir#6610) chore: ignore almost-empty directories in nargo_cli tests (noir-lang/noir#6611) chore: remove temporary allocations from `num_bits` (noir-lang/noir#6600) chore: Release Noir(1.0.0-beta.0) (noir-lang/noir#6562) feat: Add `array_refcount` and `slice_refcount` builtins for debugging (noir-lang/noir#6584) chore!: Require types of globals to be specified (noir-lang/noir#6592) fix: don't report visibility errors when elaborating comptime value (noir-lang/noir#6498) fix: preserve newlines between comments when formatting statements (noir-lang/noir#6601) fix: parse a bit more SSA stuff (noir-lang/noir#6599) chore!: remove eddsa from stdlib (noir-lang/noir#6591) chore: Typo in oracles how to (noir-lang/noir#6598) feat(ssa): Loop invariant code motion (noir-lang/noir#6563) fix: remove `compiler_version` from new `Nargo.toml` (noir-lang/noir#6590) feat: Avoid incrementing reference counts in some cases (noir-lang/noir#6568) chore: fix typo in test name (noir-lang/noir#6589) fix: consider prereleases to be compatible with pre-1.0.0 releases (noir-lang/noir#6580) feat: try to inline brillig calls with all constant arguments (noir-lang/noir#6548) fix: correct type when simplifying `derive_pedersen_generators` (noir-lang/noir#6579) feat: Sync from aztec-packages (noir-lang/noir#6576)
2 parents 7a776ac + 22163b6 commit b2653c0

File tree

3 files changed

+66
-89
lines changed

3 files changed

+66
-89
lines changed

.noir-sync-commit

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
10a9f8104e1ebb8fad044927ff130a0e2ce9131b
1+
649117570b95b26776150e337c458d478eb48c2e

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs

+27-14
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
//! v11 = mul v4, Field 12
132132
//! v12 = add v10, v11
133133
//! store v12 at v5 (new store)
134-
use fxhash::FxHashMap as HashMap;
134+
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
135135

136136
use acvm::{acir::AcirField, acir::BlackBoxFunc, FieldElement};
137137
use iter_extended::vecmap;
@@ -201,6 +201,15 @@ struct Context<'f> {
201201
/// When processing a block, we pop this stack to get its arguments
202202
/// and at the end we push the arguments for his successor
203203
arguments_stack: Vec<Vec<ValueId>>,
204+
205+
/// Stores all allocations local to the current branch.
206+
///
207+
/// Since these branches are local to the current branch (i.e. only defined within one branch of
208+
/// an if expression), they should not be merged with their previous value or stored value in
209+
/// the other branch since there is no such value.
210+
///
211+
/// The `ValueId` here is that which is returned by the allocate instruction.
212+
local_allocations: HashSet<ValueId>,
204213
}
205214

206215
#[derive(Clone)]
@@ -211,6 +220,8 @@ struct ConditionalBranch {
211220
old_condition: ValueId,
212221
// The condition of the branch
213222
condition: ValueId,
223+
// The allocations accumulated when processing the branch
224+
local_allocations: HashSet<ValueId>,
214225
}
215226

216227
struct ConditionalContext {
@@ -243,6 +254,7 @@ fn flatten_function_cfg(function: &mut Function, no_predicates: &HashMap<Functio
243254
slice_sizes: HashMap::default(),
244255
condition_stack: Vec::new(),
245256
arguments_stack: Vec::new(),
257+
local_allocations: HashSet::default(),
246258
};
247259
context.flatten(no_predicates);
248260
}
@@ -317,7 +329,6 @@ impl<'f> Context<'f> {
317329
// If this is not a separate variable, clippy gets confused and says the to_vec is
318330
// unnecessary, when removing it actually causes an aliasing/mutability error.
319331
let instructions = self.inserter.function.dfg[block].instructions().to_vec();
320-
let mut previous_allocate_result = None;
321332

322333
for instruction in instructions.iter() {
323334
if self.is_no_predicate(no_predicates, instruction) {
@@ -405,10 +416,12 @@ impl<'f> Context<'f> {
405416
let old_condition = *condition;
406417
let then_condition = self.inserter.resolve(old_condition);
407418

419+
let old_allocations = std::mem::take(&mut self.local_allocations);
408420
let branch = ConditionalBranch {
409421
old_condition,
410422
condition: self.link_condition(then_condition),
411423
last_block: *then_destination,
424+
local_allocations: old_allocations,
412425
};
413426
let cond_context = ConditionalContext {
414427
condition: then_condition,
@@ -435,10 +448,12 @@ impl<'f> Context<'f> {
435448
);
436449
let else_condition = self.link_condition(else_condition);
437450

451+
let old_allocations = std::mem::take(&mut self.local_allocations);
438452
let else_branch = ConditionalBranch {
439453
old_condition: cond_context.then_branch.old_condition,
440454
condition: else_condition,
441455
last_block: *block,
456+
local_allocations: old_allocations,
442457
};
443458
cond_context.else_branch = Some(else_branch);
444459
self.condition_stack.push(cond_context);
@@ -461,6 +476,7 @@ impl<'f> Context<'f> {
461476
}
462477

463478
let mut else_branch = cond_context.else_branch.unwrap();
479+
self.local_allocations = std::mem::take(&mut else_branch.local_allocations);
464480
else_branch.last_block = *block;
465481
cond_context.else_branch = Some(else_branch);
466482

@@ -593,22 +609,19 @@ impl<'f> Context<'f> {
593609
/// `previous_allocate_result` should only be set to the result of an allocate instruction
594610
/// if that instruction was the instruction immediately previous to this one - if there are
595611
/// any instructions in between it should be None.
596-
fn push_instruction(
597-
&mut self,
598-
id: InstructionId,
599-
previous_allocate_result: &mut Option<ValueId>,
600-
) {
612+
fn push_instruction(&mut self, id: InstructionId) {
601613
let (instruction, call_stack) = self.inserter.map_instruction(id);
602-
let instruction = self.handle_instruction_side_effects(
603-
instruction,
604-
call_stack.clone(),
605-
*previous_allocate_result,
606-
);
614+
let instruction = self.handle_instruction_side_effects(instruction, call_stack.clone());
607615

608616
let instruction_is_allocate = matches!(&instruction, Instruction::Allocate);
609617
let entry = self.inserter.function.entry_block();
610618
let results = self.inserter.push_instruction_value(instruction, id, entry, call_stack);
611-
*previous_allocate_result = instruction_is_allocate.then(|| results.first());
619+
620+
// Remember an allocate was created local to this branch so that we do not try to merge store
621+
// values across branches for it later.
622+
if instruction_is_allocate {
623+
self.local_allocations.insert(results.first());
624+
}
612625
}
613626

614627
/// If we are currently in a branch, we need to modify constrain instructions
@@ -652,7 +665,7 @@ impl<'f> Context<'f> {
652665
Instruction::Store { address, value } => {
653666
// If this instruction immediately follows an allocate, and stores to that
654667
// address there is no previous value to load and we don't need a merge anyway.
655-
if Some(address) == previous_allocate_result {
668+
if self.local_allocations.contains(&address) {
656669
Instruction::Store { address, value }
657670
} else {
658671
// Instead of storing `value`, store `if condition { value } else { previous_value }`

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs

+38-74
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,8 @@ mod tests {
614614
map::Id,
615615
types::Type,
616616
},
617+
opt::assert_normalized_ssa_equals,
618+
Ssa,
617619
};
618620

619621
#[test]
@@ -824,91 +826,53 @@ mod tests {
824826
// is later stored in a successor block
825827
#[test]
826828
fn load_aliases_in_predecessor_block() {
827-
// fn main {
828-
// b0():
829-
// v0 = allocate
830-
// store Field 0 at v0
831-
// v2 = allocate
832-
// store v0 at v2
833-
// v3 = load v2
834-
// v4 = load v2
835-
// jmp b1()
836-
// b1():
837-
// store Field 1 at v3
838-
// store Field 2 at v4
839-
// v7 = load v3
840-
// v8 = eq v7, Field 2
841-
// return
842-
// }
843-
let main_id = Id::test_new(0);
844-
let mut builder = FunctionBuilder::new("main".into(), main_id);
845-
846-
let v0 = builder.insert_allocate(Type::field());
847-
848-
let zero = builder.field_constant(0u128);
849-
builder.insert_store(v0, zero);
850-
851-
let v2 = builder.insert_allocate(Type::Reference(Arc::new(Type::field())));
852-
builder.insert_store(v2, v0);
853-
854-
let v3 = builder.insert_load(v2, Type::field());
855-
let v4 = builder.insert_load(v2, Type::field());
856-
let b1 = builder.insert_block();
857-
builder.terminate_with_jmp(b1, vec![]);
858-
859-
builder.switch_to_block(b1);
860-
861-
let one = builder.field_constant(1u128);
862-
builder.insert_store(v3, one);
863-
864-
let two = builder.field_constant(2u128);
865-
builder.insert_store(v4, two);
866-
867-
let v8 = builder.insert_load(v3, Type::field());
868-
let _ = builder.insert_binary(v8, BinaryOp::Eq, two);
869-
870-
builder.terminate_with_return(vec![]);
871-
872-
let ssa = builder.finish();
873-
assert_eq!(ssa.main().reachable_blocks().len(), 2);
829+
let src = "
830+
acir(inline) fn main f0 {
831+
b0():
832+
v0 = allocate -> &mut Field
833+
store Field 0 at v0
834+
v2 = allocate -> &mut &mut Field
835+
store v0 at v2
836+
v3 = load v2 -> &mut Field
837+
v4 = load v2 -> &mut Field
838+
jmp b1()
839+
b1():
840+
store Field 1 at v3
841+
store Field 2 at v4
842+
v7 = load v3 -> Field
843+
v8 = eq v7, Field 2
844+
return
845+
}
846+
";
874847

875-
// Expected result:
876-
// acir fn main f0 {
877-
// b0():
878-
// v9 = allocate
879-
// store Field 0 at v9
880-
// v10 = allocate
881-
// jmp b1()
882-
// b1():
883-
// return
884-
// }
885-
let ssa = ssa.mem2reg();
886-
println!("{}", ssa);
848+
let mut ssa = Ssa::from_str(src).unwrap();
849+
let main = ssa.main_mut();
887850

888-
let main = ssa.main();
889-
assert_eq!(main.reachable_blocks().len(), 2);
851+
let instructions = main.dfg[main.entry_block()].instructions();
852+
assert_eq!(instructions.len(), 6); // The final return is not counted
890853

891854
// All loads should be removed
892-
assert_eq!(count_loads(main.entry_block(), &main.dfg), 0);
893-
assert_eq!(count_loads(b1, &main.dfg), 0);
894-
895855
// The first store is not removed as it is used as a nested reference in another store.
896-
// We would need to track whether the store where `v9` is the store value gets removed to know whether
856+
// We would need to track whether the store where `v0` is the store value gets removed to know whether
897857
// to remove it.
898-
assert_eq!(count_stores(main.entry_block(), &main.dfg), 1);
899-
900858
// The first store in b1 is removed since there is another store to the same reference
901859
// in the same block, and the store is not needed before the later store.
902860
// The rest of the stores are also removed as no loads are done within any blocks
903861
// to the stored values.
904-
//
905-
// NOTE: This store is not removed due to the FIXME when handling Instruction::Store.
906-
assert_eq!(count_stores(b1, &main.dfg), 1);
907-
908-
let b1_instructions = main.dfg[b1].instructions();
862+
let expected = "
863+
acir(inline) fn main f0 {
864+
b0():
865+
v0 = allocate -> &mut Field
866+
store Field 0 at v0
867+
v2 = allocate -> &mut &mut Field
868+
jmp b1()
869+
b1():
870+
return
871+
}
872+
";
909873

910-
// We expect the last eq to be optimized out, only the store from above remains
911-
assert_eq!(b1_instructions.len(), 1);
874+
let ssa = ssa.mem2reg();
875+
assert_normalized_ssa_equals(ssa, expected);
912876
}
913877

914878
#[test]

0 commit comments

Comments
 (0)