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
6 changes: 5 additions & 1 deletion compiler/noirc_frontend/src/monomorphization/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,11 @@ impl std::fmt::Display for Program {

impl std::fmt::Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
super::printer::AstPrinter::default().print_function(self, None, f)
super::printer::AstPrinter::default().print_function(
self,
f,
super::printer::FunctionPrintOptions::default(),
)
}
}

Expand Down
34 changes: 29 additions & 5 deletions compiler/noirc_frontend/src/monomorphization/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ use super::ast::{
use iter_extended::vecmap;
use std::fmt::{Display, Formatter};

#[derive(Default)]
pub struct FunctionPrintOptions {
pub return_visibility: Option<Visibility>,
/// Wraps function body in a `comptime` block. Used to make
/// comptime function callers in fuzzing.
pub comptime_wrap_body: bool,
/// Marks function as comptime. Used in fuzzing.
pub comptime: bool,
}

#[derive(Debug)]
pub struct AstPrinter {
indent_level: u32,
Expand Down Expand Up @@ -43,8 +53,10 @@ impl AstPrinter {
self.print_global(id, global, f)?;
}
for function in &program.functions {
let vis = (function.id == Program::main_id()).then_some(program.return_visibility);
self.print_function(function, vis, f)?;
let return_visibility =
(function.id == Program::main_id()).then_some(program.return_visibility);
let fpo = FunctionPrintOptions { return_visibility, ..Default::default() };
self.print_function(function, f, fpo)?;
}
Ok(())
}
Expand All @@ -64,15 +76,16 @@ impl AstPrinter {
pub fn print_function(
&mut self,
function: &Function,
return_visibility: Option<Visibility>,
f: &mut Formatter,
options: FunctionPrintOptions,
) -> std::fmt::Result {
let params = vecmap(&function.parameters, |(id, mutable, name, typ)| {
format!("{}{}: {}", if *mutable { "mut " } else { "" }, self.fmt_local(name, *id), typ)
})
.join(", ");

let vis = return_visibility
let vis = options
.return_visibility
.map(|vis| match vis {
Visibility::Private => "".to_string(),
Visibility::Public => "pub ".to_string(),
Expand All @@ -82,14 +95,25 @@ impl AstPrinter {
.unwrap_or_default();

let unconstrained = if function.unconstrained { "unconstrained " } else { "" };
let comptime = if options.comptime { "comptime " } else { "" };
let name = self.fmt_func(&function.name, function.id);
let return_type = &function.return_type;

write!(f, "{unconstrained}fn {name}({params}) -> {vis}{return_type} {{",)?;
write!(f, "{comptime}{unconstrained}fn {name}({params}) -> {vis}{return_type} {{",)?;
self.in_unconstrained = function.unconstrained;
if options.comptime_wrap_body {
self.indent_level += 1;
self.next_line(f)?;
write!(f, "comptime {{")?;
}
self.indent_level += 1;
self.print_expr_expect_block(&function.body, f)?;
self.indent_level -= 1;
if options.comptime_wrap_body {
self.next_line(f)?;
self.indent_level -= 1;
write!(f, "}}")?;
}
self.in_unconstrained = false;
self.next_line(f)?;
writeln!(f, "}}")?;
Expand Down
23 changes: 23 additions & 0 deletions tooling/ast_fuzzer/examples/sample_comptime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Print a random comptime AST
//!
//! ```shell
//! cargo run -p noir_ast_fuzzer --example sample_comptime
//! ```
use arbitrary::Unstructured;
use noir_ast_fuzzer::{Config, DisplayAstAsNoirComptime, arb_program_comptime};
use rand::RngCore;

fn main() {
let data = {
let mut rng = rand::thread_rng();
let mut data = [0u8; 1024 * 1024];
rng.fill_bytes(&mut data);
data
};
let mut u = Unstructured::new(&data);

let config = Config { max_globals: 0, ..Default::default() };

let program = arb_program_comptime(&mut u, config).expect("arb_program");
println!("{}", DisplayAstAsNoirComptime(&program));
}
8 changes: 7 additions & 1 deletion tooling/ast_fuzzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ pub use abi::program_abi;
pub use input::arb_inputs;
use program::freq::Freqs;
pub use program::visitor::{visit_expr, visit_expr_mut};
pub use program::{DisplayAstAsNoir, arb_program};
pub use program::{DisplayAstAsNoir, DisplayAstAsNoirComptime, arb_program, arb_program_comptime};

/// AST generation configuration.
#[derive(Debug, Clone)]
pub struct Config {
/// Maximum number of global definitions.
pub max_globals: usize,
/// Minimum number of functions (other than main) to generate.
pub min_functions: usize,
/// Maximum number of functions (other than main) to generate.
pub max_functions: usize,
/// Maximum number of arguments a function can have.
Expand All @@ -40,6 +42,8 @@ pub struct Config {
pub stmt_freqs_acir: Freqs,
/// Frequency of statements in Brillig functions.
pub stmt_freqs_brillig: Freqs,
/// Whether to force all functions to be unconstrained.
pub force_brillig: bool,
}

impl Default for Config {
Expand Down Expand Up @@ -75,6 +79,7 @@ impl Default for Config {
]);
Self {
max_globals: 3,
min_functions: 0,
max_functions: 5,
max_function_args: 3,
max_function_size: 25,
Expand All @@ -88,6 +93,7 @@ impl Default for Config {
expr_freqs,
stmt_freqs_acir,
stmt_freqs_brillig,
force_brillig: false,
}
}
}
47 changes: 47 additions & 0 deletions tooling/ast_fuzzer/src/program/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ impl<'a> FunctionContext<'a> {
Ok(body)
}

/// Generate the function body, wrapping a function call with literal arguments.
/// This is used to test comptime functions, which can only take those.
pub fn gen_body_with_lit_call(
mut self,
u: &mut Unstructured,
callee_id: FuncId,
) -> arbitrary::Result<Expression> {
self.gen_lit_call(u, callee_id)
}

/// Get the function declaration.
fn decl(&self) -> &FunctionDeclaration {
self.ctx.function_decl(self.id)
Expand Down Expand Up @@ -946,6 +956,43 @@ impl<'a> FunctionContext<'a> {
self.gen_expr_from_source(u, call_expr, &callee.return_type, typ, self.max_depth())
}

/// Generate a call to a specific function, with arbitrary literals
/// for arguments (useful for generating comptime wrapper calls)
fn gen_lit_call(
&mut self,
u: &mut Unstructured,
callee_id: FuncId,
) -> arbitrary::Result<Expression> {
let callee = self.ctx.function_decl(callee_id).clone();
let param_types = callee.params.iter().map(|p| p.3.clone()).collect::<Vec<_>>();

let mut args = Vec::new();
for typ in &param_types {
args.push(expr::gen_literal(u, typ)?);
}

let call_expr = Expression::Call(Call {
func: Box::new(Expression::Ident(Ident {
location: None,
definition: Definition::Function(callee_id),
mutable: false,
name: callee.name.clone(),
typ: Type::Function(
param_types,
Box::new(callee.return_type.clone()),
Box::new(Type::Unit),
callee.unconstrained,
),
id: self.next_ident_id(),
})),
arguments: args,
return_type: callee.return_type,
location: Location::dummy(),
});

Ok(call_expr)
}

/// Generate a `loop` loop.
fn gen_loop(&mut self, u: &mut Unstructured) -> arbitrary::Result<Expression> {
// Declare break index variable visible in the loop body. Do not include it
Expand Down
112 changes: 96 additions & 16 deletions tooling/ast_fuzzer/src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use noirc_frontend::{
ast::IntegerBitSize,
monomorphization::{
ast::{Expression, FuncId, Function, GlobalId, InlineType, LocalId, Program, Type},
printer::AstPrinter,
printer::{AstPrinter, FunctionPrintOptions},
},
shared::{Signedness, Visibility},
};
Expand Down Expand Up @@ -38,6 +38,39 @@ pub fn arb_program(u: &mut Unstructured, config: Config) -> arbitrary::Result<Pr
Ok(program)
}

/// Generate an arbitrary monomorphized AST to be reversed into a valid comptime
/// Noir, with a single comptime function called from main with literal arguments.
pub fn arb_program_comptime(u: &mut Unstructured, config: Config) -> arbitrary::Result<Program> {
let mut config = config.clone();
// Comptime should use Brillig feature set
config.force_brillig = true;

let mut ctx = Context::new(config);

let decl_inner = ctx.gen_function_decl(u, 1)?;
ctx.set_function_decl(FuncId(1), decl_inner.clone());
ctx.gen_function(u, FuncId(1))?;

// Parameterless main declaration wrapping the inner "main"
// function call
let decl_main = FunctionDeclaration {
name: "main".into(),
params: vec![],
return_type: decl_inner.return_type.clone(),
param_visibilities: vec![],
return_visibility: Visibility::Public,
inline_type: InlineType::default(),
unconstrained: false,
};

ctx.set_function_decl(FuncId(0), decl_main);
ctx.gen_function_with_body(u, FuncId(0), |u, fctx| fctx.gen_body_with_lit_call(u, FuncId(1)))?;
ctx.rewrite_functions(u)?;

let program = ctx.finalize();
Ok(program)
}

/// ID of variables in scope.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum VariableId {
Expand Down Expand Up @@ -80,6 +113,11 @@ impl Context {
self.function_declarations.get(&id).expect("function should exist")
}

/// Get a function declaration.
fn set_function_decl(&mut self, id: FuncId, decl: FunctionDeclaration) {
self.function_declarations.insert(id, decl);
}

/// Get the main function declaration.
fn main_decl(&self) -> &FunctionDeclaration {
self.function_declarations.get(&Program::main_id()).expect("main should exist")
Expand Down Expand Up @@ -111,7 +149,9 @@ impl Context {

/// Generate random function names and signatures.
fn gen_function_decls(&mut self, u: &mut Unstructured) -> arbitrary::Result<()> {
let num_non_main_fns = u.int_in_range(0..=self.config.max_functions)?;
let num_non_main_fns =
u.int_in_range(self.config.min_functions..=self.config.max_functions)?;

for i in 0..(1 + num_non_main_fns) {
let d = self.gen_function_decl(u, i)?;
self.function_declarations.insert(FuncId(i as u32), d);
Expand Down Expand Up @@ -172,7 +212,7 @@ impl Context {
} else {
*u.choose(&[InlineType::Inline, InlineType::InlineAlways])?
},
unconstrained: bool::arbitrary(u)?,
unconstrained: self.config.force_brillig || bool::arbitrary(u)?,
};

Ok(decl)
Expand All @@ -189,23 +229,40 @@ impl Context {
fn gen_functions(&mut self, u: &mut Unstructured) -> arbitrary::Result<()> {
let ids = self.function_declarations.keys().cloned().collect::<Vec<_>>();
for id in ids {
let body = FunctionContext::new(self, id).gen_body(u)?;
let decl = self.function_decl(id);
let func = Function {
id,
name: decl.name.clone(),
parameters: decl.params.clone(),
body,
return_type: decl.return_type.clone(),
unconstrained: decl.unconstrained,
inline_type: decl.inline_type,
func_sig: decl.signature(),
};
self.functions.insert(id, func);
self.gen_function(u, id)?;
}
Ok(())
}

/// Generate random function body.
fn gen_function(&mut self, u: &mut Unstructured, id: FuncId) -> arbitrary::Result<()> {
self.gen_function_with_body(u, id, |u, fctx| fctx.gen_body(u))
}

/// Generate function with a specified body generator.
fn gen_function_with_body(
&mut self,
u: &mut Unstructured,
id: FuncId,
f: impl Fn(&mut Unstructured, FunctionContext) -> arbitrary::Result<Expression>,
) -> arbitrary::Result<()> {
let fctx = FunctionContext::new(self, id);
let body = f(u, fctx)?;
let decl = self.function_decl(id);
let func = Function {
id,
name: decl.name.clone(),
parameters: decl.params.clone(),
body,
return_type: decl.return_type.clone(),
unconstrained: decl.unconstrained,
inline_type: decl.inline_type,
func_sig: decl.signature(),
};
self.functions.insert(id, func);
Ok(())
}

/// As a post-processing step, identify recursive functions and add a call depth parameter to them.
fn rewrite_functions(&mut self, u: &mut Unstructured) -> arbitrary::Result<()> {
rewrite::add_recursion_limit(self, u)
Expand Down Expand Up @@ -341,3 +398,26 @@ impl std::fmt::Display for DisplayAstAsNoir<'_> {
printer.print_program(self.0, f)
}
}

/// Wrapper around `Program` that prints its AST as close to
/// Noir syntax as we can get, making it `comptime`. The AST must
/// be specifically prepared to include a main function consisting
/// of a `comptime` wrapped call to a `comptime` (or `unconstrained`)
/// marked function.
pub struct DisplayAstAsNoirComptime<'a>(pub &'a Program);

impl std::fmt::Display for DisplayAstAsNoirComptime<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut printer = AstPrinter::default();
printer.show_id = false;
for function in &self.0.functions {
let mut fpo = FunctionPrintOptions::default();
if function.id == Program::main_id() {
fpo.comptime_wrap_body = true;
fpo.return_visibility = Some(Visibility::Public);
}
printer.print_function(function, f, fpo)?;
}
Ok(())
}
}
Loading