Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
607 changes: 239 additions & 368 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs

Large diffs are not rendered by default.

61 changes: 17 additions & 44 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/function_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<FuzzerFunctionCommand>,
/// Input types of the function
///
/// Overwritten for main function by the types of the initial witness
pub(crate) input_types: Vec<Type>,
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),
}
}
}
Expand Down Expand Up @@ -152,28 +125,28 @@ 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<Id<Function>, FunctionInfo>,
}

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<NumericType>,
types: Vec<Type>,
instruction_blocks: &'a Vec<InstructionBlock>,
context_options: FunctionContextOptions,
return_type: NumericType,
return_type: Type,
defined_functions: BTreeMap<Id<Function>, 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();
Expand Down Expand Up @@ -211,7 +184,7 @@ impl<'a> FuzzerFunctionContext<'a> {
values_types: Vec<ValueWithType>,
instruction_blocks: &'a Vec<InstructionBlock>,
context_options: FunctionContextOptions,
return_type: NumericType,
return_type: Type,
defined_functions: BTreeMap<Id<Function>, FunctionInfo>,
acir_builder: &'a mut FuzzerBuilder,
brillig_builder: &'a mut FuzzerBuilder,
Expand Down Expand Up @@ -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(),
);
}
}
62 changes: 22 additions & 40 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs
Original file line number Diff line number Diff line change
@@ -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<FieldElement>, Vec<FieldElement>, Vec<NumericType>) {
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<FuzzerOutput> {
// 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)
}
48 changes: 21 additions & 27 deletions tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,35 @@
//! - 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<FunctionData>,
pub(crate) initial_witness:
[WitnessValue; (NUMBER_OF_VARIABLES_INITIAL - NUMBER_OF_PREDEFINED_VARIABLES) as usize],
pub(crate) initial_witness: Vec<WitnessValue>,
pub(crate) instruction_blocks: Vec<InstructionBlock>,
}

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![],
}
}
Expand All @@ -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<FieldElement> {
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()
}
}

Expand All @@ -86,11 +84,7 @@ impl Fuzzer {
Self { contexts }
}

pub(crate) fn process_function(
&mut self,
function_data: FunctionData,
types: Vec<NumericType>,
) {
pub(crate) fn process_function(&mut self, function_data: FunctionData, types: Vec<Type>) {
for context in &mut self.contexts {
context.process_function(function_data.clone(), types.clone());
}
Expand All @@ -111,15 +105,17 @@ impl Fuzzer {
}
let results_set = execution_results
.values()
.map(|result| -> Option<FieldElement> { result.as_ref().map(|r| r.get_return_value()) })
.map(|result| -> Option<Vec<FieldElement>> {
result.as_ref().map(|r| r.get_return_values())
})
.collect::<HashSet<_>>();

if results_set.len() != 1 {
let mut panic_string = String::new();
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"));
}
Expand Down Expand Up @@ -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:?}"
Expand Down
Loading
Loading