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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ impl std::fmt::Display for RuntimeType {
}
}

/// Iterate over every Value in this DFG in no particular order, including unused Values,
/// for testing purposes.
pub fn function_values_iter(func: &Function) -> impl DoubleEndedIterator<Item = (ValueId, &Value)> {
func.dfg.values_iter()
}

/// FunctionId is a reference for a function
///
/// This Id is how each function refers to other functions
Expand Down
1 change: 1 addition & 0 deletions tooling/ast_fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ noir_greybox_fuzzer.workspace = true
arbtest.workspace = true
insta.workspace = true
similar-asserts.workspace = true
serde_json.workspace = true
105 changes: 105 additions & 0 deletions tooling/ast_fuzzer/tests/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! Test that the SSA of an arbitrary program can be printed and parsed back.
//!
//! ```shell
//! cargo test -p noir_ast_fuzzer --test parser
//! ```
use std::time::Duration;

use acir::circuit::ExpressionWidth;
use arbtest::arbtest;
use noir_ast_fuzzer::{Config, DisplayAstAsNoir, arb_program};
use noirc_evaluator::{
brillig::BrilligOptions,
ssa::{
self,
ir::function::function_values_iter,
primary_passes,
ssa_gen::{self, Ssa},
},
};

fn seed_from_env() -> Option<u64> {
let Ok(seed) = std::env::var("NOIR_ARBTEST_SEED") else { return None };
let seed = u64::from_str_radix(seed.trim_start_matches("0x"), 16)
.unwrap_or_else(|e| panic!("failed to parse seed '{seed}': {e}"));
Some(seed)
}

#[test]
fn arb_ssa_roundtrip() {
let maybe_seed = seed_from_env();

let mut prop = arbtest(|u| {
let config = Config::default();
let program = arb_program(u, config)?;

let options = ssa::SsaEvaluatorOptions {
ssa_logging: ssa::SsaLogging::None,
brillig_options: BrilligOptions::default(),
print_codegen_timings: false,
expression_width: ExpressionWidth::default(),
emit_ssa: None,
skip_underconstrained_check: true,
skip_brillig_constraints_check: true,
enable_brillig_constraints_check_lookback: false,
inliner_aggressiveness: 0,
max_bytecode_increase_percent: None,
skip_passes: Default::default(),
};
let pipeline = primary_passes(&options);
let last_pass = u.choose_index(pipeline.len())?;
let passes = &pipeline[0..last_pass];

// Print the AST if something goes wrong, then panic.
let print_ast_and_panic = |msg: &str| -> ! {
eprintln!("{}", DisplayAstAsNoir(&program));
panic!("{msg}")
};

// If we have a seed to debug and we know it's going to crash, print the AST.
if maybe_seed.is_some() {
eprintln!("{}", DisplayAstAsNoir(&program));
}

// Generate the initial SSA;
let ssa = ssa_gen::generate_ssa(program.clone()).unwrap_or_else(|e| {
print_ast_and_panic(&format!("Failed to generate initial SSA: {e}"))
});

let mut ssa1 = passes.iter().fold(ssa, |ssa, pass| {
pass.run(ssa).unwrap_or_else(|e| {
print_ast_and_panic(&format!("Failed to run pass {}: {e}", pass.msg()))
})
});

// Normalize before printing so IDs don't change.
ssa1.normalize_ids();

// Print to str and parse back.
let ssa2 = Ssa::from_str(&ssa1.to_string()).unwrap_or_else(|e| {
let msg = passes.last().map(|p| p.msg()).unwrap_or("Initial SSA");
print_ast_and_panic(&format!(
"Could not parse SSA after step {last_pass} ({msg}): \n{e:?}"
))
});

// Not everything is populated by the parser, and unfortunately serializing to JSON doesn't work either.
Comment thread
aakoshh marked this conversation as resolved.
for (func_id, func1) in ssa1.functions {
let func2 = &ssa2.functions[&func_id];
let values1 = function_values_iter(&func1).collect::<Vec<_>>();
let values2 = function_values_iter(func2).collect::<Vec<_>>();
similar_asserts::assert_eq!(values1, values2);
}

Ok(())
})
.budget(Duration::from_secs(10))
.size_min(1 << 12)
.size_max(1 << 20);

if let Some(seed) = maybe_seed {
prop = prop.seed(seed);
}

prop.run();
}
Loading