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 aa88bb115fe..b3eb0032225 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs @@ -2,15 +2,14 @@ use super::instruction::Point as InstructionPoint; use super::instruction::Scalar as InstructionScalar; use super::{ ecdsa::{Curve, generate_ecdsa_signature_and_corrupt_it}, - instruction::{Argument, FunctionInfo, Instruction}, + instruction::{FunctionInfo, Instruction, NumericArgument}, options::SsaBlockOptions, }; use noir_ssa_fuzzer::builder::{FuzzerBuilder, InstructionWithOneArg, InstructionWithTwoArgs}; -use noir_ssa_fuzzer::r#type::{NumericType, Point, Scalar, Type, TypedValue}; +use noir_ssa_fuzzer::typed_value::{NumericType, Point, Scalar, Type, TypedValue}; use noirc_evaluator::ssa::ir::{basic_block::BasicBlockId, function::Function, map::Id}; use std::collections::{HashMap, VecDeque}; use std::iter::zip; -use std::sync::Arc; /// 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 @@ -26,34 +25,6 @@ pub(crate) struct BlockContext { pub(crate) options: SsaBlockOptions, } -/// Returns a typed value from the map -/// Variables are stored in a map with type as key and vector of typed values as value -/// We use modulo to wrap index around the length of the vector, because fuzzer can produce index that is greater than the length of the vector -fn get_typed_value_from_map( - map: &HashMap>, - type_: &Type, - idx: usize, -) -> Option { - let arr = map.get(type_); - arr?; - let arr = arr.unwrap(); - let value = arr.get(idx % arr.len()); - value?; - Some(value.unwrap().clone()) -} - -fn append_typed_value_to_map( - map: &mut HashMap>, - type_: &Type, - value: TypedValue, -) { - map.entry(type_.clone()).or_default().push(value); -} - -fn get_all_arrays_from_map(map: &HashMap>) -> Vec { - map.iter().filter(|(key, _)| key.is_array()).flat_map(|(_, arr)| arr.clone()).collect() -} - impl BlockContext { pub(crate) fn new( stored_variables: HashMap>, @@ -63,19 +34,44 @@ impl BlockContext { Self { stored_variables, parent_blocks_history, children_blocks: Vec::new(), options } } + fn store_variable(&mut self, value: &TypedValue) { + self.stored_variables + .entry(value.type_of_variable.clone()) + .or_default() + .push(value.clone()); + } + + fn get_stored_variable(&self, type_: &Type, index: usize) -> Option { + let arr = self.stored_variables.get(type_)?; + let value = arr.get(index % arr.len())?; + Some(value.clone()) + } + + fn get_stored_arrays(&self) -> Vec { + self.stored_variables + .iter() + .filter(|(key, _)| key.is_array()) + .flat_map(|(_, arr)| arr.clone()) + .collect() + } + + fn get_stored_references_to_type(&self, type_: &Type) -> Vec { + self.stored_variables + .iter() + .filter(|(key, _)| key.is_reference() && key.unwrap_reference() == *type_) + .flat_map(|(_, arr)| arr.clone()) + .collect() + } + /// Inserts an instruction that takes a single argument fn insert_instruction_with_single_arg( &mut self, acir_builder: &mut FuzzerBuilder, brillig_builder: &mut FuzzerBuilder, - arg: Argument, + arg: NumericArgument, instruction: InstructionWithOneArg, ) { - let value = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(arg.numeric_type), - arg.index, - ); + let value = self.get_stored_variable(&Type::Numeric(arg.numeric_type), arg.index); let value = match value { Some(value) => value, _ => return, @@ -83,11 +79,7 @@ impl BlockContext { let acir_result = instruction(acir_builder, value.clone()); // insert to brillig, assert id is the same assert_eq!(acir_result, instruction(brillig_builder, value)); - append_typed_value_to_map( - &mut self.stored_variables, - &acir_result.type_of_variable.clone(), - acir_result, - ); + self.store_variable(&acir_result); } /// Inserts an instruction that takes two arguments @@ -95,21 +87,13 @@ impl BlockContext { &mut self, acir_builder: &mut FuzzerBuilder, brillig_builder: &mut FuzzerBuilder, - lhs: Argument, - rhs: Argument, + lhs: NumericArgument, + rhs: NumericArgument, instruction: InstructionWithTwoArgs, ) { - let instr_lhs = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(lhs.numeric_type), - lhs.index, - ); + let instr_lhs = self.get_stored_variable(&Type::Numeric(lhs.numeric_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_variables, - &Type::Numeric(lhs.numeric_type), - rhs.index, - ); + let instr_rhs = self.get_stored_variable(&Type::Numeric(lhs.numeric_type), rhs.index); let (instr_lhs, instr_rhs) = match (instr_lhs, instr_rhs) { (Some(acir_lhs), Some(acir_rhs)) => (acir_lhs, acir_rhs), _ => return, @@ -117,11 +101,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, instruction(brillig_builder, instr_lhs, instr_rhs)); - append_typed_value_to_map( - &mut self.stored_variables, - &result.type_of_variable.clone(), - result, - ); + self.store_variable(&result); } /// Inserts an instruction into both ACIR and Brillig programs @@ -196,11 +176,7 @@ impl BlockContext { if !self.options.instruction_options.cast_enabled { return; } - let value = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(lhs.numeric_type), - lhs.index, - ); + let value = self.get_stored_variable(&Type::Numeric(lhs.numeric_type), lhs.index); let value = match value { Some(value) => value, _ => return, @@ -210,11 +186,7 @@ impl BlockContext { acir_result, brillig_builder.insert_cast(value.clone(), Type::Numeric(type_)) ); - append_typed_value_to_map( - &mut self.stored_variables, - &acir_result.type_of_variable.clone(), - acir_result, - ); + self.store_variable(&acir_result); } Instruction::Mod { lhs, rhs } => { if !self.options.instruction_options.mod_enabled { @@ -332,16 +304,8 @@ impl BlockContext { Instruction::AddSubConstrain { lhs, rhs } => { // inserts lhs' = lhs + rhs - let lhs_orig = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - lhs, - ); - let rhs = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - rhs, - ); + let lhs_orig = self.get_stored_variable(&Type::Numeric(NumericType::Field), lhs); + let rhs = self.get_stored_variable(&Type::Numeric(NumericType::Field), rhs); let (lhs_orig, rhs) = match (lhs_orig, rhs) { (Some(lhs_orig), Some(rhs)) => (lhs_orig, rhs), _ => return, @@ -371,16 +335,8 @@ impl BlockContext { brillig_builder.insert_constrain(lhs_orig.clone(), morphed.clone()); } Instruction::MulDivConstrain { lhs, rhs } => { - let lhs_orig = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - lhs, - ); - let rhs = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - rhs, - ); + let lhs_orig = self.get_stored_variable(&Type::Numeric(NumericType::Field), lhs); + let rhs = self.get_stored_variable(&Type::Numeric(NumericType::Field), rhs); let (lhs_orig, rhs) = match (lhs_orig, rhs) { (Some(lhs_orig), Some(rhs)) => (lhs_orig, rhs), _ => return, @@ -413,96 +369,67 @@ impl BlockContext { return; } - let value = match get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(lhs.numeric_type), - lhs.index, - ) { + let value = match self.get_stored_variable(&lhs.value_type, lhs.index) { Some(value) => value, _ => return, }; let addr = acir_builder.insert_add_to_memory(value.clone()); assert_eq!(addr, brillig_builder.insert_add_to_memory(value.clone())); - // Append the memory address to stored_values with the type of the result - append_typed_value_to_map( - &mut self.stored_variables, - &addr.type_of_variable.clone(), - addr, - ); + self.store_variable(&addr); } Instruction::LoadFromMemory { memory_addr } => { if !self.options.instruction_options.load_enabled { return; } - let addr = get_typed_value_from_map( - &self.stored_variables, - &Type::Reference(Arc::new(Type::Numeric(memory_addr.numeric_type))), - memory_addr.index, - ); - let addr = match addr { - Some(addr) => addr, - _ => return, - }; - let value = acir_builder.insert_load_from_memory(addr.clone()); - assert_eq!(value, brillig_builder.insert_load_from_memory(addr.clone())); - append_typed_value_to_map( - &mut self.stored_variables, - &value.type_of_variable, - value.clone(), - ); + let addresses = self.get_stored_references_to_type(&memory_addr.value_type); + if addresses.is_empty() { + return; + } + let address = addresses[memory_addr.index % addresses.len()].clone(); + let value = acir_builder.insert_load_from_memory(address.clone()); + assert_eq!(value, brillig_builder.insert_load_from_memory(address.clone())); + self.store_variable(&value); } Instruction::SetToMemory { memory_addr_index, value } => { if !self.options.instruction_options.store_enabled { return; } - let addr = get_typed_value_from_map( - &self.stored_variables, - &Type::Reference(Arc::new(Type::Numeric(value.numeric_type))), - memory_addr_index, - ); - let addr = match addr { - Some(addr) => addr, - _ => return, - }; - let value = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(value.numeric_type), - value.index, - ); - let value = match value { + let addresses = self.get_stored_references_to_type(&value.value_type); + let value = match self.get_stored_variable(&value.value_type, value.index) { Some(value) => value, _ => return, }; - acir_builder.insert_set_to_memory(addr.clone(), value.clone()); - brillig_builder.insert_set_to_memory(addr.clone(), value); + let address = if addresses.is_empty() { + let addr = acir_builder.insert_add_to_memory(value.clone()); + assert_eq!(addr, brillig_builder.insert_add_to_memory(value.clone())); + self.store_variable(&addr); + addr + } else { + addresses[memory_addr_index % addresses.len()].clone() + }; + acir_builder.insert_set_to_memory(address.clone(), value.clone()); + brillig_builder.insert_set_to_memory(address, value); } - Instruction::CreateArray { elements_indices, element_type, is_references } => { + Instruction::CreateArray { elements_indices, element_type } => { // insert to both acir and brillig builders let array = match self.insert_array( acir_builder, brillig_builder, elements_indices, element_type, - is_references, ) { Some(array) => array, _ => return, }; - append_typed_value_to_map( - &mut self.stored_variables, - &array.type_of_variable.clone(), - array, - ); + self.store_variable(&array); } Instruction::ArrayGet { array_index, index, safe_index } => { // insert array get to both acir and brillig builders - let index = match get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(index.numeric_type), - index.index, - ) { + let index = match self + .get_stored_variable(&Type::Numeric(index.numeric_type), index.index) + { Some(index) => index, _ => return, }; @@ -516,11 +443,7 @@ impl BlockContext { ); if let Some((value, is_references)) = value { if !is_references { - append_typed_value_to_map( - &mut self.stored_variables, - &value.type_of_variable.clone(), - value.clone(), - ); + self.store_variable(&value); } else { panic!("References are not supported for array get with dynamic index"); } @@ -528,11 +451,8 @@ impl BlockContext { } Instruction::ArraySet { array_index, index, value_index, safe_index } => { // get the index from the stored variables - let index = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(index.numeric_type), - index.index, - ); + let index = + self.get_stored_variable(&Type::Numeric(index.numeric_type), index.index); let index = match index { Some(index) => index, _ => return, @@ -562,11 +482,7 @@ impl BlockContext { let element_type = new_array.type_of_variable.unwrap_array_element_type(); match element_type { Type::Numeric(_element_type) => { - append_typed_value_to_map( - &mut self.stored_variables, - &new_array.type_of_variable.clone(), - new_array.clone(), - ); + self.store_variable(&new_array); } _ => panic!("Expected NumericType, found {element_type:?}"), } @@ -585,11 +501,7 @@ impl BlockContext { safe_index, ); if let Some((value, _is_references)) = value { - append_typed_value_to_map( - &mut self.stored_variables, - &value.type_of_variable.clone(), - value.clone(), - ); + self.store_variable(&value); } } Instruction::ArraySetWithConstantIndex { @@ -621,11 +533,7 @@ impl BlockContext { !is_references, "Encountered numeric element in an array with references" ); - append_typed_value_to_map( - &mut self.stored_variables, - &new_array.type_of_variable.clone(), - new_array.clone(), - ); + self.store_variable(&new_array); } Type::Reference(type_ref) => { assert!( @@ -636,11 +544,7 @@ impl BlockContext { type_ref.is_numeric(), "Expected reference to a numeric type, found {type_ref:?}" ); - append_typed_value_to_map( - &mut self.stored_variables, - &new_array.type_of_variable.clone(), - new_array.clone(), - ); + self.store_variable(&new_array); } _ => { panic!("Expected NumericType or ReferenceType, found {element_type:?}") @@ -649,11 +553,7 @@ impl BlockContext { } } Instruction::FieldToBytesToField { field_idx } => { - let field = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - field_idx, - ); + let field = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let field = match field { Some(field) => field, _ => return, @@ -662,18 +562,10 @@ impl BlockContext { assert_eq!(bytes, brillig_builder.insert_to_le_radix(field.clone(), 256, 32)); let field = acir_builder.insert_from_le_radix(bytes.clone(), 256); assert_eq!(field, brillig_builder.insert_from_le_radix(bytes.clone(), 256)); - append_typed_value_to_map( - &mut self.stored_variables, - &field.type_of_variable.clone(), - field.clone(), - ); + self.store_variable(&field); } Instruction::Blake2sHash { field_idx, limbs_count } => { - let input = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - field_idx, - ); + let input = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let input = match input { Some(input) => input, _ => return, @@ -690,18 +582,10 @@ impl BlockContext { assert_eq!(hash, brillig_builder.insert_blake2s_hash(bytes.clone())); let hash_as_field = acir_builder.insert_from_le_radix(hash.clone(), 256); assert_eq!(hash_as_field, brillig_builder.insert_from_le_radix(hash.clone(), 256)); - append_typed_value_to_map( - &mut self.stored_variables, - &hash_as_field.type_of_variable.clone(), - hash_as_field.clone(), - ); + self.store_variable(&hash_as_field); } Instruction::Blake3Hash { field_idx, limbs_count } => { - let input = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - field_idx, - ); + let input = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let input = match input { Some(input) => input, _ => return, @@ -718,19 +602,14 @@ impl BlockContext { assert_eq!(hash, brillig_builder.insert_blake3_hash(bytes.clone())); let hash_as_field = acir_builder.insert_from_le_radix(hash.clone(), 256); assert_eq!(hash_as_field, brillig_builder.insert_from_le_radix(hash.clone(), 256)); - append_typed_value_to_map( - &mut self.stored_variables, - &hash_as_field.type_of_variable.clone(), - hash_as_field.clone(), - ); + self.store_variable(&hash_as_field); } Instruction::Keccakf1600Hash { u64_indices, load_elements_of_array } => { let input = match self.insert_array( acir_builder, brillig_builder, u64_indices.to_vec(), - NumericType::U64, - false, + Type::Numeric(NumericType::U64), ) { Some(input) => input, _ => return, @@ -740,11 +619,7 @@ impl BlockContext { hash_array_u64, brillig_builder.insert_keccakf1600_permutation(input.clone()) ); - append_typed_value_to_map( - &mut self.stored_variables, - &hash_array_u64.type_of_variable.clone(), - hash_array_u64.clone(), - ); + self.store_variable(&hash_array_u64); if load_elements_of_array { for i in 0..25_u32 { let index = acir_builder.insert_constant(i, NumericType::U32); @@ -764,11 +639,7 @@ impl BlockContext { /*safe_index =*/ false ) ); - append_typed_value_to_map( - &mut self.stored_variables, - &value.type_of_variable.clone(), - value.clone(), - ); + self.store_variable(&value); } } } @@ -776,27 +647,18 @@ impl BlockContext { if input_limbs_count == 0 { return; } - let input = match get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - input_idx, - ) { - Some(input) => input, - _ => return, - }; - let key = match get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - key_idx, - ) { - Some(key) => key, - _ => return, - }; - let iv = match get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - iv_idx, - ) { + let input = + match self.get_stored_variable(&Type::Numeric(NumericType::Field), input_idx) { + Some(input) => input, + _ => return, + }; + let key = + match self.get_stored_variable(&Type::Numeric(NumericType::Field), key_idx) { + Some(key) => key, + _ => return, + }; + let iv = match self.get_stored_variable(&Type::Numeric(NumericType::Field), iv_idx) + { Some(iv) => iv, _ => return, }; @@ -824,11 +686,7 @@ impl BlockContext { encrypted_as_field, brillig_builder.insert_from_le_radix(encrypted.clone(), 256) ); - append_typed_value_to_map( - &mut self.stored_variables, - &encrypted_as_field.type_of_variable.clone(), - encrypted_as_field.clone(), - ); + self.store_variable(&encrypted_as_field); } Instruction::Sha256Compression { input_indices, @@ -839,8 +697,7 @@ impl BlockContext { acir_builder, brillig_builder, input_indices.to_vec(), - NumericType::U32, - false, + Type::Numeric(NumericType::U32), ) { Some(input) => input, _ => return, @@ -849,8 +706,7 @@ impl BlockContext { acir_builder, brillig_builder, state_indices.to_vec(), - NumericType::U32, - false, + Type::Numeric(NumericType::U32), ) { Some(state) => state, _ => return, @@ -858,11 +714,7 @@ impl BlockContext { let compressed = acir_builder.insert_sha256_compression(input.clone(), state.clone()); assert_eq!(compressed, brillig_builder.insert_sha256_compression(input, state)); - append_typed_value_to_map( - &mut self.stored_variables, - &compressed.type_of_variable.clone(), - compressed.clone(), - ); + self.store_variable(&compressed); if load_elements_of_array { for i in 0..8_u32 { let index = acir_builder.insert_constant(i, NumericType::U32); @@ -882,11 +734,7 @@ impl BlockContext { /*safe_index =*/ false ) ); - append_typed_value_to_map( - &mut self.stored_variables, - &value.type_of_variable, - value.clone(), - ); + self.store_variable(&value); } } } @@ -905,11 +753,7 @@ impl BlockContext { let brillig_point = brillig_builder.point_add(p1, p2); assert_eq!(acir_point, brillig_point); for typed_value in [&acir_point.x, &acir_point.y, &acir_point.is_infinite] { - append_typed_value_to_map( - &mut self.stored_variables, - &typed_value.type_of_variable, - typed_value.clone(), - ); + self.store_variable(typed_value); } } Instruction::MultiScalarMul { points_and_scalars } => { @@ -939,11 +783,7 @@ impl BlockContext { let brillig_point = brillig_builder.multi_scalar_mul(points_vec, scalars_vec); assert_eq!(acir_point, brillig_point); for typed_value in [&acir_point.x, &acir_point.y, &acir_point.is_infinite] { - append_typed_value_to_map( - &mut self.stored_variables, - &typed_value.type_of_variable, - typed_value.clone(), - ); + self.store_variable(typed_value); } } Instruction::EcdsaSecp256r1 { @@ -982,11 +822,7 @@ impl BlockContext { prepared_signature.signature, ) ); - append_typed_value_to_map( - &mut self.stored_variables, - &result.type_of_variable, - result.clone(), - ); + self.store_variable(&result); } Instruction::EcdsaSecp256k1 { msg, @@ -1024,27 +860,15 @@ impl BlockContext { prepared_signature.signature, ) ); - append_typed_value_to_map( - &mut self.stored_variables, - &result.type_of_variable, - result.clone(), - ); + self.store_variable(&result); } } } /// Takes scalar from [`super::instruction::Scalar`] and converts it to [`noir_ssa_fuzzer::typed_value::Scalar`] fn ssa_scalar_from_instruction_scalar(&mut self, scalar: InstructionScalar) -> Option { - let lo = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - scalar.field_lo_idx, - ); - let hi = get_typed_value_from_map( - &self.stored_variables, - &Type::Numeric(NumericType::Field), - scalar.field_hi_idx, - ); + let lo = self.get_stored_variable(&Type::Numeric(NumericType::Field), scalar.field_lo_idx); + let hi = self.get_stored_variable(&Type::Numeric(NumericType::Field), scalar.field_hi_idx); match (lo, hi) { (Some(lo), Some(hi)) => Some(Scalar { lo, hi }), _ => None, @@ -1087,37 +911,16 @@ impl BlockContext { acir_builder: &mut FuzzerBuilder, brillig_builder: &mut FuzzerBuilder, elements_indices: Vec, - element_type: NumericType, - is_references: bool, + element_type: Type, ) -> Option { if !self.options.instruction_options.create_array_enabled { return None; } // 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, - &Type::Numeric(element_type), - *index, - ) - }) - .collect::>>() - } else { - elements_indices - .iter() - .map(|index| { - get_typed_value_from_map( - &self.stored_variables, - &Type::Reference(Arc::new(Type::Numeric(element_type))), - *index, - ) - }) - .collect::>>() - }; - + let elements = elements_indices + .iter() + .map(|index| self.get_stored_variable(&element_type, *index)) + .collect::>>(); let elements = match elements { Some(elements) => elements, _ => return None, @@ -1159,7 +962,7 @@ impl BlockContext { if !safe_index && !self.options.instruction_options.unsafe_get_set_enabled { return None; } - let arrays = get_all_arrays_from_map(&self.stored_variables); + let arrays = self.get_stored_arrays(); if arrays.is_empty() { return None; } @@ -1225,7 +1028,7 @@ impl BlockContext { if !safe_index && !self.options.instruction_options.unsafe_get_set_enabled { return None; } - let arrays = get_all_arrays_from_map(&self.stored_variables); + let arrays = self.get_stored_arrays(); if arrays.is_empty() { return None; } @@ -1242,11 +1045,8 @@ impl BlockContext { if is_array_of_references && !index_is_constant { return None; } - let value = get_typed_value_from_map( - &self.stored_variables, - &array.type_of_variable.unwrap_array_element_type(), - value_index, - ); + let value = self + .get_stored_variable(&array.type_of_variable.unwrap_array_element_type(), value_index); let value = match value { Some(value) => value, _ => return None, @@ -1271,44 +1071,120 @@ impl BlockContext { } } - /// Finalizes the function by setting the return value - pub(crate) fn finalize_block_with_return( - self, + /// Finds values with the given type and index + /// + /// # Arguments + /// + /// * `type_` - Type of the value to find + /// * `index` - Index of the value to find + /// + /// # Returns + /// * TypedValue with the given type and index, if index is provided, otherwise the last value + /// * If no value is found, we create it from predefined boolean + /// + /// # Examples + /// + /// If we want to proceed function call, e.g. f1(&mut a: Field, b: [Field; 3]) + /// We have to find values with type &mut Field and [Field; 3] + /// If variables of such type are not defined, uh ohh.. we need to create it + /// The only thing we know is that one boolean is defined + fn find_values_with_type( + &mut self, acir_builder: &mut FuzzerBuilder, brillig_builder: &mut FuzzerBuilder, - return_type: NumericType, - ) { - let array_of_values_with_return_type = - self.stored_variables.get(&Type::Numeric(return_type)); - let return_value = match array_of_values_with_return_type { - Some(arr) => arr.iter().last(), - _ => None, - }; - match return_value { - Some(return_value) => { - acir_builder.finalize_function(return_value); - brillig_builder.finalize_function(return_value); + type_: &Type, + index: Option, + ) -> TypedValue { + log::debug!("Finding values with type: {type_:?}"); + let values_of_such_type = self.stored_variables.get(type_); + if values_of_such_type.is_some() { + if let Some(index) = index { + let length = values_of_such_type.unwrap().len(); + return values_of_such_type.unwrap().get(index % length).cloned().unwrap(); + } else { + return values_of_such_type.unwrap().last().cloned().unwrap(); } - _ => { - // 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_variables, - &Type::Numeric(NumericType::Boolean), - 0, - ) - .unwrap(); - let return_value = - acir_builder.insert_cast(boolean_value.clone(), Type::Numeric(return_type)); - assert_eq!( - brillig_builder.insert_cast(boolean_value, Type::Numeric(return_type)), - return_value + } + + match type_ { + // On numeric simple cast from boolean + Type::Numeric(_) => { + let boolean_value = + self.get_stored_variable(&Type::Numeric(NumericType::Boolean), 0).unwrap(); + let acir_value = acir_builder.insert_cast(boolean_value.clone(), type_.clone()); + let brillig_value = brillig_builder.insert_cast(boolean_value, type_.clone()); + assert_eq!(acir_value, brillig_value); + self.store_variable(&acir_value); + acir_value + } + // On reference, try to find value with reference type, + // allocate and store it in memory + Type::Reference(reference_type) => { + let value = self.find_values_with_type( + acir_builder, + brillig_builder, + reference_type.as_ref(), + None, ); - acir_builder.finalize_function(&return_value); - brillig_builder.finalize_function(&return_value); + let acir_value = acir_builder.insert_add_to_memory(value.clone()); + let brillig_value = brillig_builder.insert_add_to_memory(value); + assert_eq!(acir_value, brillig_value); + self.store_variable(&acir_value); + acir_value + } + Type::Array(array_type, array_size) => { + let mut values = Vec::with_capacity((*array_size as usize) * array_type.len()); + for _ in 0..*array_size { + let value = array_type.iter().map(|element_type| { + self.find_values_with_type( + acir_builder, + brillig_builder, + element_type, + None, + ) + }); + values.extend(value); + } + let acir_value = acir_builder.insert_array(values.clone()); + let brillig_value = brillig_builder.insert_array(values); + assert_eq!(acir_value, brillig_value); + self.store_variable(&acir_value); + acir_value + } + Type::Slice(slice_type) => { + let values = slice_type + .iter() + .map(|element_type| { + self.find_values_with_type( + acir_builder, + brillig_builder, + element_type, + None, + ) + }) + .collect::>(); + let acir_value = acir_builder.insert_slice(values.clone()); + let brillig_value = brillig_builder.insert_slice(values); + assert_eq!(acir_value, brillig_value); + self.store_variable(&acir_value); + acir_value } } } + /// Finalizes the function by setting the return value + pub(crate) fn finalize_block_with_return( + &mut self, + acir_builder: &mut FuzzerBuilder, + brillig_builder: &mut FuzzerBuilder, + return_type: Type, + ) { + let return_value = + self.find_values_with_type(acir_builder, brillig_builder, &return_type, None); + acir_builder.finalize_function(&return_value); + brillig_builder.finalize_function(&return_value); + } + pub(crate) fn finalize_block_with_jmp( &mut self, acir_builder: &mut FuzzerBuilder, @@ -1356,18 +1232,17 @@ impl BlockContext { assert_eq!(func_as_value_id, brillig_builder.insert_import(function_id)); // Get values from stored_values map by indices - // 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_variables, - &Type::Numeric(value_type), - *index, - ) { - Some(value) => value, - None => return, - }; + // if the length of args is less than the number of input types, we fill it with random values + // don't really like this, but there is no other way to predict it on mutation level + let mut args_to_use = args.to_vec(); + if args.len() < function_signature.input_types.len() { + args_to_use.extend(vec![0; function_signature.input_types.len() - args.len()]); + } + for (value_type, index) in zip(function_signature.input_types, args_to_use) { + let value = + self.find_values_with_type(acir_builder, brillig_builder, &value_type, Some(index)); values.push(value); } @@ -1375,25 +1250,21 @@ impl BlockContext { let ret_val = acir_builder.insert_call( func_as_value_id, &values, - Type::Numeric(function_signature.return_type), + function_signature.return_type.clone(), ); assert_eq!( ret_val, brillig_builder.insert_call( func_as_value_id, &values, - Type::Numeric(function_signature.return_type) + function_signature.return_type.clone() ) ); let typed_ret_val = TypedValue { value_id: ret_val, - type_of_variable: Type::Numeric(function_signature.return_type), + type_of_variable: function_signature.return_type.clone(), }; // Append the return value to stored_values map - append_typed_value_to_map( - &mut self.stored_variables, - &Type::Numeric(function_signature.return_type), - typed_ret_val, - ); + self.store_variable(&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 77cd95154e6..3704dfd8774 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs @@ -9,7 +9,7 @@ use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::Arbitrary; use noir_ssa_fuzzer::{ builder::FuzzerBuilder, - r#type::{NumericType, Type, TypedValue}, + typed_value::{NumericType, Type, TypedValue}, }; use noirc_evaluator::ssa::ir::{basic_block::BasicBlockId, function::Function, map::Id}; use serde::{Deserialize, Serialize}; @@ -25,52 +25,25 @@ const NUMBER_OF_BLOCKS_INSERTING_IN_LOOP: usize = 4; pub(crate) type ValueWithType = (FieldElement, NumericType); -/// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigned integers to represent a field element -/// field = low + high * 2^128 -#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)] -pub(crate) struct FieldRepresentation { - pub(crate) high: u128, - pub(crate) low: u128, -} - -impl From<&FieldRepresentation> for FieldElement { - fn from(field: &FieldRepresentation) -> FieldElement { - let lower = FieldElement::from(field.low); - let upper = FieldElement::from(field.high); - lower + upper * (FieldElement::from(u128::MAX) + FieldElement::from(1_u128)) - } -} - -#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)] -pub(crate) enum WitnessValue { - Field(FieldRepresentation), - U64(u64), - Boolean(bool), - I64(u64), - I32(u32), -} - -impl Default for WitnessValue { - fn default() -> Self { - WitnessValue::Field(FieldRepresentation { high: 0, low: 0 }) - } -} - -/// TODO(sn): initial_witness should be in ProgramData /// Represents the data describing a function -#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct FunctionData { pub(crate) commands: Vec, + /// Input types of the function + /// + /// Overwritten for main function by the types of the initial witness + pub(crate) input_types: Vec, pub(crate) return_instruction_block_idx: usize, - pub(crate) return_type: NumericType, + pub(crate) return_type: Type, } impl Default for FunctionData { fn default() -> Self { FunctionData { commands: vec![], + input_types: vec![Type::Numeric(NumericType::Field)], return_instruction_block_idx: 0, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), } } } @@ -152,7 +125,7 @@ pub(crate) struct FuzzerFunctionContext<'a> { /// Number of iterations of loops in the program parent_iterations_count: usize, - return_type: NumericType, + return_type: Type, defined_functions: BTreeMap, FunctionInfo>, } @@ -160,20 +133,20 @@ impl<'a> FuzzerFunctionContext<'a> { /// Creates a new fuzzer context with the given types /// It creates a new variable for each type and stores it in the map pub(crate) fn new( - types: Vec, + types: Vec, instruction_blocks: &'a Vec, context_options: FunctionContextOptions, - return_type: NumericType, + return_type: Type, defined_functions: BTreeMap, FunctionInfo>, acir_builder: &'a mut FuzzerBuilder, brillig_builder: &'a mut FuzzerBuilder, ) -> Self { let mut acir_ids = HashMap::new(); for type_ in types { - let acir_id = acir_builder.insert_variable(Type::Numeric(type_).into()); - let brillig_id = brillig_builder.insert_variable(Type::Numeric(type_).into()); + let acir_id = acir_builder.insert_variable(type_.clone().into()); + let brillig_id = brillig_builder.insert_variable(type_.clone().into()); assert_eq!(acir_id, brillig_id); - acir_ids.entry(Type::Numeric(type_)).or_insert(Vec::new()).push(acir_id); + acir_ids.entry(type_.clone()).or_insert(Vec::new()).push(acir_id); } let main_block = acir_builder.get_current_block(); @@ -211,7 +184,7 @@ impl<'a> FuzzerFunctionContext<'a> { values_types: Vec, instruction_blocks: &'a Vec, context_options: FunctionContextOptions, - return_type: NumericType, + return_type: Type, defined_functions: BTreeMap, FunctionInfo>, acir_builder: &'a mut FuzzerBuilder, brillig_builder: &'a mut FuzzerBuilder, @@ -943,7 +916,7 @@ impl<'a> FuzzerFunctionContext<'a> { return_block_context.finalize_block_with_return( self.acir_builder, self.brillig_builder, - self.return_type, + self.return_type.clone(), ); } } 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 03aa2c2d9a6..027b1f74956 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 @@ -1,62 +1,44 @@ use super::{ - NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL, - function_context::WitnessValue, fuzzer::{Fuzzer, FuzzerData, FuzzerOutput}, + initial_witness::{ensure_boolean_defined_in_all_functions, initialize_witness_map}, options::FuzzerOptions, }; -use acvm::FieldElement; -use acvm::acir::native_types::{Witness, WitnessMap}; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::Type; -fn initialize_witness_map( - initial_witness: &[WitnessValue; - (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize], -) -> (WitnessMap, Vec, Vec) { - let mut witness_map = WitnessMap::new(); - let mut values = vec![]; - let mut types = vec![]; - for (i, witness_value) in initial_witness.iter().enumerate() { - let (value, type_) = match witness_value { - WitnessValue::Field(field) => (FieldElement::from(field), NumericType::Field), - WitnessValue::U64(u64) => (FieldElement::from(*u64), NumericType::U64), - WitnessValue::Boolean(bool) => (FieldElement::from(*bool as u64), NumericType::Boolean), - WitnessValue::I64(i64) => (FieldElement::from(*i64), NumericType::I64), - WitnessValue::I32(i32) => (FieldElement::from(*i32 as u64), NumericType::I32), - }; - witness_map.insert(Witness(i as u32), value); - values.push(value); - types.push(type_); +fn type_contains_slice_or_reference(type_: &Type) -> bool { + match type_ { + Type::Slice(_) => true, + Type::Reference(_) => true, + Type::Array(arr, _) => arr.iter().any(type_contains_slice_or_reference), + Type::Numeric(_) => false, } - // insert true and false boolean values - witness_map.insert( - Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES), - FieldElement::from(1_u32), - ); - values.push(FieldElement::from(1_u32)); - types.push(NumericType::Boolean); - witness_map.insert( - Witness(NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES + 1), - FieldElement::from(0_u32), - ); - values.push(FieldElement::from(0_u32)); - types.push(NumericType::Boolean); - (witness_map, values, types) } /// Creates ACIR and Brillig programs from the data, runs and compares them pub(crate) fn fuzz_target(data: FuzzerData, options: FuzzerOptions) -> Option { - // to triage if data.instruction_blocks.is_empty() { return None; } + if data.functions.is_empty() { + return None; + } log::debug!("instruction_blocks: {:?}", data.instruction_blocks); + log::debug!("initial_witness: {:?}", data.initial_witness); let (witness_map, values, types) = initialize_witness_map(&data.initial_witness); + let mut data = data; + data.functions[0].input_types = types; + ensure_boolean_defined_in_all_functions(&mut data); + + if type_contains_slice_or_reference(&data.functions[0].return_type) { + // main cannot return a reference + data.functions[0].return_type = Type::default(); + } let mut fuzzer = Fuzzer::new(data.instruction_blocks, values, options); for func in data.functions { - log::debug!("initial_witness: {witness_map:?}"); log::debug!("commands: {:?}", func.commands); - fuzzer.process_function(func, types.clone()); + log::debug!("input_types: {:?}", func.input_types); + fuzzer.process_function(func.clone(), func.input_types.clone()); } fuzzer.finalize_and_run(witness_map) } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs index 597e94c796c..4618b8b8fc5 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs @@ -14,29 +14,27 @@ //! - If one program fails to compile but the other executes successfully use super::{ - NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL, - function_context::{FunctionData, WitnessValue}, + function_context::FunctionData, + initial_witness::WitnessValue, instruction::InstructionBlock, options::{FuzzerMode, FuzzerOptions}, program_context::{FuzzerProgramContext, program_context_by_mode}, }; use acvm::FieldElement; use acvm::acir::native_types::{WitnessMap, WitnessStack}; -use libfuzzer_sys::{arbitrary, arbitrary::Arbitrary}; use noir_ssa_executor::runner::execute_single; use noir_ssa_fuzzer::{ runner::{CompareResults, run_and_compare}, - r#type::NumericType, + typed_value::Type, }; use noirc_driver::CompiledProgram; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; -#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct FuzzerData { pub(crate) functions: Vec, - pub(crate) initial_witness: - [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize], + pub(crate) initial_witness: Vec, pub(crate) instruction_blocks: Vec, } @@ -44,8 +42,7 @@ impl Default for FuzzerData { fn default() -> Self { FuzzerData { functions: vec![FunctionData::default()], - initial_witness: [WitnessValue::default(); - (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize], + initial_witness: vec![WitnessValue::default()], instruction_blocks: vec![], } } @@ -62,9 +59,10 @@ pub(crate) struct FuzzerOutput { } impl FuzzerOutput { - pub(crate) fn get_return_value(&self) -> FieldElement { - let return_witness = self.program.program.functions[0].return_values.0.first().unwrap(); - self.witness_stack.peek().unwrap().witness[return_witness] + pub(crate) fn get_return_values(&self) -> Vec { + let return_witnesses = &self.program.program.functions[0].return_values.0; + let witness_vec = &self.witness_stack.peek().unwrap().witness; + return_witnesses.iter().map(|witness| witness_vec[witness]).collect() } } @@ -86,11 +84,7 @@ impl Fuzzer { Self { contexts } } - pub(crate) fn process_function( - &mut self, - function_data: FunctionData, - types: Vec, - ) { + pub(crate) fn process_function(&mut self, function_data: FunctionData, types: Vec) { for context in &mut self.contexts { context.process_function(function_data.clone(), types.clone()); } @@ -111,7 +105,9 @@ impl Fuzzer { } let results_set = execution_results .values() - .map(|result| -> Option { result.as_ref().map(|r| r.get_return_value()) }) + .map(|result| -> Option> { + result.as_ref().map(|r| r.get_return_values()) + }) .collect::>(); if results_set.len() != 1 { @@ -119,7 +115,7 @@ impl Fuzzer { for (mode, result) in execution_results { if let Some(result) = result { panic_string - .push_str(&format!("Mode {mode:?}: {:?}\n", result.get_return_value())); + .push_str(&format!("Mode {mode:?}: {:?}\n", result.get_return_values())); } else { panic_string.push_str(&format!("Mode {mode:?} failed\n")); } @@ -193,14 +189,12 @@ impl Fuzzer { Some(FuzzerOutput { witness_stack: result, program: acir_program }) } CompareResults::Disagree(acir_return_value, brillig_return_value) => { - let acir_return_witness = - acir_program.program.functions[0].return_values.0.first().unwrap(); - let brillig_return_witness = - brillig_program.program.functions[0].return_values.0.first().unwrap(); - let acir_return_value = - acir_return_value.peek().unwrap().witness[acir_return_witness]; - let brillig_return_value = - brillig_return_value.peek().unwrap().witness[brillig_return_witness]; + let acir_fuzzer_output = + FuzzerOutput { witness_stack: acir_return_value, program: acir_program }; + let brillig_fuzzer_output = + FuzzerOutput { witness_stack: brillig_return_value, program: brillig_program }; + let acir_return_value = acir_fuzzer_output.get_return_values(); + let brillig_return_value = brillig_fuzzer_output.get_return_values(); panic!( "ACIR and Brillig programs returned different results: \ ACIR returned {acir_return_value:?}, Brillig returned {brillig_return_value:?}" diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/initial_witness.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/initial_witness.rs new file mode 100644 index 00000000000..936c367ba49 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/initial_witness.rs @@ -0,0 +1,167 @@ +//! This file describes initial witness passed to the program + +use super::fuzzer::FuzzerData; +use acvm::FieldElement; +use acvm::acir::native_types::{Witness, WitnessMap}; +use libfuzzer_sys::arbitrary; +use libfuzzer_sys::arbitrary::Arbitrary; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// Field modulus has 254 bits, and FieldElement::from supports u128, so we use two unsigned integers to represent a field element +/// field = low + high * 2^128 +#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)] +pub(crate) struct FieldRepresentation { + pub(crate) high: u128, + pub(crate) low: u128, +} + +impl From<&FieldRepresentation> for FieldElement { + fn from(field: &FieldRepresentation) -> FieldElement { + let lower = FieldElement::from(field.low); + let upper = FieldElement::from(field.high); + lower + upper * (FieldElement::from(u128::MAX) + FieldElement::from(1_u128)) + } +} + +#[derive(Debug, Clone, Copy, Hash, Arbitrary, Serialize, Deserialize)] +pub(crate) enum WitnessValueNumeric { + Field(FieldRepresentation), + U128(u128), + U64(u64), + U32(u32), + U16(u16), + U8(u8), + Boolean(bool), + I64(u64), + I32(u32), + I16(u16), + I8(u8), +} + +impl From for NumericType { + fn from(value: WitnessValueNumeric) -> Self { + match value { + WitnessValueNumeric::Field(_) => NumericType::Field, + WitnessValueNumeric::U128(_) => NumericType::U128, + WitnessValueNumeric::U64(_) => NumericType::U64, + WitnessValueNumeric::U32(_) => NumericType::U32, + WitnessValueNumeric::U16(_) => NumericType::U16, + WitnessValueNumeric::U8(_) => NumericType::U8, + WitnessValueNumeric::Boolean(_) => NumericType::Boolean, + WitnessValueNumeric::I64(_) => NumericType::I64, + WitnessValueNumeric::I32(_) => NumericType::I32, + WitnessValueNumeric::I16(_) => NumericType::I16, + WitnessValueNumeric::I8(_) => NumericType::I8, + } + } +} + +impl From for FieldElement { + fn from(value: WitnessValueNumeric) -> Self { + match value { + WitnessValueNumeric::Field(field) => FieldElement::from(&field), + WitnessValueNumeric::U128(u128) => FieldElement::from(u128), + WitnessValueNumeric::U64(u64) => FieldElement::from(u64), + WitnessValueNumeric::U32(u32) => FieldElement::from(u32), + WitnessValueNumeric::U16(u16) => FieldElement::from(u16 as u64), + WitnessValueNumeric::U8(u8) => FieldElement::from(u8 as u64), + WitnessValueNumeric::Boolean(bool) => FieldElement::from(bool), + WitnessValueNumeric::I64(i64) => FieldElement::from(i64), + WitnessValueNumeric::I32(i32) => FieldElement::from(i32 as u64), + WitnessValueNumeric::I16(i16) => FieldElement::from(i16 as u64), + WitnessValueNumeric::I8(i8) => FieldElement::from(i8 as u64), + } + } +} + +impl Default for WitnessValueNumeric { + fn default() -> Self { + WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 0 }) + } +} + +#[derive(Debug, Clone, Hash, Arbitrary, Serialize, Deserialize)] +pub(crate) enum WitnessValue { + Numeric(WitnessValueNumeric), + Array(Vec), +} + +impl Default for WitnessValue { + fn default() -> Self { + WitnessValue::Numeric(WitnessValueNumeric::default()) + } +} + +impl WitnessValue { + fn to_ssa_type(&self) -> Type { + match self { + WitnessValue::Numeric(numeric) => Type::Numeric(NumericType::from(*numeric)), + WitnessValue::Array(arr) => { + let ssa_type = arr + .iter() + .map(|v| v.to_ssa_type()) + .reduce(|a, b| { + assert_eq!(a, b, "All SSA types in the array must be the same"); + a + }) + .unwrap(); + Type::Array(Arc::new(vec![ssa_type; 1]), arr.len() as u32) + } + } + } +} + +fn initialize_witness_map_internal(witness: &[WitnessValue]) -> (Vec, Vec) { + let mut types = vec![]; + let mut witness_vec = vec![]; + for witness_value in witness.iter() { + match witness_value { + WitnessValue::Numeric(numeric) => { + witness_vec.push(FieldElement::from(*numeric)); + types.push(witness_value.to_ssa_type()); + } + WitnessValue::Array(arr) => { + let type_ = witness_value.to_ssa_type(); + types.push(type_); + for val in arr { + // types of inner arrays are ignored, because they are already added to the types vector + let (values, _types) = initialize_witness_map_internal(&[val.clone()]); + witness_vec.extend(values); + } + } + }; + } + (witness_vec, types) +} + +/// Initializes [`WitnessMap`] from [`WitnessValue`] +pub(crate) fn initialize_witness_map( + initial_witness: &[WitnessValue], +) -> (WitnessMap, Vec, Vec) { + let (mut witness_vec, mut types) = initialize_witness_map_internal(initial_witness); + // add true and false boolean values + witness_vec.push(FieldElement::from(1_u32)); + types.push(Type::Numeric(NumericType::Boolean)); + witness_vec.push(FieldElement::from(0_u32)); + types.push(Type::Numeric(NumericType::Boolean)); + let mut witness_map = WitnessMap::new(); + for (i, value) in witness_vec.iter().enumerate() { + witness_map.insert(Witness(i as u32), *value); + } + (witness_map, witness_vec, types) +} + +/// Ensures that boolean is defined in all functions +/// +/// If boolean is not defined in the function, it is added to the input types +pub(crate) fn ensure_boolean_defined_in_all_functions(data: &mut FuzzerData) { + for func in &mut data.functions { + let boolean_presented_in_input_types = + func.input_types.iter().any(|t| t == &Type::Numeric(NumericType::Boolean)); + if !boolean_presented_in_input_types { + func.input_types.push(Type::Numeric(NumericType::Boolean)); + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs index c72c516a531..480aacd6c31 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs @@ -1,6 +1,6 @@ use libfuzzer_sys::arbitrary; use libfuzzer_sys::arbitrary::Arbitrary; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; use serde::{Deserialize, Serialize}; use strum_macros::EnumCount; #[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] @@ -10,7 +10,7 @@ pub(crate) struct Array { } #[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize)] -pub(crate) struct Argument { +pub(crate) struct NumericArgument { /// Index of the argument in the context of stored variables of this type /// e.g. if we have variables with ids [0, 1] in u64 vector and variables with ids [5, 8] in fields vector /// Argument(Index(0), ValueType::U64) -> id 0 @@ -21,6 +21,12 @@ pub(crate) struct Argument { pub(crate) numeric_type: NumericType, } +#[derive(Arbitrary, Debug, Clone, Serialize, Deserialize)] +pub(crate) struct Argument { + pub(crate) index: usize, + pub(crate) value_type: Type, +} + #[derive(Arbitrary, Debug, Clone, Copy, Serialize, Deserialize, Default)] pub(crate) struct Scalar { pub(crate) field_lo_idx: usize, @@ -44,34 +50,34 @@ pub(crate) type PointAndScalar = (Point, Scalar); #[derive(Arbitrary, Debug, Clone, Serialize, Deserialize, EnumCount)] pub(crate) enum Instruction { /// Addition of two values - AddChecked { lhs: Argument, rhs: Argument }, + AddChecked { lhs: NumericArgument, rhs: NumericArgument }, /// Subtraction of two values - SubChecked { lhs: Argument, rhs: Argument }, + SubChecked { lhs: NumericArgument, rhs: NumericArgument }, /// Multiplication of two values - MulChecked { lhs: Argument, rhs: Argument }, + MulChecked { lhs: NumericArgument, rhs: NumericArgument }, /// Division of two values - Div { lhs: Argument, rhs: Argument }, + Div { lhs: NumericArgument, rhs: NumericArgument }, /// Equality comparison - Eq { lhs: Argument, rhs: Argument }, + Eq { lhs: NumericArgument, rhs: NumericArgument }, /// Modulo operation - Mod { lhs: Argument, rhs: Argument }, + Mod { lhs: NumericArgument, rhs: NumericArgument }, /// Bitwise NOT - Not { lhs: Argument }, + Not { lhs: NumericArgument }, /// Left shift - Shl { lhs: Argument, rhs: Argument }, + Shl { lhs: NumericArgument, rhs: NumericArgument }, /// Right shift - Shr { lhs: Argument, rhs: Argument }, + Shr { lhs: NumericArgument, rhs: NumericArgument }, /// Cast into type - Cast { lhs: Argument, type_: NumericType }, + Cast { lhs: NumericArgument, type_: NumericType }, /// Bitwise AND - And { lhs: Argument, rhs: Argument }, + And { lhs: NumericArgument, rhs: NumericArgument }, /// Bitwise OR - Or { lhs: Argument, rhs: Argument }, + Or { lhs: NumericArgument, rhs: NumericArgument }, /// Bitwise XOR - Xor { lhs: Argument, rhs: Argument }, + Xor { lhs: NumericArgument, rhs: NumericArgument }, /// Less than comparison - Lt { lhs: Argument, rhs: Argument }, + Lt { lhs: NumericArgument, rhs: NumericArgument }, /// constrain(lhs == lhs + rhs - rhs), doesn't insert constraint if idempotent_morphing_enabled=false /// uses only fields variables @@ -90,18 +96,19 @@ pub(crate) enum Instruction { LoadFromMemory { memory_addr: Argument }, /// Store value to mutable memory /// Stores value to memory with insert_store + /// If reference to this type is not found, allocates memory for it and stores value to it 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: NumericType, is_references: bool }, + CreateArray { elements_indices: Vec, element_type: Type }, /// 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 }, + ArrayGet { array_index: usize, index: NumericArgument, 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, safe_index: bool }, + ArraySet { array_index: usize, index: NumericArgument, value_index: usize, 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 }, @@ -185,8 +192,8 @@ pub(crate) enum Instruction { impl Default for Instruction { fn default() -> Self { Self::Xor { - lhs: Argument { index: 0, numeric_type: NumericType::Boolean }, - rhs: Argument { index: 0, numeric_type: NumericType::Boolean }, + lhs: NumericArgument { index: 0, numeric_type: NumericType::Boolean }, + rhs: NumericArgument { index: 0, numeric_type: NumericType::Boolean }, } } } @@ -200,8 +207,8 @@ pub(crate) struct InstructionBlock { #[derive(Clone)] pub(crate) struct FunctionInfo { - pub(crate) input_types: Vec, - pub(crate) return_type: NumericType, + pub(crate) input_types: Vec, + pub(crate) return_type: Type, /// Max size of unrolled loops in the function pub(crate) max_unrolled_size: usize, } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs index c06df60d713..3c510348308 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod ecdsa; pub(crate) mod function_context; pub(crate) mod fuzz_target_lib; pub(crate) mod fuzzer; +pub(crate) mod initial_witness; pub(crate) mod instruction; pub(crate) mod options; pub(crate) mod program_context; @@ -11,5 +12,3 @@ pub(crate) mod program_context; mod tests; pub(crate) const NUMBER_OF_VARIABLES_INITIAL: u32 = 7; -/// Numbers of variables that are predefined in the fuzzer -pub(crate) const NUMBER_OF_PREDEFINED_VARIABLES: u32 = 2; diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs index f4833c8f343..1c7e4caaf39 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs @@ -53,7 +53,7 @@ impl Default for InstructionOptions { create_array_enabled: true, array_get_enabled: true, array_set_enabled: true, - unsafe_get_set_enabled: false, + unsafe_get_set_enabled: true, point_add_enabled: true, multi_scalar_mul_enabled: true, ecdsa_secp256r1_enabled: true, @@ -159,7 +159,7 @@ impl Default for FuzzerOptions { fn default() -> Self { Self { compile_options: CompileOptions { - show_ssa: false, + show_ssa: true, show_ssa_pass: vec![], ..Default::default() }, diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs index c5d9ac48f0c..442504b918d 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/program_context.rs @@ -6,7 +6,7 @@ use super::{ use acvm::FieldElement; use noir_ssa_fuzzer::{ builder::{FuzzerBuilder, FuzzerBuilderError}, - r#type::NumericType, + typed_value::Type, }; use noirc_driver::CompiledProgram; use noirc_evaluator::ssa::ir::{function::Function, map::Id}; @@ -15,7 +15,7 @@ use std::collections::BTreeMap; struct StoredFunction { id: Id, function: FunctionData, - types: Vec, + types: Vec, } /// FuzzerProgramContext is a context for storing and processing SSA functions @@ -98,12 +98,12 @@ impl FuzzerProgramContext { } /// Stores function and its signature - pub(crate) fn process_function(&mut self, function: FunctionData, types: Vec) { + pub(crate) fn process_function(&mut self, function: FunctionData, types: Vec) { // leaving max_unrolled_size = 0 for now // let signature = FunctionInfo { input_types: types.clone(), - return_type: function.return_type, + return_type: function.return_type.clone(), max_unrolled_size: 0, }; self.function_information.insert(self.current_function_id, signature); @@ -235,11 +235,11 @@ impl FuzzerProgramContext { self.values .iter() .zip(stored_function.types.iter()) - .map(|(value, type_)| (*value, *type_)) + .map(|(value, type_)| (*value, type_.unwrap_numeric())) .collect(), &self.instruction_blocks, self.program_context_options.clone(), - stored_function.function.return_type, + stored_function.function.return_type.clone(), defined_functions, &mut self.acir_builder, &mut self.brillig_builder, @@ -249,7 +249,7 @@ impl FuzzerProgramContext { stored_function.types.to_vec(), &self.instruction_blocks, self.program_context_options.clone(), - stored_function.function.return_type, + stored_function.function.return_type.clone(), defined_functions, &mut self.acir_builder, &mut self.brillig_builder, diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/advanced_references.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/advanced_references.rs new file mode 100644 index 00000000000..39cc4ff7c5e --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/advanced_references.rs @@ -0,0 +1,172 @@ +//! This file contains tests for advanced references. +//! 1) Test that other function mutates reference +use crate::function_context::{FunctionData, FuzzerFunctionCommand}; +use crate::fuzz_target_lib::fuzz_target; +use crate::fuzzer::FuzzerData; +use crate::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; +use crate::options::FuzzerOptions; +use crate::tests::common::{default_input_types, default_witness}; +use acvm::FieldElement; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; +use std::sync::Arc; + +/// fn main(a: Field) -> pub Field { +/// let mut t = a; +/// func(&mut t); +/// t +/// } +/// +/// fn func(a: &mut Field) { +/// *a += 1; +/// } +/// "a" = 0 +/// [nargo_tests] Circuit output: 0x01 +#[test] +fn test_other_function_mutates_reference() { + let _ = env_logger::try_init(); + let arg_0_numeric = NumericArgument { index: 0, numeric_type: NumericType::Field }; + let arg_1_numeric = NumericArgument { index: 1, numeric_type: NumericType::Field }; + + let arg_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; + let arg_2 = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; + let add_to_memory_block = + InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_0 }] }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; + let load_block = InstructionBlock { + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + }; + let set_block = InstructionBlock { + instructions: vec![Instruction::SetToMemory { memory_addr_index: 0, value: arg_2 }], + }; + let add_block = InstructionBlock { + instructions: vec![Instruction::AddChecked { lhs: arg_0_numeric, rhs: arg_1_numeric }], + }; + let instruction_blocks = vec![add_to_memory_block, load_block, set_block, add_block]; + let main_commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, // add v0 to memory + FuzzerFunctionCommand::InsertFunctionCall { function_idx: 0, args: [0, 1, 2, 3, 4, 5, 6] }, // call func(&mut v0, v1) other args ignored + ]; + let func_commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 1 }, // v3 := load v0 from memory + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 3 }, // v4 := add v3 v1 + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 2 }, // set v4 to memory v0 + ]; + + let main_function = FunctionData { + input_types: default_input_types(), + commands: main_commands, + return_instruction_block_idx: 1, // load v0 from memory + return_type: Type::Numeric(NumericType::Field), + }; + let func_function = FunctionData { + input_types: vec![ + Type::Reference(Arc::new(Type::Numeric(NumericType::Field))), + Type::Numeric(NumericType::Field), + ], + commands: func_commands, + return_instruction_block_idx: 1, // dummy + return_type: Type::Numeric(NumericType::Field), + }; + + let data = FuzzerData { + functions: vec![main_function, func_function], + initial_witness: default_witness(), + instruction_blocks, + }; + + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => { + assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)); + } + None => { + panic!("Program failed to execute"); + } + } +} + +/// let t = &mut(&mut a); +/// *t = &mut b; +/// return **t; +/// This test is compiled into +/// 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 v1 at v8 +/// v9 = allocate -> &mut &mut Field +/// store v7 at v9 +/// store v8 at v9 +/// v10 = load v9 -> &mut Field +/// v11 = load v10 -> Field +/// jmp b1() +/// b1(): +/// store v8 at v9 // dummy +/// return v11 +/// } +#[test] +fn test_reference_to_reference() { + let arg_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; + let arg_1 = Argument { index: 1, value_type: Type::Numeric(NumericType::Field) }; + let typed_memory_0 = Argument { + index: 0, + value_type: Type::Reference(Arc::new(Type::Numeric(NumericType::Field))), + }; + let typed_memory_1 = Argument { + index: 1, + value_type: Type::Reference(Arc::new(Type::Numeric(NumericType::Field))), + }; + let add_to_memory_block = InstructionBlock { + instructions: vec![ + Instruction::AddToMemory { lhs: arg_0 }, // &mut a + Instruction::AddToMemory { lhs: arg_1 }, // &mut b + Instruction::AddToMemory { lhs: typed_memory_0 }, // &mut (&mut a) + ], + }; + let set_to_memory_block = InstructionBlock { + instructions: vec![Instruction::SetToMemory { + memory_addr_index: 0, + value: typed_memory_1, + }], + }; // sets typed_memory_0 to memory (&mut &mut b) + + let reference_arg_0 = Argument { + index: 0, + value_type: Type::Reference(Arc::new(Type::Numeric(NumericType::Field))), + }; + let typed_memory_2 = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; + let load_block = InstructionBlock { + instructions: vec![ + Instruction::LoadFromMemory { memory_addr: reference_arg_0 }, // t = *(&mut &mut b) + Instruction::LoadFromMemory { memory_addr: typed_memory_2 }, // r = *t == **(&mut &mut b) + ], + }; // loads reference_arg_0 from memory (&mut &mut b) and loads typed_memory_2 from memory (**(&mut &mut b)) + + let instruction_blocks = vec![add_to_memory_block, set_to_memory_block, load_block]; + let main_commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 1 }, + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 2 }, + ]; + let main_function = FunctionData { + input_types: default_input_types(), + commands: main_commands, + return_instruction_block_idx: 1, // load v0 from memory + return_type: Type::Numeric(NumericType::Field), + }; + let data = FuzzerData { + functions: vec![main_function], + initial_witness: default_witness(), + instruction_blocks, + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => { + assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)); + } + None => { + panic!("Program failed to execute"); + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/arrays.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/arrays.rs index da2c64659b2..b52308f6c22 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/arrays.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/arrays.rs @@ -5,11 +5,12 @@ use crate::function_context::{FunctionData, FuzzerFunctionCommand}; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; +use std::sync::Arc; /// Test array get and set /// fn main f0 { @@ -27,13 +28,12 @@ use noir_ssa_fuzzer::r#type::NumericType; /// } #[test] fn array_get_and_set() { - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; + let arg_0_field = NumericArgument { index: 0, numeric_type: NumericType::Field }; // create array [v0, v1] let create_array_block = InstructionBlock { instructions: vec![Instruction::CreateArray { elements_indices: vec![0, 1, 2, 3, 4], - element_type: NumericType::Field, - is_references: false, + element_type: Type::Numeric(NumericType::Field), }], }; // create new array setting new_array[0] = v4 @@ -58,8 +58,12 @@ fn array_get_and_set() { FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 1 }, ]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 2, return_type: NumericType::Field }; + let main_func = FunctionData { + commands, + input_types: default_input_types(), + return_instruction_block_idx: 2, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -67,7 +71,7 @@ fn array_get_and_set() { }; let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(4_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(4_u32)), None => panic!("Program failed to execute"), } } @@ -91,9 +95,9 @@ fn array_get_and_set() { #[test] fn test_reference_in_array() { let _ = env_logger::try_init(); - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; - let arg_1_field = Argument { index: 1, numeric_type: NumericType::Field }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; + let arg_0_field = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; + let arg_1_field = Argument { index: 1, value_type: Type::Numeric(NumericType::Field) }; + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_0_field }] }; @@ -113,8 +117,7 @@ fn test_reference_in_array() { let create_array_block = InstructionBlock { instructions: vec![Instruction::CreateArray { elements_indices: vec![0, 1, 2], - element_type: NumericType::Field, - is_references: true, + element_type: Type::Reference(Arc::new(Type::Numeric(NumericType::Field))), }], }; let get_from_array_block = InstructionBlock { @@ -124,9 +127,9 @@ fn test_reference_in_array() { safe_index: true, }], }; - let typed_memory_2 = Argument { index: 2, numeric_type: NumericType::Field }; + let typed_memory_2 = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; let load_from_memory_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_2 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_2.clone() }], }; let instructions_blocks = vec![ add_to_memory_block, @@ -145,16 +148,143 @@ fn test_reference_in_array() { FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 4 }, FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 5 }, ]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 6, return_type: NumericType::Field }; + let main_func = FunctionData { + commands, + input_types: default_input_types(), + return_instruction_block_idx: 6, + return_type: Type::Numeric(NumericType::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_values()[0], FieldElement::from(1_u32)), + None => panic!("Program failed to execute"), + } +} + +/// Previously the fuzzer generated arrays of incorrect sizes with find_values_with_type +/// See https://github.com/noir-lang/noir/issues/9678 +/// +/// fn main(v0: u1, v1: u1) -> pub u1 { +/// func([v0, v0], v1 as u32) +/// } +/// +/// fn func(v0: [u1; 2], index: u32) -> u1 { +/// v0[index] +/// } +/// +/// This test is compiled into the following SSA: +/// Note: the array (argument to f1) is auto generated by find_values_with_type +/// brillig(inline) fn main f0 { +/// b0(v0: Field, v1: Field, v2: Field, v3: Field, v4: Field, v5: u1, v6: u1): +/// v7 = make_array [v6, v6] : [u1; 2] // this array is auto generated by find_values_with_type +/// v8 = cast v6 as u32 +/// v10 = call f1(v7, v8, v5) -> u1 +/// jmp b1() +/// b1(): +/// v11 = make_array [v6, v6] : [u1; 2] +/// return v10 +/// } +/// brillig(inline) fn f1 f1 { +/// b0(v0: [u1; 2], v1: u32, v2: u1): +/// jmp b1() +/// b1(): +/// v3 = truncate v1 to 1 bits, max_bit_size: 32 +/// v4 = array_get v0, index v3 -> u1 +/// return v4 +/// } +#[test] +fn regression_fuzzer_generated_wrong_arrays() { + let _ = env_logger::try_init(); + let arg_1_bool = NumericArgument { index: 1, numeric_type: NumericType::Boolean }; + let arg_0_u32 = NumericArgument { index: 0, numeric_type: NumericType::U32 }; + let cast_block = InstructionBlock { + instructions: vec![Instruction::Cast { lhs: arg_1_bool, type_: NumericType::U32 }], + }; + let get_from_array_block = InstructionBlock { + instructions: vec![Instruction::ArrayGet { + array_index: 0, + index: arg_0_u32, + safe_index: true, + }], + }; + let instructions_blocks = vec![cast_block, get_from_array_block]; + let main_commands = vec![ + FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, // cast + FuzzerFunctionCommand::InsertFunctionCall { function_idx: 0, args: [0, 1, 2, 3, 4, 5, 6] }, // call func([v0, v0], v1 as u32), random args + ]; + let main_func = FunctionData { + commands: main_commands, + input_types: default_input_types(), + return_instruction_block_idx: 0, // dummy + return_type: Type::Numeric(NumericType::Boolean), + }; + let func_func = FunctionData { + commands: vec![], + input_types: vec![ + Type::Array(Arc::new(vec![Type::Numeric(NumericType::Boolean)]), 2), + Type::Numeric(NumericType::U32), + ], + return_instruction_block_idx: 1, // get from array + return_type: Type::Numeric(NumericType::Boolean), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, + functions: vec![main_func, func_func], + initial_witness: default_witness(), + }; + let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); + match result { + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(0_u32)), + None => panic!("Program failed to execute"), + } +} + +/// Test that creating array of arrays works +/// fn main f0 { +/// b0(v0: Field, v1: Field, v2: Field, v3: Field, v4: Field, v5: u1, v6: u1): +/// v7 = make_array [v0, v1, v2] : [Field; 3] +/// v8 = make_array [v7, v7] : [[Field; 3]; 2] +/// return v8 +/// } +/// output should be 0,1,2,0,1,2 +#[test] +fn test_create_array_of_arrays() { + let _ = env_logger::try_init(); + + let array_type = Type::Array(Arc::new(vec![Type::Numeric(NumericType::Field)]), 3); + let array_of_arrays_type = Type::Array(Arc::new(vec![array_type.clone()]), 2); + let create_arrays_block = InstructionBlock { + instructions: vec![ + Instruction::CreateArray { + elements_indices: vec![0, 1, 2], + element_type: Type::Numeric(NumericType::Field), + }, + Instruction::CreateArray { elements_indices: vec![0, 1, 2], element_type: array_type }, + ], + }; + let main_commands = + vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }]; + let main_func = FunctionData { + commands: main_commands, + input_types: default_input_types(), + return_instruction_block_idx: 1, + return_type: array_of_arrays_type, + }; + let fuzzer_data = FuzzerData { + instruction_blocks: vec![create_arrays_block], functions: vec![main_func], initial_witness: default_witness(), }; + let expected_return_value = + (0..3).map(|i| FieldElement::from(i as u32)).collect::>().repeat(2); let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1_u32)), + Some(result) => assert_eq!(result.get_return_values(), expected_return_value), None => panic!("Program failed to execute"), } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_tests.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_tests.rs index 1e824a07a94..8b5b715ec72 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_tests.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_tests.rs @@ -3,14 +3,16 @@ //! 2) jmpif //! 3) mutable variable //! 4) Test that from_le_radix(to_le_radix(field)) == field +//! 5) Test that function can return array use crate::function_context::{FunctionData, FuzzerFunctionCommand}; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; +use std::sync::Arc; /// Test basic field addition: field_0 + field_1 = 1 #[test] @@ -18,8 +20,8 @@ fn test_field_addition_zero_plus_one() { let _ = env_logger::try_init(); // Create arguments referencing the first two fields from default_witness - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; // Field(0) - let arg_1_field = Argument { index: 1, numeric_type: NumericType::Field }; // Field(1) + let arg_0_field = NumericArgument { index: 0, numeric_type: NumericType::Field }; // Field(0) + let arg_1_field = NumericArgument { index: 1, numeric_type: NumericType::Field }; // Field(1) // Create an instruction block that adds field_0 + field_1 let add_block = InstructionBlock { @@ -28,9 +30,10 @@ fn test_field_addition_zero_plus_one() { // Create function that executes the addition and returns the result let main_function = FunctionData { + input_types: default_input_types(), commands: vec![], // No additional commands needed return_instruction_block_idx: 0, // Return the result of the add block - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; // Create the fuzzer data with our test setup @@ -46,12 +49,12 @@ fn test_field_addition_zero_plus_one() { // Verify the result match result { Some(result) => { - assert_eq!(result.get_return_value(), FieldElement::from(1_u32)); + assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)); println!( "✓ Test passed: field_0 + field_1 = {} + {} = {}", 0, 1, - result.get_return_value() + result.get_return_values()[0] ); } None => panic!("Program failed to execute"), @@ -70,8 +73,8 @@ fn test_field_addition_zero_plus_one() { /// we expect that first program succeeds, second program fails #[test] fn test_jmp_if() { - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; - let arg_1_field = Argument { index: 1, numeric_type: NumericType::Field }; + let arg_0_field = NumericArgument { index: 0, numeric_type: NumericType::Field }; + let arg_1_field = NumericArgument { index: 1, numeric_type: NumericType::Field }; let failing_block = InstructionBlock { instructions: vec![Instruction::Div { lhs: arg_1_field, rhs: arg_0_field }], // Field(1) / Field(0) }; @@ -83,21 +86,22 @@ fn test_jmp_if() { let data = FuzzerData { instruction_blocks: vec![failing_block.clone(), succeeding_block.clone()], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, // ends with non-failing block - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); // we expect that this program executed successfully match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)), None => panic!("Program failed to execute"), } - let arg_0_boolean = Argument { index: 0, numeric_type: NumericType::Boolean }; - let arg_1_boolean = Argument { index: 1, numeric_type: NumericType::Boolean }; + let arg_0_boolean = NumericArgument { index: 0, numeric_type: NumericType::Boolean }; + let arg_1_boolean = NumericArgument { index: 1, numeric_type: NumericType::Boolean }; let adding_bool_block = InstructionBlock { instructions: vec![Instruction::Or { lhs: arg_0_boolean, rhs: arg_1_boolean }], }; @@ -108,16 +112,17 @@ fn test_jmp_if() { let data = FuzzerData { instruction_blocks: vec![failing_block, succeeding_block, adding_bool_block], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, // ends with non-failing block - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); // we expect that this program failed to execute if let Some(result) = result { - panic!("Program executed successfully with result: {:?}", result.get_return_value()); + panic!("Program executed successfully with result: {:?}", result.get_return_values()); } } @@ -137,22 +142,24 @@ fn test_jmp_if() { #[test] fn test_mutable_variable() { let _ = env_logger::try_init(); - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; + let arg_0_field_numeric = NumericArgument { index: 0, numeric_type: NumericType::Field }; + let arg_2_field = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field = Argument { index: 5, value_type: Type::Numeric(NumericType::Field) }; + let arg_6_field = NumericArgument { index: 6, numeric_type: NumericType::Field }; + + let arg_0_field = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_0_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; let set_block = InstructionBlock { instructions: vec![Instruction::SetToMemory { memory_addr_index: 0, value: arg_5_field }], }; let add_block = InstructionBlock { - instructions: vec![Instruction::AddChecked { lhs: arg_0_field, rhs: arg_2_field }], + instructions: vec![Instruction::AddChecked { lhs: arg_0_field_numeric, rhs: arg_2_field }], }; let add_block_2 = InstructionBlock { instructions: vec![Instruction::AddChecked { lhs: arg_6_field, rhs: arg_2_field }], @@ -173,15 +180,16 @@ fn test_mutable_variable() { add_block_2, ], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 4, // last block adds v2 to loaded value, returns the result - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(4_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(4_u32)), None => panic!("Program failed to execute"), } } @@ -195,8 +203,12 @@ fn smoke_test_field_to_bytes_to_field() { let instructions_blocks = vec![field_to_bytes_to_field_block]; let commands = vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -204,7 +216,47 @@ fn smoke_test_field_to_bytes_to_field() { }; let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)), + None => panic!("Program failed to execute"), + } +} + +/// fn main(a: Field, b: Field, c: Field) -> pub [Field; 3] { +// [a, b, c] +/// } +#[test] +fn test_function_can_return_array() { + let _ = env_logger::try_init(); + let add_array_block = InstructionBlock { + instructions: vec![Instruction::CreateArray { + elements_indices: vec![0, 1, 2], + element_type: Type::Numeric(NumericType::Field), + }], + }; + let commands_for_main = + vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }]; + let main_func = FunctionData { + input_types: default_input_types(), + commands: commands_for_main, + return_instruction_block_idx: 1, + return_type: Type::Array(Arc::new(vec![Type::Numeric(NumericType::Field)]), 3), + }; + let data = FuzzerData { + instruction_blocks: vec![add_array_block], + functions: vec![main_func], + initial_witness: default_witness(), + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => { + assert!( + result + .get_return_values() + .iter() + .enumerate() + .all(|(i, v)| v == &FieldElement::from(i as u32)) + ); + } None => panic!("Program failed to execute"), } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_unsigned_test.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_unsigned_test.rs index 4536ecf6336..9febd7741da 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_unsigned_test.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/basic_unsigned_test.rs @@ -13,25 +13,24 @@ //! 11) Xor use crate::function_context::FunctionData; -use crate::function_context::WitnessValue; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::initial_witness::{WitnessValue, WitnessValueNumeric}; +use crate::instruction::{Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::{NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL}; +use crate::tests::common::default_input_types; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; /// Creates default witness values for testing /// Returns [U64(0), U64(1), U64(2), U64(3), U64(4)] -fn default_unsigned_witness() --> [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize] { - [ - WitnessValue::U64(0), - WitnessValue::U64(1), - WitnessValue::U64(2), - WitnessValue::U64(3), - WitnessValue::U64(4), +fn default_unsigned_witness() -> Vec { + vec![ + WitnessValue::Numeric(WitnessValueNumeric::U64(0)), + WitnessValue::Numeric(WitnessValueNumeric::U64(1)), + WitnessValue::Numeric(WitnessValueNumeric::U64(2)), + WitnessValue::Numeric(WitnessValueNumeric::U64(3)), + WitnessValue::Numeric(WitnessValueNumeric::U64(4)), ] } @@ -51,8 +50,8 @@ enum UnsignedOp { /// Returns `4_u64 op 2_u64` if binary, `op 4_u64` if unary fn test_op_u64(op: UnsignedOp) -> FieldElement { - let arg_2_u64 = Argument { index: 2, numeric_type: NumericType::U64 }; - let arg_4_u64 = Argument { index: 4, numeric_type: NumericType::U64 }; + let arg_2_u64 = NumericArgument { index: 2, numeric_type: NumericType::U64 }; + let arg_4_u64 = NumericArgument { index: 4, numeric_type: NumericType::U64 }; let instruction = match op { UnsignedOp::Add => Instruction::AddChecked { lhs: arg_4_u64, rhs: arg_2_u64 }, UnsignedOp::Sub => Instruction::SubChecked { lhs: arg_4_u64, rhs: arg_2_u64 }, @@ -72,12 +71,13 @@ fn test_op_u64(op: UnsignedOp) -> FieldElement { instruction_blocks: vec![instruction_block], functions: vec![FunctionData { commands: vec![], + input_types: default_input_types(), return_instruction_block_idx: 0, - return_type: NumericType::U64, + return_type: Type::Numeric(NumericType::U64), }], initial_witness: default_unsigned_witness(), }; - fuzz_target(data, FuzzerOptions::default()).unwrap().get_return_value() + fuzz_target(data, FuzzerOptions::default()).unwrap().get_return_values()[0] } #[test] diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/common.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/common.rs index c33d97f7217..3dd93232ff0 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/common.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/common.rs @@ -1,15 +1,18 @@ -use crate::function_context::{FieldRepresentation, WitnessValue}; -use crate::{NUMBER_OF_PREDEFINED_VARIABLES, NUMBER_OF_VARIABLES_INITIAL}; +use crate::initial_witness::{FieldRepresentation, WitnessValue, WitnessValueNumeric}; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; /// Creates default witness values for testing /// Returns [Field(0), Field(1), Field(2), Field(3), Field(4)] -pub(crate) fn default_witness() --> [WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize] { - [ - WitnessValue::Field(FieldRepresentation { high: 0, low: 0 }), - WitnessValue::Field(FieldRepresentation { high: 0, low: 1 }), - WitnessValue::Field(FieldRepresentation { high: 0, low: 2 }), - WitnessValue::Field(FieldRepresentation { high: 0, low: 3 }), - WitnessValue::Field(FieldRepresentation { high: 0, low: 4 }), +pub(crate) fn default_witness() -> Vec { + vec![ + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 0 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 1 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 2 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 3 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 4 })), ] } + +pub(crate) fn default_input_types() -> Vec { + vec![Type::Numeric(NumericType::Field); 5] +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/ecdsa.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/ecdsa.rs index 7d6c5657f8e..faa2901c5ff 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/ecdsa.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/ecdsa.rs @@ -3,10 +3,10 @@ use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; use crate::instruction::{Instruction, InstructionBlock}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::AcirField; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; #[test] fn test_valid_ecdsa_signature_secp256r1() { @@ -24,8 +24,9 @@ fn test_valid_ecdsa_signature_secp256r1() { let commands = vec![]; let function = FunctionData { commands, + input_types: default_input_types(), return_instruction_block_idx: 0, - return_type: NumericType::Boolean, + return_type: Type::Numeric(NumericType::Boolean), }; let data = FuzzerData { instruction_blocks: vec![block], @@ -33,7 +34,7 @@ fn test_valid_ecdsa_signature_secp256r1() { initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()).unwrap(); - assert_eq!(result.get_return_value(), FieldElement::one()); + assert_eq!(result.get_return_values()[0], FieldElement::one()); } #[test] @@ -52,8 +53,9 @@ fn test_valid_ecdsa_signature_secp256k1() { let commands = vec![]; let function = FunctionData { commands, + input_types: default_input_types(), return_instruction_block_idx: 0, - return_type: NumericType::Boolean, + return_type: Type::Numeric(NumericType::Boolean), }; let data = FuzzerData { instruction_blocks: vec![block], @@ -61,7 +63,7 @@ fn test_valid_ecdsa_signature_secp256k1() { initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()).unwrap(); - assert_eq!(result.get_return_value(), FieldElement::one()); + assert_eq!(result.get_return_values()[0], FieldElement::one()); } #[test] @@ -80,8 +82,9 @@ fn test_corrupted_ecdsa_signature_secp256r1() { let commands = vec![]; let function = FunctionData { commands, + input_types: default_input_types(), return_instruction_block_idx: 0, - return_type: NumericType::Boolean, + return_type: Type::Numeric(NumericType::Boolean), }; let data = FuzzerData { instruction_blocks: vec![block], @@ -90,7 +93,7 @@ fn test_corrupted_ecdsa_signature_secp256r1() { }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(res) => panic!("Programs executed with the Result: {:?}", res.get_return_value()), + Some(res) => panic!("Programs executed with the Result: {:?}", res.get_return_values()), None => println!("Error. As expected"), } } @@ -111,8 +114,9 @@ fn test_corrupted_ecdsa_signature_secp256k1() { let commands = vec![]; let function = FunctionData { commands, + input_types: default_input_types(), return_instruction_block_idx: 0, - return_type: NumericType::Boolean, + return_type: Type::Numeric(NumericType::Boolean), }; let data = FuzzerData { instruction_blocks: vec![block], @@ -121,7 +125,7 @@ fn test_corrupted_ecdsa_signature_secp256k1() { }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(res) => panic!("Programs executed with the Result: {:?}", res.get_return_value()), + Some(res) => panic!("Programs executed with the Result: {:?}", res.get_return_values()), None => println!("Error. As expected"), } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/embedded_curve_ops.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/embedded_curve_ops.rs index aebcdce3426..6753dd189a3 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/embedded_curve_ops.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/embedded_curve_ops.rs @@ -3,9 +3,9 @@ use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; use crate::instruction::{Instruction, InstructionBlock, Point, Scalar}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; /// fn main(lo: Field) -> pub Field { /// let scalar_1 = std::embedded_curve_ops::EmbeddedCurveScalar::new(lo, 0); @@ -34,8 +34,12 @@ fn smoke_test_embedded_curve_add() { }; let block = InstructionBlock { instructions: vec![add_instruction] }; let commands = vec![]; - let function = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let function = FunctionData { + commands, + input_types: default_input_types(), + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let data = FuzzerData { instruction_blocks: vec![block], functions: vec![function], @@ -43,7 +47,7 @@ fn smoke_test_embedded_curve_add() { }; let result = fuzz_target(data, FuzzerOptions::default()).unwrap(); assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::try_from_str( "8902249110305491597038405103722863701255802573786510474664632793109847672620" ) @@ -75,8 +79,12 @@ fn smoke_test_embedded_multi_scalar_mul() { }; let block = InstructionBlock { instructions: vec![instruction] }; let commands = vec![]; - let function = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let function = FunctionData { + commands, + input_types: default_input_types(), + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let data = FuzzerData { instruction_blocks: vec![block], functions: vec![function], @@ -84,7 +92,7 @@ fn smoke_test_embedded_multi_scalar_mul() { }; let result = fuzz_target(data, FuzzerOptions::default()).unwrap(); assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::try_from_str( "-3851299760922698091325321774664553326049887197487063802849283717866939395465" ) diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/function_calls.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/function_calls.rs index 69355f37156..c3ef6e565bc 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/function_calls.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/function_calls.rs @@ -6,12 +6,12 @@ use crate::function_context::{FunctionData, FuzzerFunctionCommand}; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::AcirField; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; /// brillig(inline) fn main f0 { /// b0(v0: Field, v1: Field, v2: Field, v3: Field, v4: Field, v5: u1, v6: u1): @@ -26,8 +26,9 @@ use noir_ssa_fuzzer::r#type::NumericType; /// #[test] fn simple_function_call() { - let dummy_var = Argument { index: 2, numeric_type: NumericType::I64 }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; + let _ = env_logger::try_init(); + let dummy_var = NumericArgument { index: 2, numeric_type: NumericType::I64 }; + let arg_2_field = NumericArgument { index: 2, numeric_type: NumericType::Field }; let add_block = InstructionBlock { instructions: vec![Instruction::AddChecked { lhs: arg_2_field, rhs: arg_2_field }], }; @@ -40,21 +41,23 @@ fn simple_function_call() { instruction_blocks: vec![dummy_block, add_block], functions: vec![ FunctionData { + input_types: default_input_types(), commands: commands1, return_instruction_block_idx: 0, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }, FunctionData { + input_types: default_input_types(), commands: vec![], return_instruction_block_idx: 1, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }, ], 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)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(4_u32)), None => panic!("Program failed to execute"), } } @@ -83,10 +86,10 @@ fn simple_function_call() { /// } #[test] fn several_functions_several_calls() { - let dummy_var = Argument { index: 2, numeric_type: NumericType::I64 }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; + let dummy_var = NumericArgument { index: 2, numeric_type: NumericType::I64 }; + let arg_2_field = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field = NumericArgument { index: 5, numeric_type: NumericType::Field }; + let arg_6_field = NumericArgument { index: 6, numeric_type: NumericType::Field }; let dummy_block = InstructionBlock { instructions: vec![Instruction::AddChecked { lhs: dummy_var, rhs: dummy_var }], }; @@ -107,22 +110,25 @@ fn several_functions_several_calls() { FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }, ]; let main_func = FunctionData { + input_types: default_input_types(), commands: commands_for_main, return_instruction_block_idx: 3, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; // for f1 the only defined function is f2 let commands_for_f1 = vec![FuzzerFunctionCommand::InsertFunctionCall { function_idx: 0, args }]; let f1_func = FunctionData { + input_types: default_input_types(), commands: commands_for_f1, return_instruction_block_idx: 1, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let f2_func = FunctionData { + input_types: default_input_types(), commands: vec![], return_instruction_block_idx: 2, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let fuzzer_data = FuzzerData { instruction_blocks: vec![add_block_main, add_block_f1, add_block_f2, dummy_block], @@ -131,7 +137,7 @@ fn several_functions_several_calls() { }; let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(12_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(12_u32)), None => panic!("Program failed to execute"), } } @@ -166,17 +172,18 @@ fn several_functions_several_calls() { #[test] fn call_in_if_else() { let _ = env_logger::try_init(); - let dummy_var = Argument { index: 2, numeric_type: NumericType::I64 }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_3_field = Argument { index: 3, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; + let dummy_var = NumericArgument { index: 2, numeric_type: NumericType::I64 }; + let arg_2_field = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_3_field = NumericArgument { index: 3, numeric_type: NumericType::Field }; + let arg_5_field = Argument { index: 5, value_type: Type::Numeric(NumericType::Field) }; let dummy_block = InstructionBlock { instructions: vec![Instruction::AddChecked { lhs: dummy_var, rhs: dummy_var }], }; - let add_to_memory_block = - InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_5_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let add_to_memory_block = InstructionBlock { + instructions: vec![Instruction::AddToMemory { lhs: arg_5_field.clone() }], + }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; let set_to_memory_block = InstructionBlock { instructions: vec![Instruction::SetToMemory { memory_addr_index: 0, value: arg_5_field }], }; @@ -190,14 +197,16 @@ fn call_in_if_else() { instructions: vec![Instruction::AddChecked { lhs: arg_3_field, rhs: arg_3_field }], // v3 + v3 }; let f1_func = FunctionData { + input_types: default_input_types(), commands: vec![], return_instruction_block_idx: 4, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let f2_func = FunctionData { + input_types: default_input_types(), commands: vec![], return_instruction_block_idx: 5, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let args: [usize; 7] = [0, 1, 2, 3, 4, 0, 1]; @@ -219,9 +228,10 @@ fn call_in_if_else() { add_block_f2, ]; let main_func = FunctionData { + input_types: default_input_types(), commands: commands_for_main.clone(), return_instruction_block_idx: 3, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let result = fuzz_target( FuzzerData { @@ -232,12 +242,12 @@ fn call_in_if_else() { FuzzerOptions::default(), ); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(4_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(4_u32)), None => panic!("Program failed to execute"), } - let arg_0_boolean = Argument { index: 0, numeric_type: NumericType::Boolean }; - let arg_1_boolean = Argument { index: 1, numeric_type: NumericType::Boolean }; + let arg_0_boolean = NumericArgument { index: 0, numeric_type: NumericType::Boolean }; + let arg_1_boolean = NumericArgument { index: 1, numeric_type: NumericType::Boolean }; let add_boolean_block = InstructionBlock { instructions: vec![Instruction::Or { lhs: arg_0_boolean, rhs: arg_1_boolean }], }; @@ -251,8 +261,12 @@ fn call_in_if_else() { log::debug!("commands: {commands:?}"); blocks.push(add_boolean_block); log::debug!("blocks: {blocks:?}"); - let main_func = - FunctionData { commands, return_instruction_block_idx: 3, return_type: NumericType::Field }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 3, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: blocks.clone(), functions: vec![main_func, f1_func, f2_func], @@ -260,7 +274,7 @@ fn call_in_if_else() { }; let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(6_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(6_u32)), None => panic!("Program failed to execute"), } } @@ -277,27 +291,29 @@ fn call_in_if_else() { /// Otherwise, the result should be the output of the first function #[test] fn test_does_not_insert_too_many_instructions_with_function_calls() { - let dummy_arg = Argument { index: 0, numeric_type: NumericType::I64 }; + let dummy_arg = NumericArgument { index: 0, numeric_type: NumericType::I64 }; let dummy_block = InstructionBlock { instructions: vec![Instruction::AddChecked { lhs: dummy_arg, rhs: dummy_arg }], }; - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; + let arg_2_field_numeric = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field_numeric = NumericArgument { index: 5, numeric_type: NumericType::Field }; + let arg_6_field = Argument { index: 6, value_type: Type::Numeric(NumericType::Field) }; + + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; // v8 = allocate -> &mut Field (memory address) // store v2 at v8 let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; // load v8 -> Field (loads from first defined memory address, which is v8) let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; let load_mul_set_block = InstructionBlock { instructions: vec![ Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v13 = load v8 -> Field (loaded value is 5th defined field) - Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field }, // v14 = mul v13, v2 (v14 -- 6th defined field) + Instruction::MulChecked { lhs: arg_5_field_numeric, rhs: arg_2_field_numeric }, // v14 = mul v13, v2 (v14 -- 6th defined field) Instruction::SetToMemory { memory_addr_index: 0, value: arg_6_field }, // store v14 at v8 ], }; @@ -320,19 +336,22 @@ fn test_does_not_insert_too_many_instructions_with_function_calls() { FuzzerFunctionCommand::InsertCycle { block_body_idx: 2, start_iter: 1, end_iter: 10 }, // for i in 1..10 do load_mul_set_block ]; let main_func = FunctionData { + input_types: default_input_types(), commands: commands_for_main, return_instruction_block_idx: 3, // dummy block - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let function_func = FunctionData { + input_types: default_input_types(), commands: commands_for_function1, return_instruction_block_idx: 1, // v12 = load v8 -> Field; return v12 - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let function_func2 = FunctionData { + input_types: default_input_types(), commands: commands_for_function2, return_instruction_block_idx: 1, // v12 = load v8 -> Field; return v12 - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }; let data = FuzzerData { instruction_blocks: vec![add_to_memory_block, load_block, load_mul_set_block, dummy_block], @@ -343,7 +362,7 @@ fn test_does_not_insert_too_many_instructions_with_function_calls() { let options = FuzzerOptions { max_instructions_num: 100, ..FuzzerOptions::default() }; let result = fuzz_target(data.clone(), options); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1024_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(1024_u32)), None => panic!("Program failed to execute"), } // with max 1000 instructions both functions should be executed @@ -353,7 +372,7 @@ fn test_does_not_insert_too_many_instructions_with_function_calls() { match result { Some(result) => { assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::from(2_u32).pow(&FieldElement::from(200_u32)) // 2^200 ); } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/hash_tests.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/hash_tests.rs index e4f9798a056..14bc6dd7beb 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/hash_tests.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/hash_tests.rs @@ -7,11 +7,12 @@ use crate::function_context::{FunctionData, FuzzerFunctionCommand}; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::instruction::{Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::NumericType; +use noir_ssa_fuzzer::typed_value::Type; /// blake2s(to_le_radix(0, 256, 32)) == blake2s computed with noir /// @@ -29,8 +30,12 @@ fn smoke_test_blake2s_hash() { }; let instructions_blocks = vec![blake2s_hash_block]; let commands = vec![]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -39,7 +44,7 @@ fn smoke_test_blake2s_hash() { let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { Some(result) => assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::try_from_str( "-9211429028062209127175291049466917975585300944217240748738694765619842249938" ) @@ -65,8 +70,12 @@ fn smoke_test_blake3_hash() { }; let instructions_blocks = vec![blake3_hash_block]; let commands = vec![]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -75,7 +84,7 @@ fn smoke_test_blake3_hash() { let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { Some(result) => assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::try_from_str( "11496696481601359239189947342432058980836600577383371976100559912527609453094" ) @@ -106,8 +115,12 @@ fn smoke_test_aes128_encrypt() { }; let instructions_blocks = vec![aes128_encrypt_block]; let commands = vec![]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 0, return_type: NumericType::Field }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 0, + return_type: Type::Numeric(NumericType::Field), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -116,7 +129,7 @@ fn smoke_test_aes128_encrypt() { let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { Some(result) => assert_eq!( - result.get_return_value(), + result.get_return_values()[0], FieldElement::try_from_str( "7228449286344697221705732525592563926191809635549234005020486075743434697058" ) @@ -137,7 +150,7 @@ fn smoke_test_keccakf1600() { let _ = env_logger::try_init(); // default witness are fields // so take the first one and cast it to u64 - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; + let arg_0_field = NumericArgument { index: 0, numeric_type: NumericType::Field }; let cast_block = InstructionBlock { instructions: vec![Instruction::Cast { lhs: arg_0_field, type_: NumericType::U64 }], }; @@ -152,8 +165,12 @@ fn smoke_test_keccakf1600() { let commands = vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }]; // this function will take the last defined u64, which is equal to the last element of the keccakf1600 permuted array - let main_func = - FunctionData { commands, return_instruction_block_idx: 1, return_type: NumericType::U64 }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 1, + return_type: Type::Numeric(NumericType::U64), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -162,7 +179,7 @@ fn smoke_test_keccakf1600() { let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { Some(result) => { - assert_eq!(result.get_return_value(), FieldElement::from(16929593379567477321_u64)); + assert_eq!(result.get_return_values()[0], FieldElement::from(16929593379567477321_u64)); } None => panic!("Program failed to execute"), } @@ -178,7 +195,7 @@ fn smoke_test_keccakf1600() { #[test] fn smoke_test_sha256_compression() { let _ = env_logger::try_init(); - let arg_0_field = Argument { index: 0, numeric_type: NumericType::Field }; + let arg_0_field = NumericArgument { index: 0, numeric_type: NumericType::Field }; let cast_block = InstructionBlock { instructions: vec![Instruction::Cast { lhs: arg_0_field, type_: NumericType::U32 }], }; @@ -192,8 +209,12 @@ fn smoke_test_sha256_compression() { let instructions_blocks = vec![cast_block, sha256_compression_block]; let commands = vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }]; - let main_func = - FunctionData { commands, return_instruction_block_idx: 1, return_type: NumericType::U32 }; + let main_func = FunctionData { + input_types: default_input_types(), + commands, + return_instruction_block_idx: 1, + return_type: Type::Numeric(NumericType::U32), + }; let fuzzer_data = FuzzerData { instruction_blocks: instructions_blocks, functions: vec![main_func], @@ -202,7 +223,7 @@ fn smoke_test_sha256_compression() { let result = fuzz_target(fuzzer_data, FuzzerOptions::default()); match result { Some(result) => { - assert_eq!(result.get_return_value(), FieldElement::from(3205228454_u32)); + assert_eq!(result.get_return_values()[0], FieldElement::from(3205228454_u32)); } None => panic!("Program failed to execute"), } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/loops.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/loops.rs index fd251c6771a..2c9ccc91f7c 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/loops.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/loops.rs @@ -6,11 +6,11 @@ use crate::function_context::{FunctionData, FuzzerFunctionCommand}; use crate::fuzz_target_lib::fuzz_target; use crate::fuzzer::FuzzerData; -use crate::instruction::{Argument, Instruction, InstructionBlock}; +use crate::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; use crate::options::FuzzerOptions; -use crate::tests::common::default_witness; +use crate::tests::common::{default_input_types, default_witness}; use acvm::FieldElement; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; /// fn main(x: Field) -> pub Field { /// let mut y = x; @@ -22,23 +22,25 @@ use noir_ssa_fuzzer::r#type::NumericType; /// x = 2, so we expect that y = 2 * 2 ^ 9 = 2 ^ 10 #[test] fn test_simple_loop() { - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; + let arg_2_field_numeric = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field = NumericArgument { index: 5, numeric_type: NumericType::Field }; + let arg_6_field = Argument { index: 6, value_type: Type::Numeric(NumericType::Field) }; + + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; // v8 = allocate -> &mut Field (memory address) // store v2 at v8 let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; // load v8 -> Field (loads from first defined memory address, which is v8) let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; let load_mul_set_block = InstructionBlock { instructions: vec![ Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v13 = load v8 -> Field (loaded value is 5th defined field) - Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field }, // v14 = mul v13, v2 (v14 -- 6th defined field) + Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field_numeric }, // v14 = mul v13, v2 (v14 -- 6th defined field) Instruction::SetToMemory { memory_addr_index: 0, value: arg_6_field }, // store v14 at v8 ], }; @@ -49,15 +51,16 @@ fn test_simple_loop() { let data = FuzzerData { instruction_blocks: vec![add_to_memory_block, load_block, load_mul_set_block], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, // v12 = load v8 -> Field; return v12 - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1024_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(1024_u32)), None => panic!("Program failed to execute"), } } @@ -75,32 +78,34 @@ fn test_simple_loop() { /// x = 2; so we expect y = x * x ^ (4 * 4) = 2 * 2 ^ 16 = 2 ^ 17 #[test] fn test_nested_loop() { - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; - let arg_7_field = Argument { index: 7, numeric_type: NumericType::Field }; - let arg_8_field = Argument { index: 8, numeric_type: NumericType::Field }; + let arg_2_field_numeric = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field = NumericArgument { index: 5, numeric_type: NumericType::Field }; + let arg_6_field = Argument { index: 6, value_type: Type::Numeric(NumericType::Field) }; + let arg_7_field = NumericArgument { index: 7, numeric_type: NumericType::Field }; + let arg_8_field = Argument { index: 8, value_type: Type::Numeric(NumericType::Field) }; + + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; // v9 = allocate -> &mut Field // store v2 at v9 let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; // load v9 -> Field (loads from first defined memory address, which is v9) let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; let load_mul_set_block = InstructionBlock { instructions: vec![ - Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v14 = load v9 -> Field (loaded value is 5th defined field) - Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field }, // v15 = mul v14, v2 (v15 -- 6th defined field) + Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }, // v14 = load v9 -> Field (loaded value is 5th defined field) + Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field_numeric }, // v15 = mul v14, v2 (v15 -- 6th defined field) Instruction::SetToMemory { memory_addr_index: 0, value: arg_6_field }, // store v15 at v9 ], }; let load_mul_set_block2 = InstructionBlock { instructions: vec![ Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v18 = load v9 -> Field (loaded value is 7th defined field) - Instruction::MulChecked { lhs: arg_7_field, rhs: arg_2_field }, // v19 = mul v18, v2 (v19 -- 8th defined field) + Instruction::MulChecked { lhs: arg_7_field, rhs: arg_2_field_numeric }, // v19 = mul v18, v2 (v19 -- 8th defined field) Instruction::SetToMemory { memory_addr_index: 0, value: arg_8_field }, // store v19 at v9 ], }; @@ -117,15 +122,16 @@ fn test_nested_loop() { load_mul_set_block2, ], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, // v13 = load v9 -> Field; return v13 - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(131072_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(131072_u32)), None => panic!("Program failed to execute"), } } @@ -153,19 +159,21 @@ fn test_nested_loop() { /// } #[test] fn test_loop_broken_with_jmp() { - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_5_field = Argument { index: 5, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; + let arg_2_field_numeric = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_5_field = NumericArgument { index: 5, numeric_type: NumericType::Field }; + let arg_6_field = Argument { index: 6, value_type: Type::Numeric(NumericType::Field) }; + + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; // v8 = allocate -> &mut Field (memory address) // store v2 at v8 let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; // v14 = load v8 -> Field let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; // end block does not inherit variables defined in cycle body, so we can use this instruction block twice @@ -173,7 +181,7 @@ fn test_loop_broken_with_jmp() { let load_mul_set_block = InstructionBlock { instructions: vec![ Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v14 = load v8 -> Field - Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field }, // v15 = mul v14, v2 (v15 -- 6th defined field) + Instruction::MulChecked { lhs: arg_5_field, rhs: arg_2_field_numeric }, // v15 = mul v14, v2 (v15 -- 6th defined field) Instruction::SetToMemory { memory_addr_index: 0, value: arg_6_field }, // store v15 at v8 ], }; @@ -187,15 +195,16 @@ fn test_loop_broken_with_jmp() { let data = FuzzerData { instruction_blocks: vec![add_to_memory_block, load_block, load_mul_set_block], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(4096_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(4096_u32)), None => panic!("Program failed to execute"), } } @@ -214,33 +223,35 @@ fn test_loop_broken_with_jmp() { /// x = 2; if cond = 1: y = 2 * 2 ^ 10, if cond = 0: y = 2 + 10 * 2 = 22 #[test] fn test_jmp_if_in_cycle() { - let arg_2_field = Argument { index: 2, numeric_type: NumericType::Field }; - let arg_6_field = Argument { index: 6, numeric_type: NumericType::Field }; - let arg_7_field = Argument { index: 7, numeric_type: NumericType::Field }; + let arg_2_field_numeric = NumericArgument { index: 2, numeric_type: NumericType::Field }; + let arg_6_field = NumericArgument { index: 6, numeric_type: NumericType::Field }; + let arg_7_field = Argument { index: 7, value_type: Type::Numeric(NumericType::Field) }; + + let arg_2_field = Argument { index: 2, value_type: Type::Numeric(NumericType::Field) }; // v9 = allocate -> &mut Field // store v2 at v9 let add_to_memory_block = InstructionBlock { instructions: vec![Instruction::AddToMemory { lhs: arg_2_field }] }; - let typed_memory_0 = Argument { index: 0, numeric_type: NumericType::Field }; + let typed_memory_0 = Argument { index: 0, value_type: Type::Numeric(NumericType::Field) }; // load v9 -> Field let load_block = InstructionBlock { - instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0 }], + instructions: vec![Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }], }; // load_*_block will be used for then and else blocks // then and else blocks does not share variables, so indices of arguments are the same (loaded variables from then block cannot be used in else block) let load_mul_set_block = InstructionBlock { instructions: vec![ - Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v15 = load v9 -> Field (loaded value is 5th defined field in then block) - Instruction::MulChecked { lhs: arg_6_field, rhs: arg_2_field }, // v16 = mul v15, v2 (v16 -- 6th defined field in then block) - Instruction::SetToMemory { memory_addr_index: 0, value: arg_7_field }, // store v16 at v9 + Instruction::LoadFromMemory { memory_addr: typed_memory_0.clone() }, // v15 = load v9 -> Field (loaded value is 5th defined field in then block) + Instruction::MulChecked { lhs: arg_6_field, rhs: arg_2_field_numeric }, // v16 = mul v15, v2 (v16 -- 6th defined field in then block) + Instruction::SetToMemory { memory_addr_index: 0, value: arg_7_field.clone() }, // store v16 at v9 ], }; let load_add_set_block = InstructionBlock { instructions: vec![ Instruction::LoadFromMemory { memory_addr: typed_memory_0 }, // v17 = load v9 -> Field (loaded value is 5th defined field in else block) - Instruction::AddChecked { lhs: arg_6_field, rhs: arg_2_field }, // v18 = add v17, v2 (v18 -- 6th defined field in else block) + Instruction::AddChecked { lhs: arg_6_field, rhs: arg_2_field_numeric }, // v18 = add v17, v2 (v18 -- 6th defined field in else block) Instruction::SetToMemory { memory_addr_index: 0, value: arg_7_field }, // store v18 at v9 ], }; @@ -257,20 +268,21 @@ fn test_jmp_if_in_cycle() { load_add_set_block.clone(), ], functions: vec![FunctionData { + input_types: default_input_types(), commands: commands.clone(), return_instruction_block_idx: 1, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(22_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(22_u32)), None => panic!("Program failed to execute"), } - let arg_0_boolean = Argument { index: 0, numeric_type: NumericType::Boolean }; - let arg_1_boolean = Argument { index: 1, numeric_type: NumericType::Boolean }; + let arg_0_boolean = NumericArgument { index: 0, numeric_type: NumericType::Boolean }; + let arg_1_boolean = NumericArgument { index: 1, numeric_type: NumericType::Boolean }; let add_boolean_block = InstructionBlock { instructions: vec![Instruction::Or { lhs: arg_0_boolean, rhs: arg_1_boolean }], // jmpif uses last defined boolean variable // [initialize_witness_map] func inserts two boolean variables itself, first is true, last is false @@ -292,15 +304,16 @@ fn test_jmp_if_in_cycle() { add_boolean_block, ], functions: vec![FunctionData { + input_types: default_input_types(), commands, return_instruction_block_idx: 1, - return_type: NumericType::Field, + return_type: Type::Numeric(NumericType::Field), }], initial_witness: default_witness(), }; let result = fuzz_target(data, FuzzerOptions::default()); match result { - Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(2048_u32)), + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(2048_u32)), None => panic!("Program failed to execute"), } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/mod.rs index 308b4ef8f20..9198fe862f4 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/mod.rs @@ -1,3 +1,4 @@ +mod advanced_references; mod arrays; mod basic_tests; mod basic_unsigned_test; @@ -7,3 +8,4 @@ mod embedded_curve_ops; mod function_calls; mod hash_tests; mod loops; +mod witness; diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/witness.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/witness.rs new file mode 100644 index 00000000000..f0978b01679 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/tests/witness.rs @@ -0,0 +1,135 @@ +//! This file contains tests for the witness +//! 1) Test if array can be used as a initial witness +//! 2) Test if array of arrays can be used as a initial witness + +use crate::function_context::FunctionData; +use crate::fuzz_target_lib::fuzz_target; +use crate::fuzzer::FuzzerData; +use crate::initial_witness::{FieldRepresentation, WitnessValue, WitnessValueNumeric}; +use crate::instruction::{Instruction, InstructionBlock, NumericArgument}; +use crate::options::FuzzerOptions; +use acvm::FieldElement; +use noir_ssa_fuzzer::typed_value::{NumericType, Type}; +use std::sync::Arc; + +/// fn main(arr: [Field; 2], index: u32) -> pub Field { +/// arr[index]; +/// } +/// +/// Compiled into: +/// Note: v2 and v3 auto generated +/// fn main f0 { +/// b0(v0: [Field; 2], v1: u32, v2: u1, v3: u1): +/// v4 = array_get v0, index v1 -> Field +/// return v4 +/// } +#[test] +fn test_array_as_initial_witness() { + let _ = env_logger::try_init(); + let instruction_get = Instruction::ArrayGet { + array_index: 0, + index: NumericArgument { index: 0, numeric_type: NumericType::U32 }, + safe_index: false, + }; + let array_get_block = InstructionBlock { instructions: vec![instruction_get] }; + let main_function = FunctionData { + commands: vec![], + input_types: vec![ + Type::Array(Arc::new(vec![Type::Numeric(NumericType::Field)]), 2), + Type::Numeric(NumericType::U32), + ], + return_instruction_block_idx: 0, // array get + return_type: Type::Numeric(NumericType::Field), + }; + let array_witness = WitnessValue::Array(vec![ + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 1 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 2 })), + ]); + let index_witness_0 = WitnessValue::Numeric(WitnessValueNumeric::U32(0)); + let index_witness_1 = WitnessValue::Numeric(WitnessValueNumeric::U32(1)); + let data = FuzzerData { + instruction_blocks: vec![array_get_block.clone()], + initial_witness: vec![array_witness.clone(), index_witness_0], + functions: vec![main_function.clone()], + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)), + None => panic!("Program failed to execute"), + } + + let data = FuzzerData { + instruction_blocks: vec![array_get_block], + initial_witness: vec![array_witness, index_witness_1], + functions: vec![main_function], + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => assert_eq!(result.get_return_values()[0], FieldElement::from(2_u32)), + None => panic!("Program failed to execute"), + } +} + +/// fn main(arr: [[Field; 2]; 2], index: u32) -> pub [Field; 2] { +/// arr[index]; +/// } +#[test] +fn test_array_of_arrays_as_initial_witness() { + let _ = env_logger::try_init(); + let instruction_get = Instruction::ArrayGet { + array_index: 0, + index: NumericArgument { index: 0, numeric_type: NumericType::U32 }, + safe_index: false, + }; + let array_get_block = InstructionBlock { instructions: vec![instruction_get] }; + let main_function = FunctionData { + commands: vec![], + input_types: vec![ + Type::Array( + Arc::new(vec![Type::Array(Arc::new(vec![Type::Numeric(NumericType::Field)]), 2)]), + 2, + ), + Type::Numeric(NumericType::U32), + ], + return_instruction_block_idx: 0, // array get + return_type: Type::Array(Arc::new(vec![Type::Numeric(NumericType::Field)]), 2), + }; + let array_1 = WitnessValue::Array(vec![ + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 1 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 2 })), + ]); + let array_2 = WitnessValue::Array(vec![ + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 3 })), + WitnessValue::Numeric(WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 4 })), + ]); + let array_witness = WitnessValue::Array(vec![array_1, array_2]); + let index_witness_0 = WitnessValue::Numeric(WitnessValueNumeric::U32(0)); + let index_witness_1 = WitnessValue::Numeric(WitnessValueNumeric::U32(1)); + let data = FuzzerData { + instruction_blocks: vec![array_get_block.clone()], + initial_witness: vec![array_witness.clone(), index_witness_0], + functions: vec![main_function.clone()], + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => { + assert_eq!(result.get_return_values()[0], FieldElement::from(1_u32)); + assert_eq!(result.get_return_values()[1], FieldElement::from(2_u32)); + } + None => panic!("Program failed to execute"), + } + + let data = FuzzerData { + instruction_blocks: vec![array_get_block], + initial_witness: vec![array_witness, index_witness_1], + functions: vec![main_function], + }; + let result = fuzz_target(data, FuzzerOptions::default()); + match result { + Some(result) => { + assert_eq!(result.get_return_values()[0], FieldElement::from(3_u32)); + assert_eq!(result.get_return_values()[1], FieldElement::from(4_u32)); + } + None => panic!("Program failed to execute"), + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs index 27d0784a5e2..65ff1eb2309 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_target.rs @@ -42,11 +42,12 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { // Disable some instructions with bugs that are not fixed yet let instruction_options = InstructionOptions { - // https://github.com/noir-lang/noir/issues/9583 + // https://github.com/noir-lang/noir/issues/9707 shr_enabled: false, shl_enabled: false, - // https://github.com/noir-lang/noir/issues/9452 + // https://github.com/noir-lang/noir/issues/9437 array_get_enabled: false, + array_set_enabled: false, // https://github.com/noir-lang/noir/issues/9559 point_add_enabled: false, multi_scalar_mul_enabled: false, diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/mod.rs index 1a5c7c3557d..93a4fbc473d 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/mod.rs @@ -2,5 +2,6 @@ pub(crate) mod bool; pub(crate) mod numeric_type; pub(crate) mod point; pub(crate) mod scalar; +pub(crate) mod ssa_fuzzer_type; pub(crate) mod usize; pub(crate) mod vec; diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/numeric_type.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/numeric_type.rs index 4d4a2fc53d8..fe350e49103 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/numeric_type.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/numeric_type.rs @@ -3,7 +3,7 @@ use crate::mutations::configuration::{ GenerateNumericType, GenerateNumericTypeConfig, NumericTypeMutationConfig, NumericTypeMutationOptions, }; -use noir_ssa_fuzzer::r#type::NumericType; +use noir_ssa_fuzzer::typed_value::NumericType; use rand::rngs::StdRng; pub(crate) fn generate_random_numeric_type( diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/ssa_fuzzer_type.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/ssa_fuzzer_type.rs new file mode 100644 index 00000000000..0615ba674f3 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/ssa_fuzzer_type.rs @@ -0,0 +1,44 @@ +use crate::mutations::{ + basic_types::numeric_type::generate_random_numeric_type, + configuration::{ + BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION, BASIC_GENERATE_TYPE_CONFIGURATION, GenerateType, + GenerateTypeConfig, MAX_ARRAY_SIZE, + }, +}; +use noir_ssa_fuzzer::typed_value::Type; +use rand::{Rng, rngs::StdRng}; +use std::sync::Arc; + +fn generate_random_reference_type(rng: &mut StdRng, config: GenerateTypeConfig) -> Type { + Type::Reference(Arc::new(generate_random_ssa_fuzzer_type(rng, config))) +} + +fn generate_random_array_type(rng: &mut StdRng, config: GenerateTypeConfig) -> Type { + Type::Array( + Arc::new(vec![generate_random_ssa_fuzzer_type(rng, config)]), + rng.gen_range(1..MAX_ARRAY_SIZE) as u32, // empty arrays are not allowed + ) +} + +fn generate_random_slice_type(rng: &mut StdRng, config: GenerateTypeConfig) -> Type { + Type::Slice(Arc::new(vec![generate_random_ssa_fuzzer_type(rng, config)])) +} + +pub(crate) fn generate_random_ssa_fuzzer_type( + rng: &mut StdRng, + config: GenerateTypeConfig, +) -> Type { + match config.select(rng) { + GenerateType::Numeric => Type::Numeric(generate_random_numeric_type( + rng, + BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION, + )), + GenerateType::Reference => generate_random_reference_type(rng, config), + GenerateType::Array => generate_random_array_type(rng, config), + GenerateType::Slice => generate_random_slice_type(rng, config), + } +} + +pub(crate) fn mutate_ssa_fuzzer_type(type_: &mut Type, rng: &mut StdRng) { + *type_ = generate_random_ssa_fuzzer_type(rng, BASIC_GENERATE_TYPE_CONFIGURATION); +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/vec.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/vec.rs index 2ff892b6525..658311b670b 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/vec.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/basic_types/vec.rs @@ -7,25 +7,9 @@ //! 5. Mutate a random element at a random index //! 6. Push a default element to the end of the vector -use crate::mutations::configuration::{ - SIZE_OF_LARGE_ARBITRARY_BUFFER, VecMutationConfig, VecMutationOptions, -}; -use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured}; +use crate::mutations::configuration::{VecMutationConfig, VecMutationOptions}; use rand::{Rng, rngs::StdRng}; -/// Mutate the entire vector, replacing it with a random vector -struct RandomMutation; -impl RandomMutation { - fn mutate(rng: &mut StdRng, vec: &mut Vec) - where - T: for<'a> Arbitrary<'a>, - { - let mut bytes = [0u8; SIZE_OF_LARGE_ARBITRARY_BUFFER]; - rng.fill(&mut bytes); - *vec = Unstructured::new(&bytes).arbitrary().unwrap(); - } -} - /// Insert a random element at a random index struct RandomInsertion; impl RandomInsertion { @@ -33,9 +17,7 @@ impl RandomInsertion { rng: &mut StdRng, vec: &mut Vec, generate_random_element_function: impl Fn(&mut StdRng) -> T, - ) where - T: for<'a> Arbitrary<'a>, - { + ) { let element = generate_random_element_function(rng); if !vec.is_empty() { let index = rng.gen_range(0..vec.len()); @@ -50,7 +32,10 @@ impl RandomInsertion { struct RandomDeletion; impl RandomDeletion { fn mutate(rng: &mut StdRng, vec: &mut Vec) { - if !vec.is_empty() { + // do not remove the last element + // because ssa fuzzer forbids empty arrays in initial witness + // so we must keep at least one element + if vec.len() > 1 { let index = rng.gen_range(0..vec.len()); vec.remove(index); } @@ -102,10 +87,9 @@ pub(crate) fn mutate_vec( generate_random_element_function: impl Fn(&mut StdRng) -> T, config: VecMutationConfig, ) where - T: for<'a> Arbitrary<'a> + Default, + T: Default, { match config.select(rng) { - VecMutationOptions::Random => RandomMutation::mutate(rng, vec), VecMutationOptions::Insertion => { RandomInsertion::mutate(rng, vec, generate_random_element_function); } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs index 8193e571bfc..968952bb662 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs @@ -55,13 +55,15 @@ pub(crate) enum FunctionMutationOptions { ReturnBlockIdx, FunctionFuzzerCommands, ReturnType, + InputTypes, } -pub(crate) type MutationConfig = WeightedSelectionConfig; +pub(crate) type MutationConfig = WeightedSelectionConfig; pub(crate) const BASIC_FUNCTION_MUTATION_CONFIGURATION: MutationConfig = MutationConfig::new([ (FunctionMutationOptions::ReturnBlockIdx, 1), (FunctionMutationOptions::FunctionFuzzerCommands, 1), (FunctionMutationOptions::ReturnType, 1), + (FunctionMutationOptions::InputTypes, 1), ]); /// Mutations of witness values @@ -74,15 +76,23 @@ pub(crate) enum WitnessMutationOptions { PowerOfTwoAddSub, } -pub(crate) type WitnessMutationConfig = WeightedSelectionConfig; -pub(crate) const BASIC_WITNESS_MUTATION_CONFIGURATION: WitnessMutationConfig = - WitnessMutationConfig::new([ +pub(crate) type NumericWitnessMutationConfig = WeightedSelectionConfig; +pub(crate) const BASIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION: NumericWitnessMutationConfig = + NumericWitnessMutationConfig::new([ (WitnessMutationOptions::Random, 1), (WitnessMutationOptions::MaxValue, 2), (WitnessMutationOptions::MinValue, 2), (WitnessMutationOptions::SmallAddSub, 4), (WitnessMutationOptions::PowerOfTwoAddSub, 3), ]); +pub(crate) const DETERMINISTIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION: + NumericWitnessMutationConfig = NumericWitnessMutationConfig::new([ + (WitnessMutationOptions::Random, 0), + (WitnessMutationOptions::MaxValue, 2), + (WitnessMutationOptions::MinValue, 2), + (WitnessMutationOptions::SmallAddSub, 4), + (WitnessMutationOptions::PowerOfTwoAddSub, 3), +]); /// Mutations of instructions #[derive(Copy, Clone, Debug)] @@ -117,37 +127,67 @@ pub(crate) const BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION: /// Mutations of arguments of instructions #[derive(Copy, Clone, Debug)] -pub(crate) enum ArgumentMutationOptions { +pub(crate) enum NumericArgumentMutationOptions { MutateIndex, ChangeType, } +pub(crate) type NumericArgumentMutationConfig = + WeightedSelectionConfig; +pub(crate) const BASIC_NUMERIC_ARGUMENT_MUTATION_CONFIGURATION: NumericArgumentMutationConfig = + NumericArgumentMutationConfig::new([ + (NumericArgumentMutationOptions::MutateIndex, 7), + (NumericArgumentMutationOptions::ChangeType, 2), + ]); + +#[derive(Copy, Clone, Debug)] +pub(crate) enum ArgumentMutationOptions { + MutateType, + MutateIndex, +} + pub(crate) type ArgumentMutationConfig = WeightedSelectionConfig; pub(crate) const BASIC_ARGUMENT_MUTATION_CONFIGURATION: ArgumentMutationConfig = ArgumentMutationConfig::new([ - (ArgumentMutationOptions::MutateIndex, 7), - (ArgumentMutationOptions::ChangeType, 2), + (ArgumentMutationOptions::MutateType, 1), + (ArgumentMutationOptions::MutateIndex, 3), ]); #[derive(Copy, Clone, Debug)] pub(crate) enum VecMutationOptions { - Random, Insertion, Deletion, Swap, ElementMutation, PushDefault, } -pub(crate) type VecMutationConfig = WeightedSelectionConfig; +pub(crate) type VecMutationConfig = WeightedSelectionConfig; pub(crate) const BASIC_VEC_MUTATION_CONFIGURATION: VecMutationConfig = VecMutationConfig::new([ - (VecMutationOptions::Random, 1), (VecMutationOptions::Insertion, 7), (VecMutationOptions::Deletion, 22), (VecMutationOptions::Swap, 20), (VecMutationOptions::ElementMutation, 100), (VecMutationOptions::PushDefault, 15), ]); +pub(crate) const ARRAY_WITNESS_MUTATION_CONFIGURATION: VecMutationConfig = + VecMutationConfig::new([ + (VecMutationOptions::Insertion, 10), + (VecMutationOptions::Deletion, 20), + (VecMutationOptions::Swap, 10), + (VecMutationOptions::ElementMutation, 30), + (VecMutationOptions::PushDefault, 0), // default is Field(0) + ]); + +// used for arrays inside arrays in initial witness +pub(crate) const ARRAY_WITNESS_MUTATION_CONFIGURATION_INNER: VecMutationConfig = + VecMutationConfig::new([ + (VecMutationOptions::Insertion, 0), // random insertion is not allowed (arrays must be of the same type) + (VecMutationOptions::Deletion, 0), + (VecMutationOptions::Swap, 10), + (VecMutationOptions::ElementMutation, 30), + (VecMutationOptions::PushDefault, 0), // default is Field(0) + ]); #[derive(Copy, Clone, Debug)] pub(crate) enum NumericTypeMutationOptions { @@ -245,14 +285,12 @@ pub(crate) const BASIC_ARRAY_SET_MUTATION_CONFIGURATION: ArraySetMutationConfig pub(crate) enum CreateArrayMutationOptions { ElementsIndices, ElementType, - IsReferences, } -pub(crate) type CreateArrayMutationConfig = WeightedSelectionConfig; +pub(crate) type CreateArrayMutationConfig = WeightedSelectionConfig; pub(crate) const BASIC_CREATE_ARRAY_MUTATION_CONFIGURATION: CreateArrayMutationConfig = CreateArrayMutationConfig::new([ (CreateArrayMutationOptions::ElementsIndices, 2), (CreateArrayMutationOptions::ElementType, 2), - (CreateArrayMutationOptions::IsReferences, 1), ]); #[derive(Copy, Clone, Debug)] @@ -372,8 +410,7 @@ pub(crate) enum GenerateBool { False, } pub(crate) type GenerateBoolConfig = WeightedSelectionConfig; -pub(crate) const BASIC_GENERATE_BOOL_CONFIGURATION: GenerateBoolConfig = - GenerateBoolConfig::new([(GenerateBool::True, 1), (GenerateBool::False, 1)]); + pub(crate) const GENERATE_BOOL_CONFIGURATION_MOST_TRUE: GenerateBoolConfig = GenerateBoolConfig::new([(GenerateBool::True, 999), (GenerateBool::False, 1)]); pub(crate) const GENERATE_BOOL_CONFIGURATION_MOST_FALSE: GenerateBoolConfig = @@ -411,7 +448,7 @@ pub(crate) const BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION: GenerateNumericTypeC // Compile-time check that configuration has correct number of entries const _: () = { - use noir_ssa_fuzzer::r#type::NumericType; + use noir_ssa_fuzzer::typed_value::NumericType; use strum::EnumCount; assert!( BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION.options_with_weights.len() == NumericType::COUNT, @@ -419,6 +456,44 @@ const _: () = { ); }; +#[derive(Copy, Clone, Debug)] +pub(crate) enum GenerateInitialWitness { + Numeric, + Array, +} +pub(crate) type GenerateInitialWitnessConfig = WeightedSelectionConfig; +pub(crate) const BASIC_GENERATE_INITIAL_WITNESS_CONFIGURATION: GenerateInitialWitnessConfig = + GenerateInitialWitnessConfig::new([ + (GenerateInitialWitness::Numeric, 10), + (GenerateInitialWitness::Array, 1), + ]); + +#[derive(Copy, Clone, Debug)] +pub(crate) enum GenerateType { + Numeric, + Reference, + Array, + Slice, +} + +pub(crate) type GenerateTypeConfig = WeightedSelectionConfig; +pub(crate) const BASIC_GENERATE_TYPE_CONFIGURATION: GenerateTypeConfig = GenerateTypeConfig::new([ + (GenerateType::Numeric, 10), + (GenerateType::Reference, 6), + (GenerateType::Array, 5), + (GenerateType::Slice, 6), +]); +pub(crate) const MAX_ARRAY_SIZE: usize = 10; + +const _: () = { + use noir_ssa_fuzzer::typed_value::Type; + use strum::EnumCount; + assert!( + BASIC_GENERATE_TYPE_CONFIGURATION.options_with_weights.len() == Type::COUNT, + "BASIC_GENERATE_TYPE_CONFIGURATION must have an entry for every Type variant" + ); +}; + #[derive(Copy, Clone, Debug)] pub(crate) enum GenerateCommand { InsertJmpIfBlock, diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/commands_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/commands_mutator.rs index 10cdd487b5d..0c36809ff81 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/commands_mutator.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/commands_mutator.rs @@ -10,7 +10,7 @@ use crate::mutations::{ }; use rand::rngs::StdRng; -fn generate_random_fuzzer_function_command(rng: &mut StdRng) -> FuzzerFunctionCommand { +pub(crate) fn generate_random_fuzzer_function_command(rng: &mut StdRng) -> FuzzerFunctionCommand { match BASIC_GENERATE_COMMAND_CONFIGURATION.select(rng) { GenerateCommand::InsertJmpIfBlock => { FuzzerFunctionCommand::InsertJmpIfBlock { block_then_idx: 0, block_else_idx: 0 } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/function.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/function.rs index 5baa8f18460..eab6173dbb4 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/function.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/function.rs @@ -1,10 +1,10 @@ use crate::mutations::{ - basic_types::{numeric_type::mutate_numeric_type, usize::mutate_usize}, + basic_types::{ssa_fuzzer_type::mutate_ssa_fuzzer_type, usize::mutate_usize}, configuration::{ - BASIC_FUNCTION_MUTATION_CONFIGURATION, BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION, - BASIC_USIZE_MUTATION_CONFIGURATION, FunctionMutationOptions, + BASIC_FUNCTION_MUTATION_CONFIGURATION, BASIC_USIZE_MUTATION_CONFIGURATION, + FunctionMutationOptions, }, - functions::{FunctionData, commands_mutator}, + functions::{FunctionData, commands_mutator, input_types}, }; use rand::rngs::StdRng; @@ -21,11 +21,10 @@ pub(crate) fn mutate_function(data: &mut FunctionData, rng: &mut StdRng) { commands_mutator::mutate_vec_fuzzer_command(&mut data.commands, rng); } FunctionMutationOptions::ReturnType => { - mutate_numeric_type( - &mut data.return_type, - rng, - BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION, - ); + mutate_ssa_fuzzer_type(&mut data.return_type, rng); + } + FunctionMutationOptions::InputTypes => { + input_types::mutate_input_types(&mut data.input_types, rng); } } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/input_types.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/input_types.rs new file mode 100644 index 00000000000..968780c0662 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/input_types.rs @@ -0,0 +1,23 @@ +use crate::mutations::{ + basic_types::{ + ssa_fuzzer_type::{generate_random_ssa_fuzzer_type, mutate_ssa_fuzzer_type}, + vec::mutate_vec, + }, + configuration::{BASIC_GENERATE_TYPE_CONFIGURATION, BASIC_VEC_MUTATION_CONFIGURATION}, +}; +use noir_ssa_fuzzer::typed_value::Type; +use rand::rngs::StdRng; + +pub(crate) fn generate_input_type(rng: &mut StdRng) -> Type { + generate_random_ssa_fuzzer_type(rng, BASIC_GENERATE_TYPE_CONFIGURATION) +} + +pub(crate) fn mutate_input_types(input_types: &mut Vec, rng: &mut StdRng) { + mutate_vec( + input_types, + rng, + mutate_ssa_fuzzer_type, + generate_input_type, + BASIC_VEC_MUTATION_CONFIGURATION, + ); +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/mod.rs index 24392dc0fdf..633a1252c00 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/functions/mod.rs @@ -1,21 +1,24 @@ mod command; mod commands_mutator; mod function; +mod input_types; use crate::fuzz_lib::function_context::FunctionData; use crate::mutations::{ - basic_types::vec::mutate_vec, - configuration::{BASIC_VEC_MUTATION_CONFIGURATION, SIZE_OF_LARGE_ARBITRARY_BUFFER}, + basic_types::{ssa_fuzzer_type::generate_random_ssa_fuzzer_type, vec::mutate_vec}, + configuration::{BASIC_GENERATE_TYPE_CONFIGURATION, BASIC_VEC_MUTATION_CONFIGURATION}, + functions::commands_mutator::generate_random_fuzzer_function_command, functions::function::mutate_function, }; -use libfuzzer_sys::arbitrary::Unstructured; use rand::{Rng, rngs::StdRng}; fn generate_random_function_data(rng: &mut StdRng) -> FunctionData { - let mut buf = [0u8; SIZE_OF_LARGE_ARBITRARY_BUFFER]; - rng.fill(&mut buf); - let mut unstructured = Unstructured::new(&buf); - unstructured.arbitrary().unwrap() + FunctionData { + commands: vec![generate_random_fuzzer_function_command(rng)], + return_instruction_block_idx: rng.gen_range(u8::MIN..u8::MAX).into(), + return_type: generate_random_ssa_fuzzer_type(rng, BASIC_GENERATE_TYPE_CONFIGURATION), + input_types: vec![generate_random_ssa_fuzzer_type(rng, BASIC_GENERATE_TYPE_CONFIGURATION)], + } } pub(crate) fn mutate(vec_function_data: &mut Vec, rng: &mut StdRng) { diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/mod.rs new file mode 100644 index 00000000000..5162f64282b --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/mod.rs @@ -0,0 +1,47 @@ +mod numeric_witness; +mod witness; + +use crate::fuzz_lib::initial_witness::WitnessValue; +use crate::mutations::basic_types::vec::mutate_vec; +use crate::mutations::configuration::{ + ARRAY_WITNESS_MUTATION_CONFIGURATION, BASIC_GENERATE_INITIAL_WITNESS_CONFIGURATION, + BASIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION, BASIC_VEC_MUTATION_CONFIGURATION, + GenerateInitialWitness, MAX_ARRAY_SIZE, SIZE_OF_SMALL_ARBITRARY_BUFFER, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +fn generate_random_witness_value(rng: &mut StdRng) -> WitnessValue { + match BASIC_GENERATE_INITIAL_WITNESS_CONFIGURATION.select(rng) { + GenerateInitialWitness::Numeric => { + let mut bytes = [0u8; SIZE_OF_SMALL_ARBITRARY_BUFFER]; + rng.fill(&mut bytes); + WitnessValue::Numeric(Unstructured::new(&bytes).arbitrary().unwrap()) + } + GenerateInitialWitness::Array => { + let size = rng.gen_range(1..MAX_ARRAY_SIZE); + let first_element = generate_random_witness_value(rng); + let values = (0..size) + .map(|_| witness::generate_witness_of_the_same_type(rng, &first_element)) + .collect(); + WitnessValue::Array(values) + } + } +} + +pub(crate) fn mutate(witness_value: &mut Vec, rng: &mut StdRng) { + mutate_vec( + witness_value, + rng, + |elem, rng| { + witness::mutate( + elem, + rng, + BASIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION, + ARRAY_WITNESS_MUTATION_CONFIGURATION, + ); + }, + generate_random_witness_value, + BASIC_VEC_MUTATION_CONFIGURATION, + ); +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/numeric_witness.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/numeric_witness.rs new file mode 100644 index 00000000000..90f6f769bbe --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/numeric_witness.rs @@ -0,0 +1,277 @@ +//! This file contains mechanisms for deterministically mutating a given [WitnessValue](crate::fuzz_lib::fuzz_target_lib::WitnessValue) value +//! Types of mutations applied: +//! 1. Random (randomly select a new witness value) +//! 2. Max value +//! 3. Min value +//! 4. Small add/sub +//! 5. Power of two add/sub + +use crate::fuzz_lib::initial_witness::{FieldRepresentation, WitnessValueNumeric}; +use crate::mutations::configuration::{ + NumericWitnessMutationConfig, SIZE_OF_SMALL_ARBITRARY_BUFFER, WitnessMutationOptions, +}; +use libfuzzer_sys::arbitrary::Unstructured; +use rand::{Rng, rngs::StdRng}; + +pub(crate) fn generate_element_of_the_same_type( + rng: &mut StdRng, + value: WitnessValueNumeric, +) -> WitnessValueNumeric { + let mut bytes = [0u8; SIZE_OF_SMALL_ARBITRARY_BUFFER]; + rng.fill(&mut bytes); + match value { + WitnessValueNumeric::Field(_) => { + WitnessValueNumeric::Field(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::U128(_) => { + WitnessValueNumeric::U128(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::U64(_) => { + WitnessValueNumeric::U64(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::U32(_) => { + WitnessValueNumeric::U32(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::U16(_) => { + WitnessValueNumeric::U16(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::U8(_) => { + WitnessValueNumeric::U8(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::Boolean(_) => { + WitnessValueNumeric::Boolean(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::I64(_) => { + WitnessValueNumeric::I64(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::I32(_) => { + WitnessValueNumeric::I32(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::I16(_) => { + WitnessValueNumeric::I16(Unstructured::new(&bytes).arbitrary().unwrap()) + } + WitnessValueNumeric::I8(_) => { + WitnessValueNumeric::I8(Unstructured::new(&bytes).arbitrary().unwrap()) + } + } +} + +/// Return new random witness value +struct RandomMutation; +impl RandomMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValueNumeric) { + let mut bytes = [0u8; 17]; + rng.fill(&mut bytes); + *value = Unstructured::new(&bytes).arbitrary().unwrap(); + } +} + +/// Return witness value with max value +struct MaxValueMutation; +impl MaxValueMutation { + fn mutate(value: &mut WitnessValueNumeric) { + let mutated_value = match value { + WitnessValueNumeric::Field(_) => WitnessValueNumeric::Field(FieldRepresentation { + high: 64323764613183177041862057485226039389, + low: 53438638232309528389504892708671455232, // high * 2^128 + low = p - 1 + }), + WitnessValueNumeric::U128(_) => WitnessValueNumeric::U128(u128::MAX), + WitnessValueNumeric::U64(_) => WitnessValueNumeric::U64(u64::MAX), + WitnessValueNumeric::U32(_) => WitnessValueNumeric::U32(u32::MAX), + WitnessValueNumeric::U16(_) => WitnessValueNumeric::U16(u16::MAX), + WitnessValueNumeric::U8(_) => WitnessValueNumeric::U8(u8::MAX), + WitnessValueNumeric::Boolean(_) => WitnessValueNumeric::Boolean(true), + WitnessValueNumeric::I64(_) => WitnessValueNumeric::I64((1 << 63) - 1), // 2^63 - 1, sign bit is 0 + WitnessValueNumeric::I32(_) => WitnessValueNumeric::I32((1 << 31) - 1), // 2^31 - 1, sign bit is 0 + WitnessValueNumeric::I16(_) => WitnessValueNumeric::I16((1 << 15) - 1), // 2^15 - 1, sign bit is 0 + WitnessValueNumeric::I8(_) => WitnessValueNumeric::I8((1 << 7) - 1), // 2^7 - 1, sign bit is 0 + }; + *value = mutated_value; + } +} + +/// Return witness value with min value +struct MinValueMutation; +impl MinValueMutation { + fn mutate(value: &mut WitnessValueNumeric) { + let mutated_value = match value { + WitnessValueNumeric::Field(_) => { + WitnessValueNumeric::Field(FieldRepresentation { high: 0, low: 0 }) + } + WitnessValueNumeric::U128(_) => WitnessValueNumeric::U128(u128::MIN), + WitnessValueNumeric::U64(_) => WitnessValueNumeric::U64(u64::MIN), + WitnessValueNumeric::U32(_) => WitnessValueNumeric::U32(u32::MIN), + WitnessValueNumeric::U16(_) => WitnessValueNumeric::U16(u16::MIN), + WitnessValueNumeric::U8(_) => WitnessValueNumeric::U8(u8::MIN), + WitnessValueNumeric::Boolean(_) => WitnessValueNumeric::Boolean(false), + WitnessValueNumeric::I64(_) => WitnessValueNumeric::I64(1 << 63), // 2^63, sign bit is 1 + WitnessValueNumeric::I32(_) => WitnessValueNumeric::I32(1 << 31), // 2^31, sign bit is 1 + WitnessValueNumeric::I16(_) => WitnessValueNumeric::I16(1 << 15), // 2^15, sign bit is 1 + WitnessValueNumeric::I8(_) => WitnessValueNumeric::I8(1 << 7), // 2^7, sign bit is 1 + }; + *value = mutated_value; + } +} + +/// Add or subtract small value to/from witness value +struct WitnessSmallAddSubMutation; +impl WitnessSmallAddSubMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValueNumeric) { + let small_update: i128 = rng.gen_range(0..256); + let sign: bool = rng.gen_range(0..2) == 0; + *value = match value { + WitnessValueNumeric::Field(field) => WitnessValueNumeric::Field(FieldRepresentation { + high: field.high, + low: if !sign { + field.low.wrapping_add(small_update as u128) + } else { + field.low.wrapping_sub(small_update as u128) + }, + }), + WitnessValueNumeric::U128(u128) => WitnessValueNumeric::U128(if !sign { + u128.wrapping_add(small_update as u128) + } else { + u128.wrapping_sub(small_update as u128) + }), + WitnessValueNumeric::U32(u32) => WitnessValueNumeric::U32(if !sign { + u32.wrapping_add(small_update as u32) + } else { + u32.wrapping_sub(small_update as u32) + }), + WitnessValueNumeric::U16(u16) => WitnessValueNumeric::U16(if !sign { + u16.wrapping_add(small_update as u16) + } else { + u16.wrapping_sub(small_update as u16) + }), + WitnessValueNumeric::U8(u8) => WitnessValueNumeric::U8(if !sign { + u8.wrapping_add(small_update as u8) + } else { + u8.wrapping_sub(small_update as u8) + }), + WitnessValueNumeric::U64(u64) => WitnessValueNumeric::U64(if !sign { + u64.wrapping_add(small_update as u64) + } else { + u64.wrapping_sub(small_update as u64) + }), + WitnessValueNumeric::I64(i64) => WitnessValueNumeric::I64(if !sign { + i64.wrapping_add(small_update as u64) + } else { + i64.wrapping_sub(small_update as u64) + }), + WitnessValueNumeric::I32(i32) => WitnessValueNumeric::I32(if !sign { + i32.wrapping_add(small_update as u32) + } else { + i32.wrapping_sub(small_update as u32) + }), + WitnessValueNumeric::I16(i16) => WitnessValueNumeric::I16(if !sign { + i16.wrapping_add(small_update as u16) + } else { + i16.wrapping_sub(small_update as u16) + }), + WitnessValueNumeric::I8(i8) => WitnessValueNumeric::I8(if !sign { + i8.wrapping_add(small_update as u8) + } else { + i8.wrapping_sub(small_update as u8) + }), + WitnessValueNumeric::Boolean(bool) => { + WitnessValueNumeric::Boolean(*bool ^ (small_update % 2 == 1)) + } + } + } +} + +/// Add or subtract power of two to/from witness value +struct WitnessAddSubPowerOfTwoMutation; +impl WitnessAddSubPowerOfTwoMutation { + fn mutate(rng: &mut StdRng, value: &mut WitnessValueNumeric) { + let exponent = rng.gen_range(0..254); + let sign: bool = rng.gen_range(0..2) == 0; + *value = match value { + WitnessValueNumeric::Field(field) => { + // I don't think implementing field addition is worth the effort, so we just + // add the power of two to the high or low part of the field + let is_high = exponent > 127; + let power_of_two: i128 = 1 << (exponent % 128); + + WitnessValueNumeric::Field(FieldRepresentation { + high: if !sign { + field.high.wrapping_add((is_high as i128 * power_of_two) as u128) + } else { + field.high.wrapping_sub((is_high as i128 * power_of_two) as u128) + }, + low: if !sign { + field.low.wrapping_add((!is_high as i128 * power_of_two) as u128) + } else { + field.low.wrapping_sub((!is_high as i128 * power_of_two) as u128) + }, + }) + } + WitnessValueNumeric::U128(u128) => WitnessValueNumeric::U128(if !sign { + u128.wrapping_add(1 << (exponent % 128)) + } else { + u128.wrapping_sub(1 << (exponent % 128)) + }), + WitnessValueNumeric::U32(u32) => WitnessValueNumeric::U32(if !sign { + u32.wrapping_add(1 << (exponent % 32)) + } else { + u32.wrapping_sub(1 << (exponent % 32)) + }), + WitnessValueNumeric::U16(u16) => WitnessValueNumeric::U16(if !sign { + u16.wrapping_add(1 << (exponent % 16)) + } else { + u16.wrapping_sub(1 << (exponent % 16)) + }), + WitnessValueNumeric::U8(u8) => WitnessValueNumeric::U8(if !sign { + u8.wrapping_add(1 << (exponent % 8)) + } else { + u8.wrapping_sub(1 << (exponent % 8)) + }), + WitnessValueNumeric::U64(u64) => WitnessValueNumeric::U64(if !sign { + u64.wrapping_add(1 << (exponent % 64)) + } else { + u64.wrapping_sub(1 << (exponent % 64)) + }), + WitnessValueNumeric::I64(i64) => WitnessValueNumeric::I64(if !sign { + i64.wrapping_add(1 << (exponent % 64)) + } else { + i64.wrapping_sub(1 << (exponent % 64)) + }), + WitnessValueNumeric::I32(i32) => WitnessValueNumeric::I32(if !sign { + i32.wrapping_add(1 << (exponent % 32)) + } else { + i32.wrapping_sub(1 << (exponent % 32)) + }), + WitnessValueNumeric::I16(i16) => WitnessValueNumeric::I16(if !sign { + i16.wrapping_add(1 << (exponent % 16)) + } else { + i16.wrapping_sub(1 << (exponent % 16)) + }), + WitnessValueNumeric::I8(i8) => WitnessValueNumeric::I8(if !sign { + i8.wrapping_add(1 << (exponent % 8)) + } else { + i8.wrapping_sub(1 << (exponent % 8)) + }), + WitnessValueNumeric::Boolean(bool) => { + WitnessValueNumeric::Boolean(*bool ^ (1 << (exponent % 2) == 1)) + } + } + } +} + +pub(crate) fn mutate( + witness_value: &mut WitnessValueNumeric, + rng: &mut StdRng, + config: NumericWitnessMutationConfig, +) { + match config.select(rng) { + WitnessMutationOptions::Random => RandomMutation::mutate(rng, witness_value), + WitnessMutationOptions::MaxValue => MaxValueMutation::mutate(witness_value), + WitnessMutationOptions::MinValue => MinValueMutation::mutate(witness_value), + WitnessMutationOptions::SmallAddSub => { + WitnessSmallAddSubMutation::mutate(rng, witness_value); + } + WitnessMutationOptions::PowerOfTwoAddSub => { + WitnessAddSubPowerOfTwoMutation::mutate(rng, witness_value); + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/witness.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/witness.rs new file mode 100644 index 00000000000..d1aa9bc8d85 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/initial_witness/witness.rs @@ -0,0 +1,57 @@ +use crate::fuzz_lib::initial_witness::WitnessValue; +use crate::mutations::basic_types::vec::mutate_vec; +use crate::mutations::configuration::{ + ARRAY_WITNESS_MUTATION_CONFIGURATION_INNER, + DETERMINISTIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION, NumericWitnessMutationConfig, + VecMutationConfig, +}; +use crate::mutations::initial_witness::numeric_witness; +use rand::rngs::StdRng; + +pub(crate) fn generate_witness_of_the_same_type( + rng: &mut StdRng, + value: &WitnessValue, +) -> WitnessValue { + match value { + WitnessValue::Numeric(numeric) => { + WitnessValue::Numeric(numeric_witness::generate_element_of_the_same_type(rng, *numeric)) + } + WitnessValue::Array(array) => WitnessValue::Array( + array.iter().map(|elem| generate_witness_of_the_same_type(rng, elem)).collect(), + ), + } +} + +pub(crate) fn mutate( + witness_value: &mut WitnessValue, + rng: &mut StdRng, + numeric_witness_mutation_config: NumericWitnessMutationConfig, + array_witness_mutation_config: VecMutationConfig, +) { + match witness_value { + WitnessValue::Numeric(numeric) => { + numeric_witness::mutate(numeric, rng, numeric_witness_mutation_config); + } + WitnessValue::Array(array) => { + let element_type = array[0].clone(); + mutate_vec( + array, + rng, + // if mutating inner array, use ARRAY_WITNESS_MUTATION_CONFIGURATION_INNER + // in order not to remove/add the elements of the inner array + // if for example initial_witness is [[1,2], [3, 4]] removing or adding something + // to inner element will lead to different sizes and panic of fuzzer + |elem, rng| { + mutate( + elem, + rng, + DETERMINISTIC_NUMERIC_WITNESS_MUTATION_CONFIGURATION, + ARRAY_WITNESS_MUTATION_CONFIGURATION_INNER, + ); + }, + |rng| generate_witness_of_the_same_type(rng, &element_type), + array_witness_mutation_config, + ); + } + } +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs index d9f044d4bc1..5aab2e53ee1 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/argument_mutator.rs @@ -1,21 +1,21 @@ //! This file contains mechanisms for deterministically mutating a given [Argument](crate::fuzz_lib::instruction::Argument) value -use crate::fuzz_lib::instruction::Argument; +use crate::fuzz_lib::instruction::NumericArgument; use crate::mutations::{ basic_types::{numeric_type::mutate_numeric_type, usize::mutate_usize}, configuration::{ - ArgumentMutationOptions, BASIC_ARGUMENT_MUTATION_CONFIGURATION, - BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION, BASIC_USIZE_MUTATION_CONFIGURATION, + BASIC_NUMERIC_ARGUMENT_MUTATION_CONFIGURATION, BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION, + BASIC_USIZE_MUTATION_CONFIGURATION, NumericArgumentMutationOptions, }, }; use rand::rngs::StdRng; -pub(crate) fn argument_mutator(argument: &mut Argument, rng: &mut StdRng) { - match BASIC_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { - ArgumentMutationOptions::MutateIndex => { +pub(crate) fn numeric_argument_mutator(argument: &mut NumericArgument, rng: &mut StdRng) { + match BASIC_NUMERIC_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + NumericArgumentMutationOptions::MutateIndex => { mutate_usize(&mut argument.index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); } - ArgumentMutationOptions::ChangeType => { + NumericArgumentMutationOptions::ChangeType => { mutate_numeric_type( &mut argument.numeric_type, rng, diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs index 03342b5d03a..3bdc99853ff 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_block_mutator.rs @@ -1,6 +1,6 @@ //! This file contains mechanisms for deterministically mutating a given [InstructionBlock](crate::fuzz_lib::instruction::InstructionBlock) value -use crate::fuzz_lib::instruction::{Argument, Instruction, InstructionBlock}; +use crate::fuzz_lib::instruction::{Argument, Instruction, InstructionBlock, NumericArgument}; use crate::mutations::configuration::SIZE_OF_LARGE_ARBITRARY_BUFFER; use crate::mutations::{ basic_types::{ @@ -8,16 +8,22 @@ use crate::mutations::{ point::generate_random_point, scalar::generate_random_scalar, vec::mutate_vec, }, configuration::{ - BASIC_GENERATE_BOOL_CONFIGURATION, BASIC_GENERATE_INSTRUCTION_CONFIGURATION, - BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION, BASIC_VEC_MUTATION_CONFIGURATION, - GENERATE_BOOL_CONFIGURATION_MOST_FALSE, GENERATE_BOOL_CONFIGURATION_MOST_TRUE, - GenerateInstruction, SIZE_OF_SMALL_ARBITRARY_BUFFER, + BASIC_GENERATE_INSTRUCTION_CONFIGURATION, BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION, + BASIC_VEC_MUTATION_CONFIGURATION, GENERATE_BOOL_CONFIGURATION_MOST_FALSE, + GENERATE_BOOL_CONFIGURATION_MOST_TRUE, GenerateInstruction, SIZE_OF_SMALL_ARBITRARY_BUFFER, }, instructions::instruction_mutator::instruction_mutator, }; use libfuzzer_sys::arbitrary::Unstructured; use rand::{Rng, RngCore, rngs::StdRng}; +fn generate_random_numeric_argument(rng: &mut StdRng) -> NumericArgument { + let mut buf = [0u8; SIZE_OF_SMALL_ARBITRARY_BUFFER]; + rng.fill(&mut buf); + let mut unstructured = Unstructured::new(&buf); + unstructured.arbitrary().unwrap() +} + fn generate_random_argument(rng: &mut StdRng) -> Argument { let mut buf = [0u8; SIZE_OF_SMALL_ARBITRARY_BUFFER]; rng.fill(&mut buf); @@ -28,57 +34,57 @@ fn generate_random_argument(rng: &mut StdRng) -> Argument { fn generate_random_instruction(rng: &mut StdRng) -> Instruction { match BASIC_GENERATE_INSTRUCTION_CONFIGURATION.select(rng) { GenerateInstruction::AddChecked => Instruction::AddChecked { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::SubChecked => Instruction::SubChecked { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::MulChecked => Instruction::MulChecked { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Div => Instruction::Div { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Eq => Instruction::Eq { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Mod => Instruction::Mod { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, - GenerateInstruction::Not => Instruction::Not { lhs: generate_random_argument(rng) }, + GenerateInstruction::Not => Instruction::Not { lhs: generate_random_numeric_argument(rng) }, GenerateInstruction::Shl => Instruction::Shl { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Shr => Instruction::Shr { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Cast => Instruction::Cast { - lhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), type_: generate_random_numeric_type(rng, BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION), }, GenerateInstruction::And => Instruction::And { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Or => Instruction::Or { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Xor => Instruction::Xor { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::Lt => Instruction::Lt { - lhs: generate_random_argument(rng), - rhs: generate_random_argument(rng), + lhs: generate_random_numeric_argument(rng), + rhs: generate_random_numeric_argument(rng), }, GenerateInstruction::AddSubConstrain => Instruction::AddSubConstrain { lhs: rng.gen_range(usize::MIN..usize::MAX), @@ -100,20 +106,16 @@ fn generate_random_instruction(rng: &mut StdRng) -> Instruction { }, GenerateInstruction::CreateArray => Instruction::CreateArray { elements_indices: vec![rng.gen_range(usize::MIN..usize::MAX); 10], - element_type: generate_random_numeric_type( - rng, - BASIC_GENERATE_NUMERIC_TYPE_CONFIGURATION, - ), - is_references: generate_random_bool(rng, BASIC_GENERATE_BOOL_CONFIGURATION), + element_type: generate_random_argument(rng).value_type, }, GenerateInstruction::ArrayGet => Instruction::ArrayGet { array_index: rng.gen_range(usize::MIN..usize::MAX), - index: generate_random_argument(rng), + index: generate_random_numeric_argument(rng), safe_index: generate_random_bool(rng, GENERATE_BOOL_CONFIGURATION_MOST_TRUE), }, GenerateInstruction::ArraySet => Instruction::ArraySet { array_index: rng.gen_range(usize::MIN..usize::MAX), - index: generate_random_argument(rng), + index: generate_random_numeric_argument(rng), value_index: rng.gen_range(usize::MIN..usize::MAX), safe_index: generate_random_bool(rng, GENERATE_BOOL_CONFIGURATION_MOST_TRUE), }, 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 655bec9d950..fad7a4f480d 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs @@ -4,7 +4,10 @@ //! 2. Argument mutation use crate::fuzz_lib::instruction::Instruction; -use crate::mutations::configuration::BOOL_MUTATION_CONFIGURATION_MOSTLY_FALSE; +use crate::mutations::configuration::{ + ArgumentMutationOptions, BASIC_ARGUMENT_MUTATION_CONFIGURATION, + BOOL_MUTATION_CONFIGURATION_MOSTLY_FALSE, +}; use crate::mutations::{ basic_types::{ bool::mutate_bool, @@ -12,6 +15,7 @@ use crate::mutations::{ point::{generate_random_point, mutate_point}, scalar::generate_random_scalar, scalar::mutate_scalar, + ssa_fuzzer_type::mutate_ssa_fuzzer_type, usize::mutate_usize, vec::mutate_vec, }, @@ -28,7 +32,7 @@ use crate::mutations::{ InstructionMutationOptions, SIZE_OF_SMALL_ARBITRARY_BUFFER, Sha256CompressionMutationOptions, }, - instructions::argument_mutator::argument_mutator, + instructions::argument_mutator::numeric_argument_mutator, }; use libfuzzer_sys::arbitrary::Unstructured; use rand::{Rng, rngs::StdRng}; @@ -63,26 +67,35 @@ impl InstructionArgumentsMutation { | Instruction::Lt { lhs, rhs } => { match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { InstructionArgumentMutationOptions::Left => { - argument_mutator(lhs, rng); + numeric_argument_mutator(lhs, rng); } InstructionArgumentMutationOptions::Right => { - argument_mutator(rhs, rng); + numeric_argument_mutator(rhs, rng); } } } + Instruction::Not { lhs } => { + numeric_argument_mutator(lhs, rng); + } + // Unary operations - Instruction::Not { lhs } - | Instruction::AddToMemory { lhs } - | Instruction::LoadFromMemory { memory_addr: lhs } => { - argument_mutator(lhs, rng); + Instruction::AddToMemory { lhs } | Instruction::LoadFromMemory { memory_addr: lhs } => { + match BASIC_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + ArgumentMutationOptions::MutateType => { + mutate_ssa_fuzzer_type(&mut lhs.value_type, rng); + } + ArgumentMutationOptions::MutateIndex => { + mutate_usize(&mut lhs.index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); + } + } } // Special cases Instruction::Cast { lhs, type_ } => { match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { InstructionArgumentMutationOptions::Left => { - argument_mutator(lhs, rng); + numeric_argument_mutator(lhs, rng); } InstructionArgumentMutationOptions::Right => { mutate_numeric_type(type_, rng, BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION); @@ -92,7 +105,18 @@ impl InstructionArgumentsMutation { Instruction::SetToMemory { memory_addr_index, value } => { match BASIC_INSTRUCTION_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { InstructionArgumentMutationOptions::Left => { - argument_mutator(value, rng); + match BASIC_ARGUMENT_MUTATION_CONFIGURATION.select(rng) { + ArgumentMutationOptions::MutateType => { + mutate_ssa_fuzzer_type(&mut value.value_type, rng); + } + ArgumentMutationOptions::MutateIndex => { + mutate_usize( + &mut value.index, + rng, + BASIC_USIZE_MUTATION_CONFIGURATION, + ); + } + } } InstructionArgumentMutationOptions::Right => { mutate_usize(memory_addr_index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); @@ -119,7 +143,7 @@ impl InstructionArgumentsMutation { mutate_usize(array_index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); } ArrayGetMutationOptions::Index => { - argument_mutator(index, rng); + numeric_argument_mutator(index, rng); } ArrayGetMutationOptions::SafeIndex => { mutate_bool(safe_index, rng, BOOL_MUTATION_CONFIGURATION_MOSTLY_TRUE); @@ -132,7 +156,7 @@ impl InstructionArgumentsMutation { mutate_usize(array_index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); } ArraySetMutationOptions::Index => { - argument_mutator(index, rng); + numeric_argument_mutator(index, rng); } ArraySetMutationOptions::ValueIndex => { mutate_usize(value_index, rng, BASIC_USIZE_MUTATION_CONFIGURATION); @@ -142,7 +166,7 @@ impl InstructionArgumentsMutation { } } } - Instruction::CreateArray { elements_indices, element_type, is_references } => { + Instruction::CreateArray { elements_indices, element_type } => { match BASIC_CREATE_ARRAY_MUTATION_CONFIGURATION.select(rng) { CreateArrayMutationOptions::ElementsIndices => { mutate_vec( @@ -156,14 +180,7 @@ impl InstructionArgumentsMutation { ); } CreateArrayMutationOptions::ElementType => { - mutate_numeric_type( - element_type, - rng, - BASIC_NUMERIC_TYPE_MUTATION_CONFIGURATION, - ); - } - CreateArrayMutationOptions::IsReferences => { - mutate_bool(is_references, rng, BASIC_BOOL_MUTATION_CONFIGURATION); + mutate_ssa_fuzzer_type(element_type, rng); } } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs index 86a316c8dcb..fdcd9590bd2 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/mod.rs @@ -1,8 +1,8 @@ mod basic_types; mod configuration; mod functions; +mod initial_witness; mod instructions; -mod witness_mutator; use crate::fuzz_lib::fuzzer::FuzzerData; use crate::mutations::configuration::{ @@ -21,8 +21,7 @@ pub(crate) fn mutate(data: &mut FuzzerData, rng: &mut StdRng) { instructions::mutate(&mut data.instruction_blocks, rng); } FuzzerDataMutationOptions::Witnesses => { - let idx = rng.gen_range(0..data.initial_witness.len()); - witness_mutator::mutate(&mut data.initial_witness[idx], rng); + initial_witness::mutate(&mut data.initial_witness, rng); } } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs deleted file mode 100644 index 1d921d4f1a9..00000000000 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/witness_mutator.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! This file contains mechanisms for deterministically mutating a given [WitnessValue](crate::fuzz_lib::fuzz_target_lib::WitnessValue) value -//! Types of mutations applied: -//! 1. Random (randomly select a new witness value) -//! 2. Max value -//! 3. Min value - -use crate::fuzz_lib::function_context::{FieldRepresentation, WitnessValue}; -use crate::mutations::configuration::{ - BASIC_WITNESS_MUTATION_CONFIGURATION, WitnessMutationOptions, -}; -use libfuzzer_sys::arbitrary::Unstructured; -use rand::{Rng, rngs::StdRng}; - -/// Return new random witness value -struct RandomMutation; -impl RandomMutation { - fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { - let mut bytes = [0u8; 17]; - rng.fill(&mut bytes); - *value = Unstructured::new(&bytes).arbitrary().unwrap(); - } -} - -/// Return witness value with max value -struct MaxValueMutation; -impl MaxValueMutation { - fn mutate(value: &mut WitnessValue) { - let mutated_value = match value { - WitnessValue::Field(_) => WitnessValue::Field(FieldRepresentation { - high: 64323764613183177041862057485226039389, - low: 53438638232309528389504892708671455232, // high * 2^128 + low = p - 1 - }), - WitnessValue::U64(_) => WitnessValue::U64(u64::MAX), - WitnessValue::Boolean(_) => WitnessValue::Boolean(true), - WitnessValue::I64(_) => WitnessValue::I64((1 << 63) - 1), // 2^63 - 1, sign bit is 0 - WitnessValue::I32(_) => WitnessValue::I32((1 << 31) - 1), // 2^31 - 1, sign bit is 0 - }; - *value = mutated_value; - } -} - -/// Return witness value with min value -struct MinValueMutation; -impl MinValueMutation { - fn mutate(value: &mut WitnessValue) { - let mutated_value = match value { - WitnessValue::Field(_) => WitnessValue::Field(FieldRepresentation { high: 0, low: 0 }), - WitnessValue::U64(_) => WitnessValue::U64(0), - WitnessValue::Boolean(_) => WitnessValue::Boolean(false), - WitnessValue::I64(_) => WitnessValue::I64(1 << 63), // 2^63, sign bit is 1 - WitnessValue::I32(_) => WitnessValue::I32(1 << 31), // 2^31, sign bit is 1 - }; - *value = mutated_value; - } -} - -/// Add or subtract small value to/from witness value -struct WitnessSmallAddSubMutation; -impl WitnessSmallAddSubMutation { - fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { - let small_update: i128 = rng.gen_range(0..256); - let sign: bool = rng.gen_range(0..2) == 0; - *value = match value { - WitnessValue::Field(field) => WitnessValue::Field(FieldRepresentation { - high: field.high, - low: if !sign { - field.low.wrapping_add(small_update as u128) - } else { - field.low.wrapping_sub(small_update as u128) - }, - }), - WitnessValue::U64(u64) => WitnessValue::U64(if !sign { - u64.wrapping_add(small_update as u64) - } else { - u64.wrapping_sub(small_update as u64) - }), - WitnessValue::I64(i64) => WitnessValue::I64(if !sign { - i64.wrapping_add(small_update as u64) - } else { - i64.wrapping_sub(small_update as u64) - }), - WitnessValue::I32(i32) => WitnessValue::I32(if !sign { - i32.wrapping_add(small_update as u32) - } else { - i32.wrapping_sub(small_update as u32) - }), - WitnessValue::Boolean(bool) => WitnessValue::Boolean(*bool ^ (small_update % 2 == 1)), - } - } -} - -/// Add or subtract power of two to/from witness value -struct WitnessAddSubPowerOfTwoMutation; -impl WitnessAddSubPowerOfTwoMutation { - fn mutate(rng: &mut StdRng, value: &mut WitnessValue) { - let exponent = rng.gen_range(0..254); - let sign: bool = rng.gen_range(0..2) == 0; - *value = match value { - WitnessValue::Field(field) => { - // I don't think implementing field addition is worth the effort, so we just - // add the power of two to the high or low part of the field - let is_high = exponent > 127; - let power_of_two: i128 = 1 << (exponent % 128); - - WitnessValue::Field(FieldRepresentation { - high: if !sign { - field.high.wrapping_add((is_high as i128 * power_of_two) as u128) - } else { - field.high.wrapping_sub((is_high as i128 * power_of_two) as u128) - }, - low: if !sign { - field.low.wrapping_add((!is_high as i128 * power_of_two) as u128) - } else { - field.low.wrapping_sub((!is_high as i128 * power_of_two) as u128) - }, - }) - } - WitnessValue::U64(u64) => WitnessValue::U64(if !sign { - u64.wrapping_add(1 << (exponent % 64)) - } else { - u64.wrapping_sub(1 << (exponent % 64)) - }), - WitnessValue::I64(i64) => WitnessValue::I64(if !sign { - i64.wrapping_add(1 << (exponent % 64)) - } else { - i64.wrapping_sub(1 << (exponent % 64)) - }), - WitnessValue::I32(i32) => WitnessValue::I32(if !sign { - i32.wrapping_add(1 << (exponent % 32)) - } else { - i32.wrapping_sub(1 << (exponent % 32)) - }), - WitnessValue::Boolean(bool) => { - WitnessValue::Boolean(*bool ^ (1 << (exponent % 2) == 1)) - } - } - } -} - -pub(crate) fn mutate(witness_value: &mut WitnessValue, rng: &mut StdRng) { - match BASIC_WITNESS_MUTATION_CONFIGURATION.select(rng) { - WitnessMutationOptions::Random => RandomMutation::mutate(rng, witness_value), - WitnessMutationOptions::MaxValue => MaxValueMutation::mutate(witness_value), - WitnessMutationOptions::MinValue => MinValueMutation::mutate(witness_value), - WitnessMutationOptions::SmallAddSub => { - WitnessSmallAddSubMutation::mutate(rng, witness_value); - } - WitnessMutationOptions::PowerOfTwoAddSub => { - WitnessAddSubPowerOfTwoMutation::mutate(rng, witness_value); - } - } -} diff --git a/tooling/ssa_fuzzer/src/builder.rs b/tooling/ssa_fuzzer/src/builder.rs index 3b08b33ea78..deb8bb1e2ce 100644 --- a/tooling/ssa_fuzzer/src/builder.rs +++ b/tooling/ssa_fuzzer/src/builder.rs @@ -1,4 +1,4 @@ -use crate::r#type::{NumericType, Point, Scalar, Type, TypedValue}; +use crate::typed_value::{NumericType, Point, Scalar, Type, TypedValue}; use acvm::FieldElement; use noir_ssa_executor::compiler::compile_from_ssa; use noirc_driver::{CompileOptions, CompiledProgram}; @@ -172,7 +172,7 @@ impl FuzzerBuilder { /// Inserts a cast instruction pub fn insert_cast(&mut self, value: TypedValue, cast_type: Type) -> TypedValue { assert!(value.is_numeric(), "must be numeric"); - assert!(cast_type.is_numeric(), "must be numeric"); + assert!(cast_type.is_numeric(), "must be numeric, got {cast_type:?}"); let init_bit_length = value.bit_length(); @@ -383,6 +383,20 @@ impl FuzzerBuilder { TypedValue::new(res, array_elements_type) } + pub fn insert_slice(&mut self, elements: Vec) -> TypedValue { + let element_type = elements[0].type_of_variable.clone(); + assert!( + elements.iter().all(|e| e.type_of_variable == element_type), + "All elements must have the same type" + ); + let array_elements_type = Type::Slice(Arc::new(vec![element_type])); + let res = self.builder.insert_make_array( + elements.into_iter().map(|e| e.value_id).collect(), + array_elements_type.clone().into(), + ); + TypedValue::new(res, array_elements_type) + } + /// Inserts a to_le_radix intrinsic call that decomposes a field into little-endian radix representation /// /// # Arguments diff --git a/tooling/ssa_fuzzer/src/lib.rs b/tooling/ssa_fuzzer/src/lib.rs index a5c6849d585..f545f85f85a 100644 --- a/tooling/ssa_fuzzer/src/lib.rs +++ b/tooling/ssa_fuzzer/src/lib.rs @@ -3,13 +3,13 @@ pub mod builder; pub mod runner; -pub mod r#type; +pub mod typed_value; #[cfg(test)] mod tests { use crate::builder::{FuzzerBuilder, FuzzerBuilderError, InstructionWithTwoArgs}; use crate::runner::{CompareResults, run_and_compare}; - use crate::r#type::{NumericType, Type, TypedValue}; + use crate::typed_value::{NumericType, Type, TypedValue}; use acvm::acir::native_types::{Witness, WitnessMap}; use acvm::{AcirField, FieldElement}; use noirc_driver::{CompileOptions, CompiledProgram}; diff --git a/tooling/ssa_fuzzer/src/runner.rs b/tooling/ssa_fuzzer/src/runner.rs index 8fc1dcec354..ee26a047f55 100644 --- a/tooling/ssa_fuzzer/src/runner.rs +++ b/tooling/ssa_fuzzer/src/runner.rs @@ -6,6 +6,7 @@ use acvm::{ }, }; use noir_ssa_executor::runner::execute_single; +use std::collections::BTreeSet; #[derive(Debug)] pub enum CompareResults { @@ -29,10 +30,8 @@ pub fn run_and_compare( let return_witnesses_acir = &acir_program.functions[0].return_values; let return_witnesses_brillig = &brillig_program.functions[0].return_values; - assert!(return_witnesses_acir.0.len() <= 1, "Multiple return value witnesses encountered"); - assert!(return_witnesses_brillig.0.len() <= 1, "Multiple return value witnesses encountered"); - let return_witness_acir: Option<&Witness> = return_witnesses_acir.0.first(); - let return_witness_brillig: Option<&Witness> = return_witnesses_brillig.0.first(); + let return_witness_acir: BTreeSet = return_witnesses_acir.0.clone(); + let return_witness_brillig: BTreeSet = return_witnesses_brillig.0.clone(); // we found bug in case of // 1) acir_result != brillig_result @@ -43,28 +42,21 @@ pub fn run_and_compare( // we assume that if execution for both modes succeeds both programs returned something let acir_witness_map = acir_witness.peek().unwrap().witness.clone(); let brillig_witness_map = brillig_witness.peek().unwrap().witness.clone(); - let acir_result = acir_witness_map[return_witness_acir.unwrap()]; - let brillig_result = brillig_witness_map[return_witness_brillig.unwrap()]; - if acir_result == brillig_result { + let acir_results = return_witness_acir.iter().map(|w| acir_witness_map[w]); + let brillig_results = return_witness_brillig.iter().map(|w| brillig_witness_map[w]); + let results_equal = acir_results.eq(brillig_results); + if results_equal { CompareResults::Agree(acir_witness) } else { CompareResults::Disagree(acir_witness, brillig_witness) } } - (Err(acir_error), Ok(brillig_witness)) => match return_witness_brillig { - Some(_) => CompareResults::AcirFailed(acir_error.to_string(), brillig_witness), - None => CompareResults::BothFailed( - acir_error.to_string(), - "Brillig program does not return anything".into(), - ), - }, - (Ok(acir_witness), Err(brillig_error)) => match return_witness_acir { - Some(_) => CompareResults::BrilligFailed(brillig_error.to_string(), acir_witness), - None => CompareResults::BothFailed( - "ACIR program does not return anything".into(), - brillig_error.to_string(), - ), - }, + (Err(acir_error), Ok(brillig_witness)) => { + CompareResults::AcirFailed(acir_error.to_string(), brillig_witness) + } + (Ok(acir_witness), Err(brillig_error)) => { + CompareResults::BrilligFailed(brillig_error.to_string(), acir_witness) + } (Err(acir_error), Err(brillig_error)) => { CompareResults::BothFailed(acir_error.to_string(), brillig_error.to_string()) } diff --git a/tooling/ssa_fuzzer/src/type.rs b/tooling/ssa_fuzzer/src/type.rs index a21c40efa2f..6c8106ba54b 100644 --- a/tooling/ssa_fuzzer/src/type.rs +++ b/tooling/ssa_fuzzer/src/type.rs @@ -58,7 +58,7 @@ impl From for SsaNumericType { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumCount)] pub enum Type { Numeric(NumericType), Reference(Arc), @@ -66,6 +66,13 @@ pub enum Type { Slice(Arc>), } +/// Used as default value for mutations +impl Default for Type { + fn default() -> Self { + Type::Numeric(NumericType::Field) + } +} + impl Type { pub fn bit_length(&self) -> u32 { match self { @@ -222,6 +229,9 @@ impl From for Type { SsaType::Reference(element_type) => { Type::Reference(Arc::new((*element_type).clone().into())) } + SsaType::Slice(element_types) => { + Type::Slice(Arc::new(element_types.iter().map(|t| t.clone().into()).collect())) + } _ => unreachable!("Not supported type: {:?}", type_), } } diff --git a/tooling/ssa_fuzzer/src/typed_value.rs b/tooling/ssa_fuzzer/src/typed_value.rs new file mode 100644 index 00000000000..a3b69b67714 --- /dev/null +++ b/tooling/ssa_fuzzer/src/typed_value.rs @@ -0,0 +1,285 @@ +use libfuzzer_sys::arbitrary; +use libfuzzer_sys::arbitrary::Arbitrary; +use noirc_evaluator::ssa::ir::types::{NumericType as SsaNumericType, Type as SsaType}; +use noirc_evaluator::ssa::ir::{map::Id, value::Value}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use strum_macros::EnumCount; + +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize, EnumCount)] +pub enum NumericType { + Field, + Boolean, + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, +} + +impl NumericType { + pub fn bit_length(&self) -> u32 { + match self { + NumericType::Field => 254, + NumericType::Boolean => 1, + NumericType::U8 => 8, + NumericType::U16 => 16, + NumericType::U32 => 32, + NumericType::U64 => 64, + NumericType::U128 => 128, + NumericType::I8 => 8, + NumericType::I16 => 16, + NumericType::I32 => 32, + NumericType::I64 => 64, + } + } +} + +impl From for SsaNumericType { + fn from(numeric_type: NumericType) -> Self { + let bit_size = numeric_type.bit_length(); + match numeric_type { + NumericType::Field => SsaNumericType::NativeField, + NumericType::Boolean => SsaNumericType::Unsigned { bit_size }, + NumericType::U8 => SsaNumericType::Unsigned { bit_size }, + NumericType::U16 => SsaNumericType::Unsigned { bit_size }, + NumericType::U32 => SsaNumericType::Unsigned { bit_size }, + NumericType::U64 => SsaNumericType::Unsigned { bit_size }, + NumericType::U128 => SsaNumericType::Unsigned { bit_size }, + NumericType::I8 => SsaNumericType::Signed { bit_size }, + NumericType::I16 => SsaNumericType::Signed { bit_size }, + NumericType::I32 => SsaNumericType::Signed { bit_size }, + NumericType::I64 => SsaNumericType::Signed { bit_size }, + } + } +} + +#[derive(Arbitrary, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumCount)] +pub enum Type { + Numeric(NumericType), + Reference(Arc), + Array(Arc>, u32), + Slice(Arc>), +} + +/// Used as default value for mutations +impl Default for Type { + fn default() -> Self { + Type::Numeric(NumericType::Field) + } +} + +impl Type { + pub fn bit_length(&self) -> u32 { + match self { + Type::Numeric(numeric_type) => numeric_type.bit_length(), + Type::Array(_, _) => unreachable!("Array type unexpected"), + Type::Slice(_) => unreachable!("Slice type unexpected"), + Type::Reference(value_type) => value_type.bit_length(), + } + } + + pub fn is_numeric(&self) -> bool { + matches!(self, Type::Numeric(_)) + } + + pub fn is_reference(&self) -> bool { + matches!(self, Type::Reference(_)) + } + + pub fn is_array(&self) -> bool { + matches!(self, Type::Array(_, _)) + } + + pub fn is_slice(&self) -> bool { + matches!(self, Type::Slice(_)) + } + + pub fn is_field(&self) -> bool { + matches!(self, Type::Numeric(NumericType::Field)) + } + + pub fn is_boolean(&self) -> bool { + matches!(self, Type::Numeric(NumericType::Boolean)) + } + + pub fn is_array_of_references(&self) -> bool { + match self { + Type::Array(element_types, _) => element_types.iter().all(|t| t.is_reference()), + _ => false, + } + } + + pub fn unwrap_reference(&self) -> Type { + match self { + Type::Reference(value_type) => value_type.as_ref().clone(), + _ => panic!("Expected Reference, found {self:?}"), + } + } + + pub fn unwrap_numeric(&self) -> NumericType { + match self { + Type::Numeric(numeric_type) => *numeric_type, + _ => panic!("Expected NumericType, found {self:?}"), + } + } + + pub fn unwrap_array_element_type(&self) -> Type { + match self { + Type::Array(element_types, _) => element_types[0].clone(), + _ => panic!("Expected Array, found {self:?}"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct TypedValue { + pub value_id: Id, + pub type_of_variable: Type, +} + +impl TypedValue { + pub fn new(value_id: Id, type_of_variable: Type) -> Self { + Self { value_id, type_of_variable } + } + + pub fn is_numeric(&self) -> bool { + self.type_of_variable.is_numeric() + } + + pub fn is_reference(&self) -> bool { + self.type_of_variable.is_reference() + } + + pub fn is_array(&self) -> bool { + self.type_of_variable.is_array() + } + + pub fn is_field(&self) -> bool { + self.type_of_variable.is_field() + } + + pub fn is_boolean(&self) -> bool { + self.type_of_variable.is_boolean() + } + + /// Returns the bit length of the type + /// + /// For field returns 254, for references returns the bit length of the referenced type + /// Panics if the type is an array + pub fn bit_length(&self) -> u32 { + self.type_of_variable.bit_length() + } + + pub fn same_types(&self, other: &TypedValue) -> bool { + self.type_of_variable == other.type_of_variable + } + + pub fn unwrap_numeric(&self) -> NumericType { + match self.type_of_variable { + Type::Numeric(numeric_type) => numeric_type, + _ => panic!("Expected NumericType, found {:?}", self.type_of_variable), + } + } +} + +impl From for Type { + fn from(type_: SsaType) -> Self { + match type_ { + SsaType::Numeric(SsaNumericType::NativeField) => Type::Numeric(NumericType::Field), + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 1 }) => { + Type::Numeric(NumericType::Boolean) + } + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 8 }) => { + Type::Numeric(NumericType::U8) + } + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 16 }) => { + Type::Numeric(NumericType::U16) + } + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 32 }) => { + Type::Numeric(NumericType::U32) + } + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 64 }) => { + Type::Numeric(NumericType::U64) + } + SsaType::Numeric(SsaNumericType::Unsigned { bit_size: 128 }) => { + Type::Numeric(NumericType::U128) + } + SsaType::Numeric(SsaNumericType::Signed { bit_size: 8 }) => { + Type::Numeric(NumericType::I8) + } + SsaType::Numeric(SsaNumericType::Signed { bit_size: 16 }) => { + Type::Numeric(NumericType::I16) + } + SsaType::Numeric(SsaNumericType::Signed { bit_size: 32 }) => { + Type::Numeric(NumericType::I32) + } + SsaType::Numeric(SsaNumericType::Signed { bit_size: 64 }) => { + Type::Numeric(NumericType::I64) + } + SsaType::Array(element_types, length) => Type::Array( + Arc::new(element_types.iter().map(|t| t.clone().into()).collect()), + length, + ), + SsaType::Reference(element_type) => { + Type::Reference(Arc::new((*element_type).clone().into())) + } + SsaType::Slice(element_types) => { + Type::Slice(Arc::new(element_types.iter().map(|t| t.clone().into()).collect())) + } + _ => unreachable!("Not supported type: {:?}", type_), + } + } +} + +impl From for SsaType { + fn from(typ: Type) -> Self { + match typ { + Type::Numeric(numeric_type) => SsaType::Numeric(numeric_type.into()), + Type::Array(element_types, length) => SsaType::Array( + Arc::new(element_types.iter().map(|t| t.clone().into()).collect()), + length, + ), + Type::Reference(element_type) => { + SsaType::Reference(Arc::new((*element_type).clone().into())) + } + Type::Slice(element_types) => { + SsaType::Slice(Arc::new(element_types.iter().map(|t| t.clone().into()).collect())) + } + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Point { + pub x: TypedValue, + pub y: TypedValue, + pub is_infinite: TypedValue, +} +impl Point { + pub fn validate(&self) -> bool { + self.x.is_field() && self.y.is_field() && self.is_infinite.is_boolean() + } + pub fn to_id_vec(&self) -> Vec> { + vec![self.x.value_id, self.y.value_id, self.is_infinite.value_id] + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Scalar { + pub lo: TypedValue, + pub hi: TypedValue, +} + +impl Scalar { + pub fn validate(&self) -> bool { + self.lo.is_field() && self.hi.is_field() + } + pub fn to_id_vec(&self) -> Vec> { + vec![self.lo.value_id, self.hi.value_id] + } +}