Skip to content

Commit

Permalink
feat: Enable dynamic indices on slices (#2446)
Browse files Browse the repository at this point in the history
Co-authored-by: Jake Fecher <[email protected]>
  • Loading branch information
vezenovm and jfecher authored Sep 7, 2023
1 parent 464f878 commit c5c4052
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 122 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ tower = "0.4"
url = "2.2.0"
wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] }
wasm-bindgen-test = "0.3.33"
base64 = "0.21.2"
base64 = "0.21.2"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "slice_dynamic_index"
type = "bin"
authors = [""]
compiler_version = "0.10.3"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "5"
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
fn main(x : Field) {
// The parameters to this function must come directly from witness values (inputs to main).
regression_dynamic_slice_index(x - 1, x - 4);
}

fn regression_dynamic_slice_index(x: Field, y: Field) {
let mut slice = [];
for i in 0..5 {
slice = slice.push_back(i);
}
assert(slice.len() == 5);

dynamic_slice_index_set_if(slice, x, y);
dynamic_slice_index_set_else(slice, x, y);
dynamic_slice_index_set_nested_if_else_else(slice, x, y);
dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1);
dynamic_slice_index_if(slice, x);
dynamic_array_index_if([0, 1, 2, 3, 4], x);
dynamic_slice_index_else(slice, x);

dynamic_slice_merge_if(slice, x);
dynamic_slice_merge_else(slice, x);
}

fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) {
assert(slice[x] == 4);
assert(slice[y] == 1);
slice[y] = 0;
assert(slice[x] == 4);
assert(slice[1] == 0);
if x as u32 < 10 {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;
slice[x - 1] = slice[x];
} else {
slice[x] = 0;
}
assert(slice[3] == 2);
assert(slice[4] == 2);
}

fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) {
assert(slice[x] == 4);
assert(slice[y] == 1);
slice[y] = 0;
assert(slice[x] == 4);
assert(slice[1] == 0);
if x as u32 > 10 {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;
slice[x - 1] = slice[x];
} else {
slice[x] = 0;
}
assert(slice[4] == 0);
}

// This tests the case of missing a store instruction in the else branch
// of merging slices
fn dynamic_slice_index_if(mut slice: [Field], x: Field) {
if x as u32 < 10 {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;
} else {
assert(slice[x] == 0);
}
assert(slice[4] == 2);
}

fn dynamic_array_index_if(mut array: [Field; 5], x: Field) {
if x as u32 < 10 {
assert(array[x] == 4);
array[x] = array[x] - 2;
} else {
assert(array[x] == 0);
}
assert(array[4] == 2);
}

// This tests the case of missing a store instruction in the then branch
// of merging slices
fn dynamic_slice_index_else(mut slice: [Field], x: Field) {
if x as u32 > 10 {
assert(slice[x] == 0);
} else {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;
}
assert(slice[4] == 2);
}


fn dynamic_slice_merge_if(mut slice: [Field], x: Field) {
if x as u32 < 10 {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;

slice = slice.push_back(10);
// Having an array set here checks whether we appropriately
// handle a slice length that is not yet resolving to a constant
// during flattening
slice[x] = 10;
assert(slice[slice.len() - 1] == 10);
assert(slice.len() == 6);

slice[x] = 20;
slice[x] = slice[x] + 10;

slice = slice.push_front(11);
assert(slice[0] == 11);
assert(slice.len() == 7);
assert(slice[5] == 30);

slice = slice.push_front(12);
assert(slice[0] == 12);
assert(slice.len() == 8);
assert(slice[6] == 30);

let (popped_slice, last_elem) = slice.pop_back();
assert(last_elem == 10);
assert(popped_slice.len() == 7);

let (first_elem, rest_of_slice) = popped_slice.pop_front();
assert(first_elem == 12);
assert(rest_of_slice.len() == 6);

// TODO(#2462): SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen
slice = rest_of_slice.insert(2, 20);
assert(slice[2] == 20);
assert(slice[6] == 30);
assert(slice.len() == 7);

// TODO(#2462): SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen
let (removed_slice, removed_elem) = slice.remove(3);
// The deconstructed tuple assigns to the slice but is not seen outside of the if statement
// without a direct assignment
slice = removed_slice;

assert(removed_elem == 1);
assert(slice.len() == 6);
} else {
assert(slice[x] == 0);
slice = slice.push_back(20);
}

assert(slice.len() == 6);
assert(slice[slice.len() - 1] == 30);
}

fn dynamic_slice_merge_else(mut slice: [Field], x: Field) {
if x as u32 > 10 {
assert(slice[x] == 0);
slice[x] = 2;
} else {
assert(slice[x] == 4);
slice[x] = slice[x] - 2;
slice = slice.push_back(10);
}
assert(slice.len() == 6);
assert(slice[slice.len() - 1] == 10);

slice = slice.push_back(20);
assert(slice.len() == 7);
assert(slice[slice.len() - 1] == 20);
}

fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) {
assert(slice[x] == 4);
assert(slice[y] == 1);
slice[y] = 0;
assert(slice[x] == 4);
assert(slice[1] == 0);
if x as u32 < 10 {
slice[x] = slice[x] - 2;
if y != 1 {
slice[x] = slice[x] + 20;
} else {
if x == 5 {
// We should not hit this case
assert(slice[x] == 22);
} else {
slice[x] = 10;
slice = slice.push_back(15);
assert(slice.len() == 6);
}
assert(slice[4] == 10);
}
} else {
slice[x] = 0;
}
assert(slice[4] == 10);
assert(slice.len() == 6);
assert(slice[slice.len() - 1] == 15);

slice = slice.push_back(20);
assert(slice.len() == 7);
assert(slice[slice.len() - 1] == 20);
}

fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Field) {
assert(slice[x] == 4);
assert(slice[y] == 2);
slice[y] = 0;
assert(slice[x] == 4);
assert(slice[2] == 0);
if x as u32 < 10 {
slice[x] = slice[x] - 2;
// TODO: this panics as we have a load for the slice in flattening
if y == 1 {
slice[x] = slice[x] + 20;
} else {
if x == 4 {
slice[x] = 5;
}
assert(slice[4] == 5);
}
} else {
slice[x] = 0;
}
assert(slice[4] == 5);
}

3 changes: 2 additions & 1 deletion crates/nargo_cli/tests/execution_success/slices/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std::slice;
use dep::std;

fn main(x : Field, y : pub Field) {
let mut slice = [0; 2];
assert(slice[0] == 0);
Expand Down Expand Up @@ -155,4 +156,4 @@ fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] {
slice = slice.push_back(x);
}
slice
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ impl<'block> BrilligBlock<'block> {
destination_variable,
);
}
Instruction::ArraySet { array, index, value } => {
Instruction::ArraySet { array, index, value, .. } => {
let source_variable = self.convert_ssa_value(*array, dfg);
let index_register = self.convert_ssa_register_value(*index, dfg);
let value_variable = self.convert_ssa_value(*value, dfg);
Expand Down
3 changes: 2 additions & 1 deletion crates/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ impl RuntimeError {
}
_ => {
let message = self.to_string();
let location = self.call_stack().back().expect("Expected RuntimeError to have a location");
let location =
self.call_stack().back().expect("Expected RuntimeError to have a location");

Diagnostic::simple_error(message, String::new(), location.span)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ impl AcirContext {
}

/// Converts an [`AcirVar`] to an [`Expression`]
fn var_to_expression(&self, var: AcirVar) -> Result<Expression, InternalError> {
pub(crate) fn var_to_expression(&self, var: AcirVar) -> Result<Expression, InternalError> {
let var_data = match self.vars.get(&var) {
Some(var_data) => var_data,
None => {
Expand Down
Loading

0 comments on commit c5c4052

Please sign in to comment.