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

feat: dynamic array indexing #886

Merged
merged 34 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bcb631d
assign a VM per array
guipublic Feb 13, 2023
4668c72
Merge branch 'master' into gd/dynamic_array4
guipublic Feb 13, 2023
520c2f9
cargo format
guipublic Feb 13, 2023
9764650
Code review
guipublic Feb 14, 2023
50e7d57
code review
guipublic Feb 14, 2023
6dbe6f6
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 14, 2023
528051b
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 20, 2023
8f8d0c5
enable dynamic arrays
guipublic Feb 21, 2023
b01a92c
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 21, 2023
2d51e6f
use trace index as counter
guipublic Feb 21, 2023
cb43e94
restore bad commit
guipublic Feb 21, 2023
c5a0bf9
restore bad commit
guipublic Feb 21, 2023
327fe57
restore bad commit
guipublic Feb 21, 2023
2aa2fb5
Code review
guipublic Feb 22, 2023
55afff4
Code review
guipublic Feb 23, 2023
e6eface
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 23, 2023
c23cefb
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 23, 2023
a027e74
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 24, 2023
af17ec6
Code review
guipublic Feb 28, 2023
36ca66c
Merge branch 'master' into gd/dynamic_array5
guipublic Feb 28, 2023
0c1bc4e
MemAddress
guipublic Feb 28, 2023
1e3861f
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 10, 2023
352b447
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 10, 2023
966b557
merge from master
guipublic Mar 10, 2023
ff90d69
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 10, 2023
cb93f6e
Remove ilog2 because it is tagged as unstable
guipublic Mar 10, 2023
9002218
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 14, 2023
c79e9dc
clippy
guipublic Mar 14, 2023
5a5f158
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 16, 2023
f13731b
Merge branch 'master' into gd/dynamic_array5
guipublic Mar 17, 2023
e406af7
de-activate dynamic arrays
guipublic Mar 17, 2023
023ab4e
cargo fmt
guipublic Mar 17, 2023
61c24ad
Code review
guipublic Mar 20, 2023
f4e590f
format
guipublic Mar 20, 2023
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
22 changes: 19 additions & 3 deletions crates/nargo/tests/test_data/6_array/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//Basic tests for arrays
fn main(x: [u32; 5], y: [u32; 5], mut z: u32, t: u32) {
let mut c = (z-z+2301) as u32;

//t= t+x[0]-x[0];
let mut c = 2301;
let idx = (z - 5*t - 5) as Field;
z = y[4];
//Test 1:
for i in 0..5 {
Expand Down Expand Up @@ -52,4 +51,21 @@ fn main(x: [u32; 5], y: [u32; 5], mut z: u32, t: u32) {
constrain x_elem != y_elem;
}
}

//dynamic array test
dyn_array(x, idx, idx - 3);
}

fn dyn_array(mut x: [u32; 5], y: Field, z: Field) {
constrain x[y] == 111;
constrain x[z] == 101;
x[z] = 0;
constrain x[y] == 111;
constrain x[1] == 0;
if y as u32 < 10 {
x[y] = x[y] - 2;
} else {
x[y] = 0;
}
constrain x[4] == 109;
}
2 changes: 1 addition & 1 deletion crates/nargo/tests/test_data/merkle_insert/Prover.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ old_hash_path = [
new_root = "0x256c13d7694e2f900f55756246aa1104169efd9fb9e7c6be54c15794795d476f"
leaf = "0x2e5ba44f3c5329aeb915c703e39b33c5872f1542500cbb22f12b71640aba502f"
index = "0"
mimc_input = [12,45,78,41]
mimc_input = [12,45,78,41]
1 change: 0 additions & 1 deletion crates/nargo/tests/test_data/merkle_insert/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ fn main(
let h = std::hash::mimc_bn254(mimc_input);
constrain h == 18226366069841799622585958305961373004333097209608110160936134895615261821931;
}

32 changes: 28 additions & 4 deletions crates/noirc_evaluator/src/ssa/acir_gen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use crate::ssa::{
builtin,
context::SsaContext,
node::{Instruction, Operation},
use crate::{
errors::RuntimeError,
ssa::{
block::BasicBlock,
builtin,
context::SsaContext,
node::{Instruction, Operation},
},
};
use crate::{Evaluator, RuntimeErrorKind};
use acvm::{
Expand Down Expand Up @@ -29,6 +33,26 @@ pub struct Acir {
}

impl Acir {
pub fn acir_gen(
&mut self,
evaluator: &mut Evaluator,
ctx: &SsaContext,
root: &BasicBlock,
show_output: bool,
) -> Result<(), RuntimeError> {
let mut current_block = Some(root);
while let Some(block) = current_block {
for iter in &block.instructions {
let ins = ctx.instruction(*iter);
self.acir_gen_instruction(ins, evaluator, ctx, show_output)?;
}
//TODO we should rather follow the jumps
current_block = block.left.map(|block_id| &ctx[block_id]);
}
self.memory.acir_gen(evaluator);
Ok(())
}

/// Generate ACIR opcodes based on the given instruction
pub fn acir_gen_instruction(
&mut self,
Expand Down
202 changes: 184 additions & 18 deletions crates/noirc_evaluator/src/ssa/acir_gen/acir_mem.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,154 @@
use crate::ssa::{
acir_gen::InternalVar,
context::SsaContext,
mem::{ArrayId, MemArray},
use crate::{
ssa::{
acir_gen::InternalVar,
context::SsaContext,
mem::{ArrayId, MemArray},
},
Evaluator,
};
use acvm::acir::native_types::Witness;
use acvm::{
acir::{
circuit::{directives::Directive, opcodes::Opcode as AcirOpcode},
native_types::{Expression, Witness},
},
FieldElement,
};

use iter_extended::vecmap;
use std::collections::BTreeMap;

use super::{
constraints::{self, mul_with_witness, subtract},
expression_from_witness,
operations::{self},
};

/// Represent a memory operation on the ArrayHeap, at the specified index
/// Operation is one for a store and 0 for a load
guipublic marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, Debug)]
struct MemOp {
operation: Expression,
value: Expression,
index: Expression,
}

#[derive(Default)]
pub struct ArrayHeap {
// maps memory address to InternalVar
memory_map: BTreeMap<u32, InternalVar>,
trace: Vec<MemOp>,
// maps memory address to (values,operation) that must be committed to the trace
staged: BTreeMap<u32, (Expression, Expression)>,
jfecher marked this conversation as resolved.
Show resolved Hide resolved
}

impl ArrayHeap {
pub fn commit_staged(&mut self) {
for (idx, (value, op)) in &self.staged {
let item = MemOp {
operation: op.clone(),
value: value.clone(),
index: Expression::from_field(FieldElement::from(*idx as i128)),
};
self.trace.push(item);
}
self.staged.clear();
}

pub fn push(&mut self, index: Expression, value: Expression, op: Expression) {
let item = MemOp { operation: op, value, index };
self.trace.push(item);
}

pub fn stage(&mut self, index: u32, value: Expression, op: Expression) {
self.staged.insert(index, (value, op));
}

fn generate_outputs(
inputs: Vec<Expression>,
bits: &mut Vec<Witness>,
evaluator: &mut Evaluator,
) -> Vec<Expression> {
let outputs =
vecmap(0..inputs.len(), |_| expression_from_witness(evaluator.add_witness_to_cs()));
if bits.is_empty() {
*bits = operations::sort::evaluate_permutation(&inputs, &outputs, evaluator);
} else {
operations::sort::evaluate_permutation_with_witness(&inputs, &outputs, bits, evaluator);
}
outputs
}
pub fn acir_gen(&self, evaluator: &mut Evaluator) {
let len = self.trace.len();
if len == 0 {
return;
}
let len_bits = AcirMem::bits(len);
// permutations
let mut in_counter = Vec::new();
let mut in_index = Vec::new();
let mut in_value = Vec::new();
let mut in_op = Vec::new();

let mut tuple_expressions = Vec::new();
for (counter, item) in self.trace.iter().enumerate() {
let counter_expr = Expression::from_field(FieldElement::from(counter as i128));
in_counter.push(counter_expr.clone());
in_index.push(item.index.clone());
in_value.push(item.value.clone());
in_op.push(item.operation.clone());
tuple_expressions.push(vec![item.index.clone(), counter_expr.clone()]);
}
let mut bit_counter = Vec::new();
let out_counter = Self::generate_outputs(in_counter, &mut bit_counter, evaluator);
let out_index = Self::generate_outputs(in_index, &mut bit_counter, evaluator);
let out_value = Self::generate_outputs(in_value, &mut bit_counter, evaluator);
let out_op = Self::generate_outputs(in_op, &mut bit_counter, evaluator);

// sort directive
evaluator.opcodes.push(AcirOpcode::Directive(Directive::PermutationSort {
inputs: tuple_expressions,
tuple: 2,
bits: bit_counter,
sort_by: vec![0, 1],
}));
let init = subtract(&out_op[0], FieldElement::one(), &Expression::one());
evaluator.opcodes.push(AcirOpcode::Arithmetic(init));
for i in 0..len - 1 {
// index sort
let index_sub = subtract(&out_index[i + 1], FieldElement::one(), &out_index[i]);
let primary_order = constraints::boolean_expr(&index_sub, evaluator);
evaluator.opcodes.push(AcirOpcode::Arithmetic(primary_order));
// counter sort
let cmp = constraints::evaluate_cmp(
&out_counter[i],
&out_counter[i + 1],
len_bits,
false,
evaluator,
);
let sub_cmp = subtract(&cmp, FieldElement::one(), &Expression::one());
let secondary_order = subtract(
&mul_with_witness(evaluator, &index_sub, &sub_cmp),
FieldElement::one(),
&sub_cmp,
);
evaluator.opcodes.push(AcirOpcode::Arithmetic(secondary_order));
// consistency checks
let sub1 = subtract(&Expression::one(), FieldElement::one(), &out_op[i + 1]);
let sub2 = subtract(&out_value[i + 1], FieldElement::one(), &out_value[i]);
let load_on_same_adr = mul_with_witness(evaluator, &sub1, &sub2);
let store_on_new_adr = mul_with_witness(evaluator, &index_sub, &sub1);
evaluator.opcodes.push(AcirOpcode::Arithmetic(store_on_new_adr));
evaluator.opcodes.push(AcirOpcode::Arithmetic(load_on_same_adr));
}
}
}

/// Handle virtual memory access
#[derive(Default)]
pub struct AcirMem {
virtual_memory: BTreeMap<ArrayId, ArrayHeap>,
pub dummy: Witness,
}

impl AcirMem {
Expand All @@ -25,9 +157,19 @@ impl AcirMem {
&mut self.virtual_memory.entry(array_id).or_default().memory_map
}

// returns the memory trace for the array
pub fn array_heap_mut(&mut self, array_id: ArrayId) -> &mut ArrayHeap {
let e = self.virtual_memory.entry(array_id);
e.or_default()
}

// Write the value to the array's VM at the specified index
pub fn insert(&mut self, array_id: ArrayId, index: u32, value: InternalVar) {
self.array_map_mut(array_id).insert(index, value);
let entry = self.virtual_memory.entry(array_id).or_default();
guipublic marked this conversation as resolved.
Show resolved Hide resolved
let value_expr = value.to_expression();
entry.memory_map.insert(index, value);

entry.stage(index, value_expr, Expression::one());
}

//Map the outputs into the array
Expand All @@ -52,30 +194,54 @@ impl AcirMem {
})
}

// number of bits required to store the input
guipublic marked this conversation as resolved.
Show resolved Hide resolved
fn bits(mut t: usize) -> u32 {
let mut r = 0;
while t != 0 {
t >>= 1;
r += 1;
}
r
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved

// Loads the associated `InternalVar` for the element
// in the `array` at the given `offset`.
//
// We check if the address of the array element
// is in the memory_map.
//
//
// Returns `None` if `offset` is out of bounds.
// Returns `None` if not found
pub(crate) fn load_array_element_constant_index(
&mut self,
array: &MemArray,
offset: u32,
) -> Option<InternalVar> {
// Check to see if the index has gone out of bounds
let array_length = array.len;
if offset >= array_length {
return None; // IndexOutOfBoundsError
}

// Check the memory_map to see if the element is there
let array_element = self
.array_map_mut(array.id)
.get(&offset)
.expect("ICE: Could not find value at index {offset}");
Some(array_element.clone())
self.array_map_mut(array.id).get(&offset).cloned()
}

// Apply staged stores to the memory trace
pub fn commit(&mut self, array_id: &ArrayId, clear: bool) {
let e = self.virtual_memory.entry(*array_id).or_default();
e.commit_staged();
if clear {
e.memory_map.clear();
}
}
pub fn add_to_trace(
&mut self,
array_id: &ArrayId,
index: Expression,
value: Expression,
op: Expression,
) {
self.commit(array_id, op != Expression::zero());
self.array_heap_mut(*array_id).push(index, value, op);
}
pub fn acir_gen(&self, evaluator: &mut Evaluator) {
for mem in &self.virtual_memory {
mem.1.acir_gen(evaluator);
}
}
}
2 changes: 1 addition & 1 deletion crates/noirc_evaluator/src/ssa/acir_gen/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ pub mod intrinsics;
pub mod load;
pub mod not;
pub mod r#return;
mod sort;
pub mod sort;
pub mod store;
pub mod truncate;
3 changes: 1 addition & 2 deletions crates/noirc_evaluator/src/ssa/acir_gen/operations/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ pub(crate) fn evaluate(
} else {
//we need the type of rhs and its max value, then:
//lhs-rhs+k*2^bit_size where k=ceil(max_value/2^bit_size)
// TODO: what is this code doing?
let bit_size = r_size;
let bit_size = ctx[binary.rhs].get_type().bits();
let r_big = BigUint::one() << bit_size;
let mut k = max_rhs_value / &r_big;
if max_rhs_value % &r_big != BigUint::zero() {
Expand Down
Loading