diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 1e8749e88c7..57f5f805129 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -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(), + ) } } diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index bf625abc29c..1b9473f051b 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -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, + /// 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, @@ -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(()) } @@ -64,15 +76,16 @@ impl AstPrinter { pub fn print_function( &mut self, function: &Function, - return_visibility: Option, 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(), @@ -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, "}}")?; diff --git a/tooling/ast_fuzzer/examples/sample_comptime.rs b/tooling/ast_fuzzer/examples/sample_comptime.rs new file mode 100644 index 00000000000..70381ff910d --- /dev/null +++ b/tooling/ast_fuzzer/examples/sample_comptime.rs @@ -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)); +} diff --git a/tooling/ast_fuzzer/src/lib.rs b/tooling/ast_fuzzer/src/lib.rs index 5ef74e871e8..fdb4cfaa1ba 100644 --- a/tooling/ast_fuzzer/src/lib.rs +++ b/tooling/ast_fuzzer/src/lib.rs @@ -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. @@ -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 { @@ -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, @@ -88,6 +93,7 @@ impl Default for Config { expr_freqs, stmt_freqs_acir, stmt_freqs_brillig, + force_brillig: false, } } } diff --git a/tooling/ast_fuzzer/src/program/func.rs b/tooling/ast_fuzzer/src/program/func.rs index c55ba912489..f2442f16fac 100644 --- a/tooling/ast_fuzzer/src/program/func.rs +++ b/tooling/ast_fuzzer/src/program/func.rs @@ -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 { + self.gen_lit_call(u, callee_id) + } + /// Get the function declaration. fn decl(&self) -> &FunctionDeclaration { self.ctx.function_decl(self.id) @@ -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 { + let callee = self.ctx.function_decl(callee_id).clone(); + let param_types = callee.params.iter().map(|p| p.3.clone()).collect::>(); + + let mut args = Vec::new(); + for typ in ¶m_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 { // Declare break index variable visible in the loop body. Do not include it diff --git a/tooling/ast_fuzzer/src/program/mod.rs b/tooling/ast_fuzzer/src/program/mod.rs index 6f82a17d150..1914f183f65 100644 --- a/tooling/ast_fuzzer/src/program/mod.rs +++ b/tooling/ast_fuzzer/src/program/mod.rs @@ -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}, }; @@ -38,6 +38,39 @@ pub fn arb_program(u: &mut Unstructured, config: Config) -> arbitrary::Result arbitrary::Result { + 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 { @@ -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") @@ -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); @@ -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) @@ -189,23 +229,40 @@ impl Context { fn gen_functions(&mut self, u: &mut Unstructured) -> arbitrary::Result<()> { let ids = self.function_declarations.keys().cloned().collect::>(); 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, + ) -> 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) @@ -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(()) + } +}