Skip to content

Commit

Permalink
fix: Fix assignment when both mut and &mut are used (#2264)
Browse files Browse the repository at this point in the history
* Fix #2255

* Expand doc comment
  • Loading branch information
jfecher authored Aug 11, 2023
1 parent a72daa4 commit b07a7ff
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 11 deletions.
14 changes: 14 additions & 0 deletions crates/nargo_cli/tests/execution_success/references/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fn main(mut x: Field) {
regression_1887();
regression_2054();
regression_2030();
regression_2255();
}

fn add1(x: &mut Field) {
Expand Down Expand Up @@ -97,3 +98,16 @@ fn regression_2030() {
let _ = *array[0];
*array[0] = 1;
}

// The `mut x: &mut ...` caught a bug handling lvalues where a double-dereference would occur internally
// in one step rather than being tracked by two separate steps. This lead to assigning the 1 value to the
// incorrect outer `mut` reference rather than the correct `&mut` reference.
fn regression_2255() {
let x = &mut 0;
regression_2255_helper(x);
assert(*x == 1);
}

fn regression_2255_helper(mut x: &mut Field) {
*x = 1;
}
35 changes: 24 additions & 11 deletions crates/noirc_evaluator/src/ssa/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,14 @@ impl<'a> FunctionContext<'a> {
/// version, as it is only needed for recursion.
pub(super) fn extract_current_value(&mut self, lvalue: &ast::LValue) -> LValue {
match lvalue {
ast::LValue::Ident(ident) => LValue::Ident(self.ident_lvalue(ident)),
ast::LValue::Ident(ident) => {
let (reference, should_auto_deref) = self.ident_lvalue(ident);
if should_auto_deref {
LValue::Dereference { reference }
} else {
LValue::Ident
}
}
ast::LValue::Index { array, index, .. } => self.index_lvalue(array, index).2,
ast::LValue::MemberAccess { object, field_index } => {
let (old_object, object_lvalue) = self.extract_current_value_recursive(object);
Expand All @@ -551,18 +558,19 @@ impl<'a> FunctionContext<'a> {
}
}

pub(super) fn dereference(&mut self, values: &Values, element_type: &ast::Type) -> Values {
fn dereference_lvalue(&mut self, values: &Values, element_type: &ast::Type) -> Values {
let element_types = Self::convert_type(element_type);
values.map_both(element_types, |value, element_type| {
let reference = value.eval(self);
let reference = value.eval_reference();
self.builder.insert_load(reference, element_type).into()
})
}

/// Compile the given identifier as a reference - ie. avoid calling .eval()
fn ident_lvalue(&self, ident: &ast::Ident) -> Values {
/// Compile the given identifier as a reference - ie. avoid calling .eval().
/// Returns the variable's value and whether the variable is mutable.
fn ident_lvalue(&self, ident: &ast::Ident) -> (Values, bool) {
match &ident.definition {
ast::Definition::Local(id) => self.lookup(*id),
ast::Definition::Local(id) => (self.lookup(*id), ident.mutable),
other => panic!("Unexpected definition found for mutable value: {other}"),
}
}
Expand All @@ -585,8 +593,13 @@ impl<'a> FunctionContext<'a> {
fn extract_current_value_recursive(&mut self, lvalue: &ast::LValue) -> (Values, LValue) {
match lvalue {
ast::LValue::Ident(ident) => {
let variable = self.ident_lvalue(ident);
(variable.clone(), LValue::Ident(variable))
let (variable, should_auto_deref) = self.ident_lvalue(ident);
if should_auto_deref {
let dereferenced = self.dereference_lvalue(&variable, &ident.typ);
(dereferenced, LValue::Dereference { reference: variable })
} else {
(variable.clone(), LValue::Ident)
}
}
ast::LValue::Index { array, index, element_type, location } => {
let (old_array, index, index_lvalue) = self.index_lvalue(array, index);
Expand All @@ -601,7 +614,7 @@ impl<'a> FunctionContext<'a> {
}
ast::LValue::Dereference { reference, element_type } => {
let (reference, _) = self.extract_current_value_recursive(reference);
let dereferenced = self.dereference(&reference, element_type);
let dereferenced = self.dereference_lvalue(&reference, element_type);
(dereferenced, LValue::Dereference { reference })
}
}
Expand All @@ -614,7 +627,7 @@ impl<'a> FunctionContext<'a> {
/// `extract_current_value` for more details.
pub(super) fn assign_new_value(&mut self, lvalue: LValue, new_value: Values) {
match lvalue {
LValue::Ident(references) => self.assign(references, new_value),
LValue::Ident => unreachable!("Cannot assign to a variable without a reference"),
LValue::Index { old_array: mut array, index, array_lvalue } => {
let element_size = self.builder.field_constant(self.element_size(array));

Expand Down Expand Up @@ -811,7 +824,7 @@ impl SharedContext {
/// Used to remember the results of each step of extracting a value from an ast::LValue
#[derive(Debug)]
pub(super) enum LValue {
Ident(Values),
Ident,
Index { old_array: ValueId, index: ValueId, array_lvalue: Box<LValue> },
MemberAccess { old_object: Values, index: usize, object_lvalue: Box<LValue> },
Dereference { reference: Values },
Expand Down
8 changes: 8 additions & 0 deletions crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ impl<'a> FunctionContext<'a> {
}
}

fn dereference(&mut self, values: &Values, element_type: &ast::Type) -> Values {
let element_types = Self::convert_type(element_type);
values.map_both(element_types, |value, element_type| {
let reference = value.eval(self);
self.builder.insert_load(reference, element_type).into()
})
}

fn codegen_reference(&mut self, expr: &Expression) -> Values {
match expr {
Expression::Ident(ident) => self.codegen_ident_reference(ident),
Expand Down

0 comments on commit b07a7ff

Please sign in to comment.