diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs index 5304b0a33f3..267d0fba9b1 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs @@ -10,18 +10,27 @@ use noirc_evaluator::ssa::ir::map::Id; use std::collections::{HashMap, VecDeque}; use std::iter::zip; +#[derive(Debug, Clone)] +pub(crate) struct StoredArray { + array_id: TypedValue, + element_type: ValueType, + is_references: bool, +} + /// Main context for the ssa block containing both ACIR and Brillig builders and their state /// It works with indices of variables Ids, because it cannot handle Ids logic for ACIR and Brillig #[derive(Debug, Clone)] pub(crate) struct BlockContext { /// Ids of the Program variables stored as TypedValue separated by type - pub(crate) stored_values: HashMap>, + pub(crate) stored_variables: HashMap>, /// Ids of typed addresses of memory (mutable variables) pub(crate) memory_addresses: HashMap>, /// Parent blocks history pub(crate) parent_blocks_history: VecDeque, /// Children blocks pub(crate) children_blocks: Vec, + /// Arrays stored in the block + pub(crate) stored_arrays: Vec, /// Options for the block pub(crate) options: SsaBlockOptions, } @@ -52,16 +61,17 @@ fn append_typed_value_to_map( impl BlockContext { pub(crate) fn new( - stored_values: HashMap>, + stored_variables: HashMap>, memory_addresses: HashMap>, parent_blocks_history: VecDeque, options: SsaBlockOptions, ) -> Self { Self { - stored_values, + stored_variables, memory_addresses, parent_blocks_history, children_blocks: Vec::new(), + stored_arrays: Vec::new(), options, } } @@ -74,7 +84,7 @@ impl BlockContext { arg: Argument, instruction: InstructionWithOneArg, ) { - let value = get_typed_value_from_map(&self.stored_values, &arg.value_type, arg.index); + let value = get_typed_value_from_map(&self.stored_variables, &arg.value_type, arg.index); let value = match value { Some(value) => value, _ => return, @@ -83,7 +93,7 @@ impl BlockContext { // insert to brillig, assert id is the same assert_eq!(acir_result.value_id, instruction(brillig_builder, value).value_id); append_typed_value_to_map( - &mut self.stored_values, + &mut self.stored_variables, &acir_result.to_value_type(), acir_result, ); @@ -98,9 +108,11 @@ impl BlockContext { rhs: Argument, instruction: InstructionWithTwoArgs, ) { - let instr_lhs = get_typed_value_from_map(&self.stored_values, &lhs.value_type, lhs.index); + let instr_lhs = + get_typed_value_from_map(&self.stored_variables, &lhs.value_type, lhs.index); // We ignore type of the second argument, because all binary instructions must use the same type - let instr_rhs = get_typed_value_from_map(&self.stored_values, &lhs.value_type, rhs.index); + let instr_rhs = + get_typed_value_from_map(&self.stored_variables, &lhs.value_type, rhs.index); let (instr_lhs, instr_rhs) = match (instr_lhs, instr_rhs) { (Some(acir_lhs), Some(acir_rhs)) => (acir_lhs, acir_rhs), _ => return, @@ -108,7 +120,7 @@ impl BlockContext { let result = instruction(acir_builder, instr_lhs.clone(), instr_rhs.clone()); // insert to brillig, assert id of return is the same assert_eq!(result.value_id, instruction(brillig_builder, instr_lhs, instr_rhs).value_id); - append_typed_value_to_map(&mut self.stored_values, &result.to_value_type(), result); + append_typed_value_to_map(&mut self.stored_variables, &result.to_value_type(), result); } /// Inserts an instruction into both ACIR and Brillig programs @@ -184,7 +196,7 @@ impl BlockContext { return; } let value = - get_typed_value_from_map(&self.stored_values, &lhs.value_type, lhs.index); + get_typed_value_from_map(&self.stored_variables, &lhs.value_type, lhs.index); let value = match value { Some(value) => value, _ => return, @@ -195,11 +207,12 @@ impl BlockContext { brillig_builder.insert_cast(value.clone(), type_).value_id ); // Cast can return the same value as the original value, if cast type is forbidden, so we skip it - if self.stored_values.get(&value.to_value_type()).unwrap().contains(&acir_result) { + if self.stored_variables.get(&value.to_value_type()).unwrap().contains(&acir_result) + { return; } append_typed_value_to_map( - &mut self.stored_values, + &mut self.stored_variables, &acir_result.to_value_type(), acir_result, ); @@ -307,8 +320,8 @@ impl BlockContext { Instruction::AddSubConstrain { lhs, rhs } => { // inserts lhs' = lhs + rhs let lhs_orig = - get_typed_value_from_map(&self.stored_values, &ValueType::Field, lhs); - let rhs = get_typed_value_from_map(&self.stored_values, &ValueType::Field, rhs); + get_typed_value_from_map(&self.stored_variables, &ValueType::Field, lhs); + let rhs = get_typed_value_from_map(&self.stored_variables, &ValueType::Field, rhs); let (lhs_orig, rhs) = match (lhs_orig, rhs) { (Some(lhs_orig), Some(rhs)) => (lhs_orig, rhs), _ => return, @@ -343,8 +356,8 @@ impl BlockContext { } Instruction::MulDivConstrain { lhs, rhs } => { let lhs_orig = - get_typed_value_from_map(&self.stored_values, &ValueType::Field, lhs); - let rhs = get_typed_value_from_map(&self.stored_values, &ValueType::Field, rhs); + get_typed_value_from_map(&self.stored_variables, &ValueType::Field, lhs); + let rhs = get_typed_value_from_map(&self.stored_variables, &ValueType::Field, rhs); let (lhs_orig, rhs) = match (lhs_orig, rhs) { (Some(lhs_orig), Some(rhs)) => (lhs_orig, rhs), _ => return, @@ -379,12 +392,14 @@ impl BlockContext { return; } - let value = - match get_typed_value_from_map(&self.stored_values, &lhs.value_type, lhs.index) - { - Some(value) => value, - _ => return, - }; + let value = match get_typed_value_from_map( + &self.stored_variables, + &lhs.value_type, + lhs.index, + ) { + Some(value) => value, + _ => return, + }; let addr = acir_builder.insert_add_to_memory(value.clone()); assert_eq!( @@ -415,7 +430,7 @@ impl BlockContext { "load from memory differs in ACIR and Brillig" ); append_typed_value_to_map( - &mut self.stored_values, + &mut self.stored_variables, &value.to_value_type(), value.clone(), ); @@ -433,8 +448,11 @@ impl BlockContext { Some(addr) => addr, _ => return, }; - let value = - get_typed_value_from_map(&self.stored_values, &value.value_type, value.index); + let value = get_typed_value_from_map( + &self.stored_variables, + &value.value_type, + value.index, + ); let value = match value { Some(value) => value, _ => return, @@ -442,6 +460,295 @@ impl BlockContext { acir_builder.insert_set_to_memory(addr.clone(), value.clone()); brillig_builder.insert_set_to_memory(addr.clone(), value); } + + Instruction::CreateArray { elements_indices, element_type, is_references } => { + if !self.options.instruction_options.create_array_enabled { + return; + } + // if we storing references, take values from memory addresses, otherwise from stored variables + let elements = if !is_references { + elements_indices + .iter() + .map(|index| { + get_typed_value_from_map(&self.stored_variables, &element_type, *index) + }) + .collect::>>() + } else { + elements_indices + .iter() + .map(|index| { + get_typed_value_from_map(&self.memory_addresses, &element_type, *index) + }) + .collect::>>() + }; + + let elements = match elements { + Some(elements) => elements, + _ => return, + }; + if elements.is_empty() { + return; + } + // insert to both acir and brillig builders + let array = acir_builder.insert_array(elements.clone(), is_references); + assert_eq!( + array.value_id, + brillig_builder.insert_array(elements, is_references).value_id, + ); + self.stored_arrays.push(StoredArray { + array_id: array, + element_type, + is_references, + }); + } + Instruction::ArrayGet { array_index, index, safe_index } => { + if !self.options.instruction_options.array_get_enabled + || !self.options.instruction_options.create_array_enabled + { + return; + } + if self.stored_arrays.is_empty() { + return; + } + // get the array from the stored arrays + let stored_array = self.stored_arrays.get(array_index % self.stored_arrays.len()); + let stored_array = match stored_array { + Some(stored_array) => stored_array, + _ => return, + }; + // references are not supported for array get with dynamic index + if stored_array.is_references { + return; + } + let array_id = stored_array.array_id.clone(); + // get the index from the stored variables + let index = get_typed_value_from_map( + &self.stored_variables, + &index.value_type, + index.index, + ); + let index = match index { + Some(index) => index, + _ => return, + }; + // cast the index to u32 + let index_casted = acir_builder.insert_cast(index.clone(), ValueType::U32); + assert_eq!( + index_casted.value_id, + brillig_builder.insert_cast(index.clone(), ValueType::U32).value_id + ); + // insert array get to both acir and brillig builders + let value = acir_builder.insert_array_get( + array_id.clone(), + index_casted.clone(), + stored_array.element_type.to_ssa_type(), + safe_index, + ); + assert_eq!( + value.value_id, + brillig_builder + .insert_array_get( + array_id.clone(), + index_casted.clone(), + stored_array.element_type.to_ssa_type(), + safe_index, + ) + .value_id, + ); + append_typed_value_to_map( + &mut self.stored_variables, + &value.to_value_type(), + value.clone(), + ); + } + Instruction::ArraySet { array_index, index, value_index, mutable, safe_index } => { + if !self.options.instruction_options.array_set_enabled { + return; + } + if self.stored_arrays.is_empty() { + return; + } + // get the array from the stored arrays + let stored_array = self.stored_arrays.get(array_index % self.stored_arrays.len()); + let stored_array = match stored_array { + Some(stored_array) => stored_array, + _ => return, + }; + // references are not supported for array set with dynamic index + if stored_array.is_references { + return; + } + let array_id = stored_array.array_id.clone(); + + // get the index from the stored variables + let index = get_typed_value_from_map( + &self.stored_variables, + &index.value_type, + index.index, + ); + let index = match index { + Some(index) => index, + _ => return, + }; + // cast the index to u32 + let index_casted = acir_builder.insert_cast(index.clone(), ValueType::U32); + assert_eq!( + index_casted.value_id, + brillig_builder.insert_cast(index.clone(), ValueType::U32).value_id + ); + + // get the value from the stored variables + let value = get_typed_value_from_map( + &self.stored_variables, + &stored_array.element_type, + value_index, + ); + let value = match value { + Some(value) => value, + _ => return, + }; + // insert array set to both acir and brillig builders + let new_array = acir_builder.insert_array_set( + array_id.clone(), + index_casted.clone(), + value.clone(), + mutable, + safe_index, + ); + assert_eq!( + new_array.value_id, + brillig_builder + .insert_array_set(array_id, index_casted, value, mutable, safe_index) + .value_id + ); + + self.stored_arrays.push(StoredArray { + array_id: new_array, + element_type: stored_array.element_type, + is_references: stored_array.is_references, + }); + } + Instruction::ArrayGetWithConstantIndex { array_index, index, safe_index } => { + if !self.options.instruction_options.array_get_enabled + || !self.options.instruction_options.create_array_enabled + { + return; + } + if self.stored_arrays.is_empty() { + return; + } + // get the array from the stored arrays + let stored_array = self.stored_arrays.get(array_index % self.stored_arrays.len()); + let stored_array = match stored_array { + Some(stored_array) => stored_array, + _ => return, + }; + let index_id = acir_builder.insert_constant(index, ValueType::U32); + assert_eq!( + index_id.value_id, + brillig_builder.insert_constant(index, ValueType::U32).value_id + ); + let value = acir_builder.insert_array_get( + stored_array.array_id.clone(), + index_id.clone(), + stored_array.element_type.to_ssa_type(), + safe_index, + ); + assert_eq!( + value.value_id, + brillig_builder + .insert_array_get( + stored_array.array_id.clone(), + index_id.clone(), + stored_array.element_type.to_ssa_type(), + safe_index, + ) + .value_id + ); + if !stored_array.is_references { + append_typed_value_to_map( + &mut self.stored_variables, + &value.to_value_type(), + value.clone(), + ); + } else { + append_typed_value_to_map( + &mut self.memory_addresses, + &value.to_value_type(), + value.clone(), + ); + } + } + Instruction::ArraySetWithConstantIndex { + array_index, + index, + value_index, + mutable, + safe_index, + } => { + if !self.options.instruction_options.array_set_enabled + || !self.options.instruction_options.create_array_enabled + { + return; + } + if self.stored_arrays.is_empty() { + return; + } + // get the array from the stored arrays + let stored_array = self.stored_arrays.get(array_index % self.stored_arrays.len()); + let stored_array = match stored_array { + Some(stored_array) => stored_array, + _ => return, + }; + let index_id = acir_builder.insert_constant(index, ValueType::U32); + assert_eq!( + index_id.value_id, + brillig_builder.insert_constant(index, ValueType::U32).value_id + ); + // get the value from the stored variables if not references, otherwise from memory addresses + let value = if !stored_array.is_references { + get_typed_value_from_map( + &self.stored_variables, + &stored_array.element_type, + value_index, + ) + } else { + get_typed_value_from_map( + &self.memory_addresses, + &stored_array.element_type, + value_index, + ) + }; + let value = match value { + Some(value) => value, + _ => return, + }; + // insert array set to both acir and brillig builders + let new_array = acir_builder.insert_array_set( + stored_array.array_id.clone(), + index_id.clone(), + value.clone(), + mutable, + safe_index, + ); + assert_eq!( + new_array.value_id, + brillig_builder + .insert_array_set( + stored_array.array_id.clone(), + index_id, + value, + mutable, + safe_index, + ) + .value_id + ); + self.stored_arrays.push(StoredArray { + array_id: new_array, + element_type: stored_array.element_type, + is_references: stored_array.is_references, + }); + } } } @@ -452,7 +759,7 @@ impl BlockContext { instructions: &Vec, ) { for instruction in instructions { - self.insert_instruction(acir_builder, brillig_builder, *instruction); + self.insert_instruction(acir_builder, brillig_builder, instruction.clone()); } } @@ -463,7 +770,7 @@ impl BlockContext { brillig_builder: &mut FuzzerBuilder, return_type: ValueType, ) { - let array_of_values_with_return_type = self.stored_values.get(&return_type); + let array_of_values_with_return_type = self.stored_variables.get(&return_type); let return_value = match array_of_values_with_return_type { Some(arr) => arr.iter().last(), _ => None, @@ -476,7 +783,8 @@ impl BlockContext { _ => { // If no last value was set, we take a boolean that is definitely set and cast it to the return type let boolean_value = - get_typed_value_from_map(&self.stored_values, &ValueType::Boolean, 0).unwrap(); + get_typed_value_from_map(&self.stored_variables, &ValueType::Boolean, 0) + .unwrap(); let return_value = acir_builder.insert_cast(boolean_value.clone(), return_type); assert_eq!(brillig_builder.insert_cast(boolean_value, return_type), return_value); acir_builder.finalize_function(&return_value); @@ -506,7 +814,7 @@ impl BlockContext { ) { // takes last boolean variable as condition let condition = self - .stored_values + .stored_variables .get(&ValueType::Boolean) .and_then(|values| values.last().cloned()) .expect("Should have at least one boolean") @@ -535,7 +843,8 @@ impl BlockContext { // If we don't have some value of type of the argument, we skip the function call let mut values = vec![]; for (value_type, index) in zip(function_signature.input_types, args) { - let value = match get_typed_value_from_map(&self.stored_values, &value_type, *index) { + let value = match get_typed_value_from_map(&self.stored_variables, &value_type, *index) + { Some(value) => value, None => return, }; @@ -556,7 +865,7 @@ impl BlockContext { }; // Append the return value to stored_values map append_typed_value_to_map( - &mut self.stored_values, + &mut self.stored_variables, &function_signature.return_type, typed_ret_val, ); diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs index 0d24ad74630..a970ac9eb47 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs @@ -289,8 +289,10 @@ impl<'a> FuzzerFunctionContext<'a> { /// SSA block can use variables from predecessor that is not in branch. /// Look [Self::find_closest_parent] for more details. fn store_variables(&mut self) { - self.stored_variables_for_block - .insert(self.current_block.block_id, self.current_block.context.stored_values.clone()); + self.stored_variables_for_block.insert( + self.current_block.block_id, + self.current_block.context.stored_variables.clone(), + ); } fn process_jmp_if_command(&mut self, block_then_idx: usize, block_else_idx: usize) { @@ -324,18 +326,16 @@ impl<'a> FuzzerFunctionContext<'a> { // creates new contexts of created blocks let mut parent_blocks_history = self.current_block.context.parent_blocks_history.clone(); parent_blocks_history.push_front(self.current_block.block_id); - let mut block_then_context = BlockContext::new( - self.current_block.context.stored_values.clone(), - self.current_block.context.memory_addresses.clone(), - parent_blocks_history.clone(), - SsaBlockOptions::from(self.function_context_options.clone()), - ); - let mut block_else_context = BlockContext::new( - self.current_block.context.stored_values.clone(), - self.current_block.context.memory_addresses.clone(), + let mut block_then_context = BlockContext { + children_blocks: vec![], + parent_blocks_history: parent_blocks_history.clone(), + ..self.current_block.context.clone() + }; + let mut block_else_context = BlockContext { + children_blocks: vec![], parent_blocks_history, - SsaBlockOptions::from(self.function_context_options.clone()), - ); + ..self.current_block.context.clone() + }; // inserts instructions into created blocks self.switch_to_block(block_then_id); @@ -431,12 +431,11 @@ impl<'a> FuzzerFunctionContext<'a> { let mut parent_blocks_history = self.current_block.context.parent_blocks_history.clone(); parent_blocks_history.push_front(self.current_block.block_id); self.switch_to_block(destination_block_id); - let mut destination_block_context = BlockContext::new( - self.current_block.context.stored_values.clone(), - self.current_block.context.memory_addresses.clone(), + let mut destination_block_context = BlockContext { + children_blocks: vec![], parent_blocks_history, - SsaBlockOptions::from(self.function_context_options.clone()), - ); + ..self.current_block.context.clone() + }; // inserts instructions into the new block destination_block_context.insert_instructions( @@ -582,12 +581,8 @@ impl<'a> FuzzerFunctionContext<'a> { self.insert_jmp_instruction(block_if_id, vec![start_id.clone()]); // fill body block with instructions - let mut block_body_context = BlockContext::new( - self.current_block.context.stored_values.clone(), - self.current_block.context.memory_addresses.clone(), - self.current_block.context.parent_blocks_history.clone(), - SsaBlockOptions::from(self.function_context_options.clone()), - ); + let mut block_body_context = + BlockContext { children_blocks: vec![], ..self.current_block.context.clone() }; self.switch_to_block(block_body_id); block_body_context.insert_instructions( self.acir_builder, @@ -605,12 +600,11 @@ impl<'a> FuzzerFunctionContext<'a> { self.current_block.context.clone() }; // end block does not share variables with body block, so we copy them from the current block - let block_end_context = BlockContext::new( - end_context.stored_values.clone(), - end_context.memory_addresses.clone(), - block_body_context.parent_blocks_history.clone(), - SsaBlockOptions::from(self.function_context_options.clone()), - ); + let block_end_context = BlockContext { + children_blocks: vec![], + parent_blocks_history: block_body_context.parent_blocks_history.clone(), + ..end_context + }; let end_block_stored = StoredBlock { context: block_end_context, block_id: block_end_id }; // connect end block with the current block @@ -718,12 +712,11 @@ impl<'a> FuzzerFunctionContext<'a> { let closest_parent = self.find_closest_parent(&first_block, &second_block); let closest_parent_block = self.stored_blocks[&closest_parent].clone(); - let merged_block_context = BlockContext::new( - closest_parent_block.context.stored_values.clone(), - closest_parent_block.context.memory_addresses.clone(), + let merged_block_context = BlockContext { + children_blocks: vec![], parent_blocks_history, - SsaBlockOptions::from(self.function_context_options.clone()), - ); + ..closest_parent_block.context.clone() + }; self.switch_to_block(first_block.block_id); first_block.context.finalize_block_with_jmp( self.acir_builder, @@ -915,12 +908,11 @@ impl<'a> FuzzerFunctionContext<'a> { // add instructions to the return block self.switch_to_block(return_block_id); - let mut return_block_context = BlockContext::new( - last_block.context.stored_values.clone(), - last_block.context.memory_addresses.clone(), - VecDeque::new(), - SsaBlockOptions::from(self.function_context_options.clone()), - ); + let mut return_block_context = BlockContext { + children_blocks: vec![], + parent_blocks_history: VecDeque::new(), + ..last_block.context.clone() + }; return_block_context.insert_instructions( self.acir_builder, self.brillig_builder, diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs index 85165d065f1..530c7c8ce20 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs @@ -764,4 +764,163 @@ mod tests { None => panic!("Program failed to execute"), } } + + /// Test array get and set + /// fn main f0 { + /// b0(v0: Field, v1: Field, v2: Field, v3: Field, v4: Field, v5: u1, v6: u1): + /// v7 = make_array [v0, v1, v2, v3, v4] : [Field; 5] + /// v8 = truncate v0 to 32 bits, max_bit_size: 254 + /// v9 = cast v8 as u32 + /// v11 = mod v9, u32 5 + /// v12 = array_set v7, index v11, value v4 + /// v13 = truncate v0 to 32 bits, max_bit_size: 254 + /// v14 = cast v13 as u32 + /// v15 = mod v14, u32 5 + /// v16 = array_get v12, index v15 -> Field + /// return v16 + /// } + #[test] + fn array_get_and_set() { + let arg_0_field = Argument { index: 0, value_type: ValueType::Field }; + // create array [v0, v1] + let create_array_block = InstructionBlock { + instructions: vec![Instruction::CreateArray { + elements_indices: vec![0, 1, 2, 3, 4], + element_type: ValueType::Field, + is_references: false, + }], + }; + // create new array setting new_array[0] = v4 + let array_set_block = InstructionBlock { + instructions: vec![Instruction::ArraySet { + array_index: 0, + index: arg_0_field, + value_index: 4, // set v4 + mutable: false, + safe_index: true, + }], + }; + // get new_array[0] + let array_get_block = InstructionBlock { + instructions: vec![Instruction::ArrayGet { + array_index: 1, + index: arg_0_field, + safe_index: true, + }], + }; + let instructions_blocks = vec![create_array_block, array_set_block, array_get_block]; + let commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 1 }, + ]; + let main_func = FunctionData { + commands, + return_instruction_block_idx: 2, + return_type: ValueType::Field, + }; + let fuzzer_data = FuzzerData { + instruction_blocks: instructions_blocks, + functions: vec![main_func], + initial_witness: default_witness(), + }; + let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); + match result { + Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(4_u32)), + None => panic!("Program failed to execute"), + } + } + + /// Test that references in arrays work + /// fn main f0 { + /// b0(v0: Field, v1: Field, v2: Field, v3: Field, v4: Field, v5: u1, v6: u1): + /// v7 = allocate -> &mut Field + /// store v0 at v7 + /// v8 = allocate -> &mut Field + /// store v2 at v8 + /// v9 = make_array [v7, v8, v7] : [&mut Field; 3] + /// store v1 at v7 + /// v10 = make_array [v7, v7, v7] : [&mut Field; 3] <----- set v9, index 1, value v7 + /// jmp b1() + /// b1(): + /// v11 = load v7 -> Field <---- its simplified from v11 = v10[1]; v12 = load v11 + /// return v11 + /// } + /// assert that return value is v1 + #[test] + fn test_reference_in_array() { + let _ = env_logger::try_init(); + let arg_0_field = Argument { index: 0, value_type: ValueType::Field }; + let arg_1_field = Argument { index: 1, value_type: ValueType::Field }; + let arg_2_field = Argument { index: 2, value_type: ValueType::Field }; + + let add_to_memory_block = + InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_0_field }] }; + let add_to_memory_block_2 = + InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; + let set_to_memory_block = InstructionBlock { + instructions: vec![Instruction::SetToMemory { + memory_addr_index: 0, + value: arg_1_field, + }], + }; + let set_to_array_block = InstructionBlock { + instructions: vec![Instruction::ArraySetWithConstantIndex { + array_index: 0, + index: 1, + value_index: 0, + mutable: false, + safe_index: true, + }], + }; + let create_array_block = InstructionBlock { + instructions: vec![Instruction::CreateArray { + elements_indices: vec![0, 1, 2], + element_type: ValueType::Field, + is_references: true, + }], + }; + let get_from_array_block = InstructionBlock { + instructions: vec![Instruction::ArrayGetWithConstantIndex { + array_index: 1, + index: 1, + safe_index: true, + }], + }; + let typed_memory_2 = Argument { index: 2, value_type: ValueType::Field }; + let load_from_memory_block = InstructionBlock { + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_2 }], + }; + let instructions_blocks = vec![ + add_to_memory_block, + add_to_memory_block_2, + create_array_block, + set_to_memory_block, + set_to_array_block, + get_from_array_block, + load_from_memory_block, + ]; + let commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 1 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 2 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 3 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 4 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 5 }, + ]; + let main_func = FunctionData { + commands, + return_instruction_block_idx: 6, + return_type: ValueType::Field, + }; + let fuzzer_data = FuzzerData { + instruction_blocks: instructions_blocks, + functions: vec![main_func], + initial_witness: default_witness(), + }; + let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); + match result { + Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1_u32)), + None => panic!("Program failed to execute"), + } + } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs index c13bfabe149..95b71fab8cb 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs @@ -3,6 +3,12 @@ use libfuzzer_sys::arbitrary::Arbitrary; use noir_ssa_fuzzer::typed_value::ValueType; use serde::{Deserialize, Serialize}; +#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] +pub(crate) struct Array { + pub(crate) size: usize, + pub(crate) element_type: ValueType, +} + #[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] pub(crate) struct Argument { /// Index of the argument in the context of stored variables of this type @@ -18,7 +24,7 @@ pub(crate) struct Argument { /// Represents set of instructions /// /// For operations that take two arguments we ignore type of the second argument. -#[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] pub(crate) enum Instruction { /// Addition of two values AddChecked { lhs: Argument, rhs: Argument }, @@ -68,6 +74,36 @@ pub(crate) enum Instruction { /// Store value to mutable memory /// Stores value to memory with insert_store SetToMemory { memory_addr_index: usize, value: Argument }, + + /// Create array, only type of first argument is used + /// Other elements will be taken from stored variables of the same type + CreateArray { elements_indices: Vec, element_type: ValueType, is_references: bool }, + /// Get element from array, index will be casted to u32, only for arrays without references + /// If safe_index is true, index will be taken modulo the size of the array + ArrayGet { array_index: usize, index: Argument, safe_index: bool }, + /// Set element in array, index will be casted to u32, only for arrays without references + /// Value will be cast to the type of the array + /// If safe_index is true, index will be taken modulo the size of the array + ArraySet { + array_index: usize, + index: Argument, + value_index: usize, + mutable: bool, + safe_index: bool, + }, + /// Get element from array, index is constant + /// If safe_index is true, index will be taken modulo the size of the array + ArrayGetWithConstantIndex { array_index: usize, index: usize, safe_index: bool }, + /// Set element in array, index is constant + /// Value will be cast to the type of the array + /// If safe_index is true, index will be taken modulo the size of the array + ArraySetWithConstantIndex { + array_index: usize, + index: usize, + value_index: usize, + mutable: bool, + safe_index: bool, + }, } /// Represents set of instructions diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs index 37bb808e7c4..36e69ec5cb5 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs @@ -20,6 +20,9 @@ pub(crate) struct InstructionOptions { pub(crate) load_enabled: bool, pub(crate) store_enabled: bool, pub(crate) alloc_enabled: bool, + pub(crate) create_array_enabled: bool, + pub(crate) array_get_enabled: bool, + pub(crate) array_set_enabled: bool, } impl Default for InstructionOptions { @@ -42,6 +45,9 @@ impl Default for InstructionOptions { load_enabled: true, store_enabled: true, alloc_enabled: true, + create_array_enabled: true, + array_get_enabled: true, + array_set_enabled: true, } } } @@ -137,7 +143,7 @@ pub(crate) struct FuzzerOptions { impl Default for FuzzerOptions { fn default() -> Self { Self { - compile_options: CompileOptions { show_ssa: false, ..Default::default() }, + compile_options: CompileOptions { show_ssa: true, ..Default::default() }, max_ssa_blocks_num: 100, max_instructions_num: 1000, max_iterations_num: 1000, diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs index c96df26b019..bc3b20338b5 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs @@ -7,7 +7,7 @@ mod utils; use bincode::serde::{borrow_decode_from_slice, encode_to_vec}; use fuzz_lib::fuzz_target_lib::fuzz_target; use fuzz_lib::fuzzer::FuzzerData; -use fuzz_lib::options::{FuzzerOptions, InstructionOptions}; +use fuzz_lib::options::{FuzzerCommandOptions, FuzzerOptions, InstructionOptions}; use libfuzzer_sys::Corpus; use mutations::mutate; use noirc_driver::CompileOptions; @@ -47,9 +47,16 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { alloc_enabled: false, ..InstructionOptions::default() }; - let modes = vec![FuzzerMode::NonConstant, FuzzerMode::NonConstantWithoutDIE]; - let options = - FuzzerOptions { compile_options, instruction_options, modes, ..FuzzerOptions::default() }; + let modes = vec![FuzzerMode::NonConstant]; + let fuzzer_command_options = + FuzzerCommandOptions { loops_enabled: false, ..FuzzerCommandOptions::default() }; + let options = FuzzerOptions { + compile_options, + instruction_options, + modes, + fuzzer_command_options, + ..FuzzerOptions::default() + }; let fuzzer_data = borrow_decode_from_slice(data, bincode::config::legacy()) .unwrap_or((FuzzerData::default(), 1337)) .0; diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs index 706fd4c556d..3c1f88e1c3a 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs @@ -105,6 +105,13 @@ impl InstructionMutator for InstructionArgumentsMutation { } } } + + // TODO(sn): Implement mutations for array instructions + Instruction::ArrayGet { .. } + | Instruction::ArraySet { .. } + | Instruction::CreateArray { .. } + | Instruction::ArrayGetWithConstantIndex { .. } + | Instruction::ArraySetWithConstantIndex { .. } => {} } } } diff --git a/tooling/ssa_fuzzer/src/builder.rs b/tooling/ssa_fuzzer/src/builder.rs index 5e46d5c11c0..1286d587325 100644 --- a/tooling/ssa_fuzzer/src/builder.rs +++ b/tooling/ssa_fuzzer/src/builder.rs @@ -5,6 +5,7 @@ use noirc_driver::{CompileOptions, CompiledProgram}; use noirc_evaluator::ssa::function_builder::FunctionBuilder; use noirc_evaluator::ssa::ir::basic_block::BasicBlockId; use noirc_evaluator::ssa::ir::function::{Function, RuntimeType}; +use noirc_evaluator::ssa::ir::instruction::ArrayOffset; use noirc_evaluator::ssa::ir::instruction::BinaryOp; use noirc_evaluator::ssa::ir::map::Id; use noirc_evaluator::ssa::ir::types::{NumericType, Type}; @@ -12,6 +13,7 @@ use noirc_evaluator::ssa::ir::value::Value; use noirc_frontend::monomorphization::ast::InlineType; use noirc_frontend::monomorphization::ast::InlineType as FrontendInlineType; use std::panic::AssertUnwindSafe; +use std::sync::Arc; use thiserror::Error; #[derive(Debug, Error)] @@ -365,4 +367,103 @@ impl FuzzerBuilder { .first() .unwrap() } + + /// Inserts a new array with the given elements and type + pub fn insert_array(&mut self, elements: Vec, is_references: bool) -> TypedValue { + let array_length = elements.len() as u32; + assert!(array_length > 0, "Array must have at least one element"); + let element_type = elements[0].type_of_variable.clone(); + let array_elements_type = if is_references { + Type::Array( + Arc::new(vec![Type::Reference(Arc::new(element_type.clone()))]), + array_length, + ) + } else { + Type::Array(Arc::new(vec![element_type.clone()]), array_length) + }; + let typ = array_elements_type.clone(); + assert!( + elements.iter().all(|e| e.type_of_variable == element_type), + "All elements must have the same type" + ); + let res = self + .builder + .insert_make_array(elements.into_iter().map(|e| e.value_id).collect(), typ.clone()); + TypedValue::new(res, Type::Array(Arc::new(vec![typ]), array_length)) + } + + /// Inserts a modulo operation between the index and the array length + /// Returns the id of the index modulo the array length + fn insert_index_mod_array_length( + &mut self, + index: TypedValue, + array: TypedValue, + ) -> TypedValue { + match array.type_of_variable.clone() { + Type::Array(_, array_length) => { + let array_length_id = self + .builder + .numeric_constant(array_length, NumericType::Unsigned { bit_size: 32 }); + let index_mod_length = + self.builder.insert_binary(index.value_id, BinaryOp::Mod, array_length_id); + TypedValue::new(index_mod_length, ValueType::U32.to_ssa_type()) + } + _ => unreachable!("Array type expected"), + } + } + + /// Inserts an array get instruction + /// + /// Index must be u32 + /// If safe_index is true, index will be taken modulo the array length + pub fn insert_array_get( + &mut self, + array: TypedValue, + index: TypedValue, + element_type: Type, + safe_index: bool, + ) -> TypedValue { + assert!(index.type_of_variable == Type::Numeric(NumericType::Unsigned { bit_size: 32 })); + let index = if safe_index { + self.insert_index_mod_array_length(index.clone(), array.clone()) + } else { + index + }; + let res = self.builder.insert_array_get( + array.value_id, + index.value_id, + ArrayOffset::None, + element_type.clone(), + ); + TypedValue::new(res, element_type) + } + + /// Inserts an array set instruction + /// + /// Index must be u32 + /// If safe_index is true, index will be taken modulo the array length + pub fn insert_array_set( + &mut self, + array: TypedValue, + index: TypedValue, + value: TypedValue, + mutable: bool, + safe_index: bool, + ) -> TypedValue { + assert!(matches!(array.type_of_variable, Type::Array(_, _))); + assert!(index.type_of_variable == Type::Numeric(NumericType::Unsigned { bit_size: 32 })); + let index = if safe_index { + self.insert_index_mod_array_length(index.clone(), array.clone()) + } else { + index + }; + let res = self.builder.insert_array_set( + array.value_id, + index.value_id, + value.value_id, + mutable, + ArrayOffset::None, + ); + TypedValue::new(res, array.type_of_variable.clone()) + } }