Skip to content

Commit

Permalink
feat(ssa): Merge slices in if statements with witness conditions (#2347)
Browse files Browse the repository at this point in the history
Co-authored-by: jfecher <jake@aztecprotocol.com>
  • Loading branch information
vezenovm and jfecher authored Aug 18, 2023
1 parent d8d6bc6 commit 76f7e43
Show file tree
Hide file tree
Showing 16 changed files with 679 additions and 115 deletions.
22 changes: 17 additions & 5 deletions crates/nargo/src/ops/foreign_calls.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use acvm::{
acir::brillig::{ForeignCallResult, Value},
acir::brillig::{ForeignCallOutput, ForeignCallResult, Value},
pwg::ForeignCallWaitInfo,
};
use iter_extended::vecmap;
Expand Down Expand Up @@ -54,13 +54,25 @@ impl ForeignCall {
}
Some(ForeignCall::Sequence) => {
let sequence_length: u128 = foreign_call.inputs[0][0].to_field().to_u128();

Ok(vecmap(0..sequence_length, Value::from).into())
let sequence = vecmap(0..sequence_length, Value::from);

Ok(ForeignCallResult {
values: vec![
ForeignCallOutput::Single(sequence_length.into()),
ForeignCallOutput::Array(sequence),
],
})
}
Some(ForeignCall::ReverseSequence) => {
let sequence_length: u128 = foreign_call.inputs[0][0].to_field().to_u128();

Ok(vecmap((0..sequence_length).rev(), Value::from).into())
let sequence = vecmap((0..sequence_length).rev(), Value::from);

Ok(ForeignCallResult {
values: vec![
ForeignCallOutput::Single(sequence_length.into()),
ForeignCallOutput::Array(sequence),
],
})
}
None => panic!("unexpected foreign call {:?}", foreign_call_name),
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "slice_access_failure"
type = "bin"
authors = [""]
compiler_version = "0.10.2"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = "5"
y = "10"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main(x : Field, y : pub Field) {
let mut slice = [0; 2];
if x == y {
slice = slice.push_back(y);
slice = slice.push_back(x);
} else {
slice = slice.push_back(x);
}
// This constraint should fail as the slice length is 3 and the index is 3
// The right hand side AND case ensures that the circuit inputs have not changed
// and we always hit the else case in the if statement above.
assert((slice[3] == 0) & (slice[2] != y));
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,85 @@ unconstrained fn main(x: Field, y: Field) {
let (empty_slice, should_be_x) = slice.remove(0);
assert(should_be_x == x);
assert(empty_slice.len() == 0);

regression_merge_slices(x, y);
}

// Tests slice passing to/from functions
unconstrained fn push_front_to_slice<T>(slice: [T], item: T) -> [T] {
slice.push_front(item)
}

// The parameters to this function must come from witness values (inputs to main)
unconstrained fn regression_merge_slices(x: Field, y: Field) {
merge_slices_if(x, y);
merge_slices_else(x);
}

unconstrained fn merge_slices_if(x: Field, y: Field) {
let slice = merge_slices_return(x, y);
assert(slice[2] == 10);
assert(slice.len() == 3);

let slice = merge_slices_mutate(x, y);
assert(slice[3] == 5);
assert(slice.len() == 4);

let slice = merge_slices_mutate_in_loop(x, y);
assert(slice[6] == 4);
assert(slice.len() == 7);
}

unconstrained fn merge_slices_else(x: Field) {
let slice = merge_slices_return(x, 5);
assert(slice[0] == 0);
assert(slice[1] == 0);
assert(slice.len() == 2);

let slice = merge_slices_mutate(x, 5);
assert(slice[2] == 5);
assert(slice.len() == 3);

let slice = merge_slices_mutate_in_loop(x, 5);
assert(slice[2] == 5);
assert(slice.len() == 3);
}

// Test returning a merged slice without a mutation
unconstrained fn merge_slices_return(x: Field, y: Field) -> [Field] {
let slice = [0; 2];
if x != y {
if x != 20 {
slice.push_back(y)
} else {
slice
}
} else {
slice
}
}

// Test mutating a slice inside of an if statement
unconstrained fn merge_slices_mutate(x: Field, y: Field) -> [Field] {
let mut slice = [0; 2];
if x != y {
slice = slice.push_back(y);
slice = slice.push_back(x);
} else {
slice = slice.push_back(x);
}
slice
}

// Test mutating a slice inside of a loop in an if statement
unconstrained fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] {
let mut slice = [0; 2];
if x != y {
for i in 0..5 {
slice = slice.push_back(i);
}
} else {
slice = slice.push_back(x);
}
slice
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ unconstrained fn main(x : Field) -> pub [u8; 31] {
// The result of this byte array will be little-endian
let byte_array = x.to_le_bytes(31);
assert(byte_array.len() == 31);

let mut bytes = [0; 31];
for i in 0..31 {
bytes[i] = byte_array[i];
Expand Down
79 changes: 76 additions & 3 deletions crates/nargo_cli/tests/execution_success/slices/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use dep::std::slice;
use dep::std;
fn main(x : Field, y : pub Field) {
/// TODO(#1889): Using slices in if statements where the condition is a witness
/// is not yet supported
let mut slice = [0; 2];
assert(slice[0] == 0);
assert(slice[0] != 1);
Expand Down Expand Up @@ -49,6 +46,8 @@ fn main(x : Field, y : pub Field) {
assert(append[4] == 5);

regression_2083();
// The parameters to this function must come from witness values (inputs to main)
regression_merge_slices(x, y);
}

// Ensure that slices of struct/tuple values work.
Expand Down Expand Up @@ -83,3 +82,77 @@ fn regression_2083() {
assert(x.0 == 5);
assert(x.1 == 6);
}

// The parameters to this function must come from witness values (inputs to main)
fn regression_merge_slices(x: Field, y: Field) {
merge_slices_if(x, y);
merge_slices_else(x);
}

fn merge_slices_if(x: Field, y: Field) {
let slice = merge_slices_return(x, y);
assert(slice[2] == 10);
assert(slice.len() == 3);

let slice = merge_slices_mutate(x, y);
assert(slice[3] == 5);
assert(slice.len() == 4);

let slice = merge_slices_mutate_in_loop(x, y);
assert(slice[6] == 4);
assert(slice.len() == 7);
}

fn merge_slices_else(x: Field) {
let slice = merge_slices_return(x, 5);
assert(slice[0] == 0);
assert(slice[1] == 0);
assert(slice.len() == 2);

let slice = merge_slices_mutate(x, 5);
assert(slice[2] == 5);
assert(slice.len() == 3);

let slice = merge_slices_mutate_in_loop(x, 5);
assert(slice[2] == 5);
assert(slice.len() == 3);
}

// Test returning a merged slice without a mutation
fn merge_slices_return(x: Field, y: Field) -> [Field] {
let slice = [0; 2];
if x != y {
if x != 20 {
slice.push_back(y)
} else {
slice
}
} else {
slice
}
}

// Test mutating a slice inside of an if statement
fn merge_slices_mutate(x: Field, y: Field) -> [Field] {
let mut slice = [0; 2];
if x != y {
slice = slice.push_back(y);
slice = slice.push_back(x);
} else {
slice = slice.push_back(x);
}
slice
}

// Test mutating a slice inside of a loop in an if statement
fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] {
let mut slice = [0; 2];
if x != y {
for i in 0..5 {
slice = slice.push_back(i);
}
} else {
slice = slice.push_back(x);
}
slice
}
Loading

0 comments on commit 76f7e43

Please sign in to comment.