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

fix!: Only decrement the counter of an array if its address has not changed #7297

Merged
merged 21 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
70 changes: 58 additions & 12 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::ssa::ir::{
value::{Value, ValueId},
};
use acvm::acir::brillig::{MemoryAddress, ValueOrArray};
use acvm::brillig_vm::MEMORY_ADDRESSING_BIT_SIZE;
use acvm::{acir::AcirField, FieldElement};
use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use iter_extended::vecmap;
Expand Down Expand Up @@ -835,20 +836,65 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> {
.store_instruction(array_or_vector.extract_register(), rc_register);
self.brillig_context.deallocate_register(rc_register);
}
Instruction::DecrementRc { value } => {
Instruction::DecrementRc { value, original } => {
let array_or_vector = self.convert_ssa_value(*value, dfg);
let rc_register = self.brillig_context.allocate_register();
let orig_array_or_vector = self.convert_ssa_value(*original, dfg);

self.brillig_context
.load_instruction(rc_register, array_or_vector.extract_register());
self.brillig_context.codegen_usize_op_in_place(
rc_register,
BrilligBinaryOp::Sub,
1,
);
self.brillig_context
.store_instruction(array_or_vector.extract_register(), rc_register);
self.brillig_context.deallocate_register(rc_register);
let array_register = array_or_vector.extract_register();
let orig_array_register = orig_array_or_vector.extract_register();

let codegen_dec_rc = |ctx: &mut BrilligContext<FieldElement, Registers>| {
let rc_register = ctx.allocate_register();

ctx.load_instruction(rc_register, array_register);

ctx.codegen_usize_op_in_place(rc_register, BrilligBinaryOp::Sub, 1);

// Check that the refcount didn't go below 1. If we allow it to underflow
// and become 0 or usize::MAX, and then return to 1, then it will indicate
// an array as mutable when it probably shouldn't be.
{
let min_addr = ReservedRegisters::usize_one();
let condition = SingleAddrVariable::new(ctx.allocate_register(), 1);
ctx.memory_op_instruction(
min_addr,
rc_register,
condition.address,
BrilligBinaryOp::LessThanEquals,
);
ctx.codegen_constrain(
condition,
Some("array ref-count went to zero".to_owned()),
);
ctx.deallocate_single_addr(condition);
}

ctx.store_instruction(array_register, rc_register);
ctx.deallocate_register(rc_register);
};

if array_register == orig_array_register {
codegen_dec_rc(self.brillig_context);
} else {
// Check if the current and original register point at the same memory.
// If they don't, it means that an array-copy procedure has created a
// new array, which includes decrementing the RC of the original,
// which means we don't have to do the decrement here.
let condition =
SingleAddrVariable::new(self.brillig_context.allocate_register(), 1);
// We will use a binary op to interpret the pointer as a number.
let bit_size: u32 = MEMORY_ADDRESSING_BIT_SIZE.into();
let array_var = SingleAddrVariable::new(array_register, bit_size);
let orig_array_var = SingleAddrVariable::new(orig_array_register, bit_size);
self.brillig_context.binary_instruction(
array_var,
orig_array_var,
condition,
BrilligBinaryOp::Equals,
);
self.brillig_context.codegen_if(condition.address, codegen_dec_rc);
self.brillig_context.deallocate_single_addr(condition);
}
}
Instruction::EnableSideEffectsIf { .. } => {
unreachable!("enable_side_effects not supported by brillig")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ mod test {
v5 = add v4, Field 2
return
}

brillig(inline) fn br f1 {
b0(v0: Field, v1: Field):
v2 = add v0, v1
Expand Down Expand Up @@ -1021,8 +1021,8 @@ mod test {
v19 = call f1(v5) -> u32
v20 = add v8, v19
constrain v6 == v20
dec_rc v4
dec_rc v5
dec_rc v4 v4
dec_rc v5 v5
return
}

Expand Down Expand Up @@ -1352,7 +1352,7 @@ mod test {
constrain v32 == v35
return v30, v31, v32
}

brillig(inline) fn foo f2 {
b0(v0: Field):
return Field 4, u8 2, v0
Expand Down
78 changes: 44 additions & 34 deletions compiler/noirc_evaluator/src/ssa/function_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ impl FunctionBuilder {

/// Insert an instruction to decrement an array's reference count. This only has an effect
/// in unconstrained code where arrays are reference counted and copy on write.
pub(crate) fn insert_dec_rc(&mut self, value: ValueId) {
self.insert_instruction(Instruction::DecrementRc { value }, None);
pub(crate) fn insert_dec_rc(&mut self, value: ValueId, original: ValueId) {
self.insert_instruction(Instruction::DecrementRc { value, original }, None);
}

/// Insert an enable_side_effects_if instruction. These are normally only automatically
Expand Down Expand Up @@ -491,53 +491,63 @@ impl FunctionBuilder {
/// within the given value. If the given value is not an array and does not contain
/// any arrays, this does nothing.
///
/// Returns whether a reference count instruction was issued.
pub(crate) fn increment_array_reference_count(&mut self, value: ValueId) -> bool {
self.update_array_reference_count(value, true)
/// Returns the ID of the array that was affected, if any.
pub(crate) fn increment_array_reference_count(&mut self, value: ValueId) -> Option<ValueId> {
self.update_array_reference_count(value, None)
}

/// Insert instructions to decrement the reference count of any array(s) stored
/// within the given value. If the given value is not an array and does not contain
/// any arrays, this does nothing.
///
/// Returns whether a reference count instruction was issued.
pub(crate) fn decrement_array_reference_count(&mut self, value: ValueId) -> bool {
self.update_array_reference_count(value, false)
/// Returns the ID of the array that was affected, if any.
pub(crate) fn decrement_array_reference_count(
&mut self,
value: ValueId,
original: ValueId,
) -> Option<ValueId> {
self.update_array_reference_count(value, Some(original))
}

/// Increment or decrement the given value's reference count if it is an array.
/// If it is not an array, this does nothing. Note that inc_rc and dec_rc instructions
/// are ignored outside of unconstrained code.
///
/// Returns whether a reference count instruction was issued.
fn update_array_reference_count(&mut self, value: ValueId, increment: bool) -> bool {
if self.current_function.runtime().is_brillig() {
match self.type_of_value(value) {
Type::Numeric(_) => false,
Type::Function => false,
Type::Reference(element) => {
if element.contains_an_array() {
let reference = value;
let value = self.insert_load(reference, element.as_ref().clone());
self.update_array_reference_count(value, increment);
true
} else {
false
}
/// If there is an `original` value it indicates that we're now decrementing it,
/// but that should only happen if it hasn't been changed by an `array_set` in
/// the meantime, which in itself would make sure its RC is decremented.
///
/// Returns the ID of the array that was affected, if any.
fn update_array_reference_count(
&mut self,
value: ValueId,
original: Option<ValueId>,
) -> Option<ValueId> {
if self.current_function.runtime().is_acir() {
return None;
}
match self.type_of_value(value) {
Type::Numeric(_) | Type::Function => None,
Type::Reference(element) => {
if element.contains_an_array() {
let reference = value;
let value = self.insert_load(reference, element.as_ref().clone());
self.update_array_reference_count(value, original);
Some(value)
} else {
None
}
Type::Array(..) | Type::Slice(..) => {
// If there are nested arrays or slices, we wait until ArrayGet
// is issued to increment the count of that array.
if increment {
self.insert_inc_rc(value);
} else {
self.insert_dec_rc(value);
}
true
}
Type::Array(..) | Type::Slice(..) => {
// If there are nested arrays or slices, we wait until ArrayGet
// is issued to increment the count of that array.
if let Some(original) = original {
self.insert_dec_rc(value, original);
} else {
self.insert_inc_rc(value);
}
Some(value)
}
} else {
false
}
}

Expand Down
21 changes: 15 additions & 6 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,9 @@
/// This currently only has an effect in Brillig code where array sharing and copy on write is
/// implemented via reference counting. In ACIR code this is done with im::Vector and these
/// DecrementRc instructions are ignored.
DecrementRc { value: ValueId },
///
/// The `original` contains the value of the array which was incremented by the pair of this decrement.
DecrementRc { value: ValueId, original: ValueId },

/// Merge two values returned from opposite branches of a conditional into one.
///
Expand Down Expand Up @@ -423,7 +425,7 @@
// These can fail.
Constrain(..) | ConstrainNotEqual(..) | RangeCheck { .. } => true,

// This should never be side-effectful

Check warning on line 428 in compiler/noirc_evaluator/src/ssa/ir/instruction.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (effectful)
MakeArray { .. } | Noop => false,

// Some binary math can overflow or underflow
Expand Down Expand Up @@ -717,7 +719,9 @@
mutable: *mutable,
},
Instruction::IncrementRc { value } => Instruction::IncrementRc { value: f(*value) },
Instruction::DecrementRc { value } => Instruction::DecrementRc { value: f(*value) },
Instruction::DecrementRc { value, original } => {
Instruction::DecrementRc { value: f(*value), original: f(*original) }
}
Instruction::RangeCheck { value, max_bit_size, assert_message } => {
Instruction::RangeCheck {
value: f(*value),
Expand Down Expand Up @@ -788,7 +792,10 @@
*value = f(*value);
}
Instruction::IncrementRc { value } => *value = f(*value),
Instruction::DecrementRc { value } => *value = f(*value),
Instruction::DecrementRc { value, original } => {
*value = f(*value);
*original = f(*original);
}
Instruction::RangeCheck { value, max_bit_size: _, assert_message: _ } => {
*value = f(*value);
}
Expand Down Expand Up @@ -854,10 +861,12 @@
Instruction::EnableSideEffectsIf { condition } => {
f(*condition);
}
Instruction::IncrementRc { value }
| Instruction::DecrementRc { value }
| Instruction::RangeCheck { value, .. } => {
Instruction::IncrementRc { value } | Instruction::RangeCheck { value, .. } => {
f(*value);
}
Instruction::DecrementRc { value, original } => {
f(*value);
f(*original);
}
Instruction::IfElse { then_condition, then_value, else_condition, else_value } => {
f(*then_condition);
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_evaluator/src/ssa/ir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ fn display_instruction_inner(
Instruction::IncrementRc { value } => {
writeln!(f, "inc_rc {}", show(*value))
}
Instruction::DecrementRc { value } => {
writeln!(f, "dec_rc {}", show(*value))
Instruction::DecrementRc { value, original } => {
writeln!(f, "dec_rc {} {}", show(*value), show(*original))
}
Instruction::RangeCheck { value, max_bit_size, .. } => {
writeln!(f, "range_check {} to {} bits", show(*value), *max_bit_size,)
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,7 @@ mod test {
v2 = array_get v0, index u32 0 -> Field
v4 = array_get v0, index u32 1 -> Field
v5 = add v2, v4
dec_rc v0
dec_rc v0 v0
return v5
}
";
Expand Down
12 changes: 6 additions & 6 deletions compiler/noirc_evaluator/src/ssa/opt/die.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ impl Context {
self.rc_instructions.iter().fold(HashMap::default(), |mut acc, (rc, block)| {
let value = match &dfg[*rc] {
Instruction::IncrementRc { value } => *value,
Instruction::DecrementRc { value } => *value,
Instruction::DecrementRc { value, .. } => *value,
other => {
unreachable!(
"Expected IncrementRc or DecrementRc instruction, found {other:?}"
Expand Down Expand Up @@ -434,7 +434,7 @@ impl Context {
dfg: &DataFlowGraph,
) -> bool {
use Instruction::*;
if let IncrementRc { value } | DecrementRc { value } = instruction {
if let IncrementRc { value } | DecrementRc { value, .. } = instruction {
let Some(instruction) = dfg.get_local_or_global_instruction(*value) else {
return false;
};
Expand Down Expand Up @@ -685,7 +685,7 @@ impl<'a> RcTracker<'a> {
// Remember that this array was RC'd by this instruction.
self.inc_rcs.entry(*value).or_default().insert(instruction_id);
}
Instruction::DecrementRc { value } => {
Instruction::DecrementRc { value, .. } => {
let typ = function.dfg.type_of_value(*value);

// We assume arrays aren't mutated until we find an array_set
Expand Down Expand Up @@ -835,7 +835,7 @@ mod test {
b0(v0: [Field; 2]):
inc_rc v0
v2 = array_get v0, index u32 0 -> Field
dec_rc v0
dec_rc v0 v0
return v2
}
";
Expand All @@ -859,7 +859,7 @@ mod test {
b0(v0: [Field; 2]):
inc_rc v0
v2 = array_set v0, index u32 0, value u32 0
dec_rc v0
dec_rc v0 v0
return v2
}
";
Expand Down Expand Up @@ -967,7 +967,7 @@ mod test {
v3 = load v0 -> [Field; 3]
v6 = array_set v3, index u32 0, value Field 5
store v6 at v0
dec_rc v6
dec_rc v6 v1
return
}
";
Expand Down
8 changes: 4 additions & 4 deletions compiler/noirc_evaluator/src/ssa/opt/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl Context {
let mut to_remove = HashSet::default();

for instruction in function.dfg[last_block].instructions() {
if let Instruction::DecrementRc { value } = &function.dfg[*instruction] {
if let Instruction::DecrementRc { value, .. } = &function.dfg[*instruction] {
if let Some(inc_rc) = pop_rc_for(*value, function, &mut self.inc_rcs) {
if !inc_rc.possibly_mutated {
to_remove.insert(inc_rc.id);
Expand Down Expand Up @@ -213,7 +213,7 @@ mod test {

builder.insert_inc_rc(v0);
builder.insert_inc_rc(v0);
builder.insert_dec_rc(v0);
builder.insert_dec_rc(v0, v0);

let outer_array_type = Type::Array(Arc::new(vec![inner_array_type]), 1);
let v1 = builder.insert_make_array(vec![v0].into(), outer_array_type);
Expand Down Expand Up @@ -261,7 +261,7 @@ mod test {
let v7 = builder.insert_array_set(v2, zero, five);

builder.insert_store(v1, v7);
builder.insert_dec_rc(v0);
builder.insert_dec_rc(v0, v0);
builder.terminate_with_return(vec![]);

let ssa = builder.finish().remove_paired_rc();
Expand Down Expand Up @@ -314,7 +314,7 @@ mod test {

builder.insert_store(v0, v7);
let v8 = builder.insert_load(v0, array_type);
builder.insert_dec_rc(v8);
builder.insert_dec_rc(v8, v1);
builder.insert_store(v0, v8);
builder.terminate_with_return(vec![]);

Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_evaluator/src/ssa/opt/unrolling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@
use super::{is_new_size_ok, BoilerplateStats, Loops};

/// Tries to unroll all loops in each SSA function once, calling the `Function` directly,
/// bypassing the iterative loop done by the SSA which does further optimisations.

Check warning on line 1029 in compiler/noirc_evaluator/src/ssa/opt/unrolling.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (optimisations)
///
/// If any loop cannot be unrolled, it is left as-is or in a partially unrolled state.
fn try_unroll_loops(mut ssa: Ssa) -> (Ssa, Vec<RuntimeError>) {
Expand Down Expand Up @@ -1273,7 +1273,7 @@
jmp b1()
b1():
v20 = load v3 -> [u64; 6]
dec_rc v0
dec_rc v0 v0
return v20
}
";
Expand Down Expand Up @@ -1449,7 +1449,7 @@
jmp b1(v16)
b2():
v8 = load v4 -> [u64; 6]
dec_rc v0
dec_rc v0 v0
return v8
}}
"
Expand Down
Loading
Loading