diff --git a/acvm-repo/brillig_vm/src/memory.rs b/acvm-repo/brillig_vm/src/memory.rs index 4092cd06ae0..6c6b5968cd7 100644 --- a/acvm-repo/brillig_vm/src/memory.rs +++ b/acvm-repo/brillig_vm/src/memory.rs @@ -294,6 +294,12 @@ impl Memory { } pub fn read_slice(&self, addr: MemoryAddress, len: usize) -> &[MemoryValue] { + // Allows to read a slice of uninitialized memory if the length is zero. + // Ideally we'd be able to read uninitialized memory in general (as read does) + // but that's not possible if we want to return a slice instead of owned data. + if len == 0 { + return &[]; + } &self.inner[addr.to_usize()..(addr.to_usize() + len)] } diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index d38601bfc1b..80a63f223e7 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -58,8 +58,9 @@ pub(crate) fn optimize_into_acir( let ssa = SsaBuilder::new(program, print_passes, force_brillig_output, print_timings)? .run_pass(Ssa::defunctionalize, "After Defunctionalization:") .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") - .run_pass(Ssa::inline_functions, "After Inlining:") + .run_pass(Ssa::separate_runtime, "After Runtime Separation:") .run_pass(Ssa::resolve_is_unconstrained, "After Resolving IsUnconstrained:") + .run_pass(Ssa::inline_functions, "After Inlining:") // Run mem2reg with the CFG separated into blocks .run_pass(Ssa::mem2reg, "After Mem2Reg:") .run_pass(Ssa::as_slice_optimization, "After `as_slice` optimization") diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 545827df1c5..f763ae52d50 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -22,7 +22,7 @@ use noirc_errors::Location; /// its blocks, instructions, and values. This struct is largely responsible for /// owning most data in a function and handing out Ids to this data that can be /// shared without worrying about ownership. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub(crate) struct DataFlowGraph { /// All of the instructions in a function instructions: DenseMap, diff --git a/compiler/noirc_evaluator/src/ssa/ir/function.rs b/compiler/noirc_evaluator/src/ssa/ir/function.rs index a49e02b0380..c44824b464b 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/function.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/function.rs @@ -64,6 +64,13 @@ impl Function { Self { name, id, entry_block, dfg, runtime: RuntimeType::Acir(InlineType::default()) } } + /// Creates a new function as a clone of the one passed in with the passed in id. + pub(crate) fn clone_with_id(id: FunctionId, another: &Function) -> Self { + let dfg = another.dfg.clone(); + let entry_block = another.entry_block; + Self { name: another.name.clone(), id, entry_block, dfg, runtime: another.runtime } + } + /// The name of the function. /// Used exclusively for debugging purposes. pub(crate) fn name(&self) -> &str { diff --git a/compiler/noirc_evaluator/src/ssa/ir/map.rs b/compiler/noirc_evaluator/src/ssa/ir/map.rs index b6055973f1c..3c3feabc390 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/map.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/map.rs @@ -115,7 +115,7 @@ impl std::fmt::Display for Id { /// access to indices is provided. Since IDs must be stable and correspond /// to indices in the internal Vec, operations that would change element /// ordering like pop, remove, swap_remove, etc, are not possible. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct DenseMap { storage: Vec, } diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 1293671da50..e2a7f51d0a0 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -27,7 +27,7 @@ const RECURSION_LIMIT: u32 = 1000; impl Ssa { /// Inline all functions within the IR. /// - /// In the case of recursive functions, this will attempt + /// In the case of recursive Acir functions, this will attempt /// to recursively inline until the RECURSION_LIMIT is reached. /// /// Functions are recursively inlined into main until either we finish @@ -41,6 +41,8 @@ impl Ssa { /// There are some attributes that allow inlining a function at a different step of codegen. /// Currently this is just `InlineType::NoPredicates` for which we have a flag indicating /// whether treating that inline functions. The default is to treat these functions as entry points. + /// + /// This step should run after runtime separation, since it relies on the runtime of the called functions being final. #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn inline_functions(self) -> Ssa { Self::inline_functions_inner(self, true) @@ -52,12 +54,17 @@ impl Ssa { } fn inline_functions_inner(mut self, no_predicates_is_entry_point: bool) -> Ssa { + let recursive_functions = find_all_recursive_functions(&self); self.functions = btree_map( - get_entry_point_functions(&self, no_predicates_is_entry_point), + get_functions_to_inline_into(&self, no_predicates_is_entry_point), |entry_point| { - let new_function = - InlineContext::new(&self, entry_point, no_predicates_is_entry_point) - .inline_all(&self); + let new_function = InlineContext::new( + &self, + entry_point, + no_predicates_is_entry_point, + recursive_functions.clone(), + ) + .inline_all(&self); (entry_point, new_function) }, ); @@ -80,6 +87,8 @@ struct InlineContext { entry_point: FunctionId, no_predicates_is_entry_point: bool, + // We keep track of the recursive functions in the SSA to avoid inlining them in a brillig context. + recursive_functions: BTreeSet, } /// The per-function inlining context contains information that is only valid for one function. @@ -113,28 +122,101 @@ struct PerFunctionContext<'function> { inlining_entry: bool, } -/// The entry point functions are each function we should inline into - and each function that -/// should be left in the final program. -/// This is the `main` function, any Acir functions with a [fold inline type][InlineType::Fold], -/// and any brillig functions used. -fn get_entry_point_functions( +/// Utility function to find out the direct calls of a function. +fn called_functions(func: &Function) -> BTreeSet { + let mut called_function_ids = BTreeSet::default(); + for block_id in func.reachable_blocks() { + for instruction_id in func.dfg[block_id].instructions() { + let Instruction::Call { func: called_value_id, .. } = &func.dfg[*instruction_id] else { + continue; + }; + + if let Value::Function(function_id) = func.dfg[*called_value_id] { + called_function_ids.insert(function_id); + } + } + } + + called_function_ids +} + +// Recursively explore the SSA to find the functions that end up calling themselves +fn find_recursive_functions( + ssa: &Ssa, + current_function: FunctionId, + mut explored_functions: im::HashSet, + recursive_functions: &mut BTreeSet, +) { + if explored_functions.contains(¤t_function) { + recursive_functions.insert(current_function); + return; + } + + let called_functions = called_functions(&ssa.functions[¤t_function]); + + explored_functions.insert(current_function); + + for called_function in called_functions { + find_recursive_functions( + ssa, + called_function, + explored_functions.clone(), + recursive_functions, + ); + } +} + +fn find_all_recursive_functions(ssa: &Ssa) -> BTreeSet { + let mut recursive_functions = BTreeSet::default(); + find_recursive_functions(ssa, ssa.main_id, im::HashSet::default(), &mut recursive_functions); + recursive_functions +} + +/// The functions we should inline into (and that should be left in the final program) are: +/// - main +/// - Any Brillig function called from Acir +/// - Any Brillig recursive function (Acir recursive functions will be inlined into the main function) +/// - Any Acir functions with a [fold inline type][InlineType::Fold], +fn get_functions_to_inline_into( ssa: &Ssa, no_predicates_is_entry_point: bool, ) -> BTreeSet { - let functions = ssa.functions.iter(); - let mut entry_points = functions - .filter(|(_, function)| { - // If we have not already finished the flattening pass, functions marked - // to not have predicates should be marked as entry points. - let no_predicates_is_entry_point = - no_predicates_is_entry_point && function.is_no_predicates(); - function.runtime().is_entry_point() || no_predicates_is_entry_point + let mut brillig_entry_points = BTreeSet::default(); + let mut acir_entry_points = BTreeSet::default(); + + for (func_id, function) in ssa.functions.iter() { + if function.runtime() == RuntimeType::Brillig { + continue; + } + + // If we have not already finished the flattening pass, functions marked + // to not have predicates should be marked as entry points. + let no_predicates_is_entry_point = + no_predicates_is_entry_point && function.is_no_predicates(); + if function.runtime().is_entry_point() || no_predicates_is_entry_point { + acir_entry_points.insert(*func_id); + } + + for called_function_id in called_functions(function) { + if ssa.functions[&called_function_id].runtime() == RuntimeType::Brillig { + brillig_entry_points.insert(called_function_id); + } + } + } + + let brillig_recursive_functions: BTreeSet<_> = find_all_recursive_functions(ssa) + .into_iter() + .filter(|recursive_function_id| { + let function = &ssa.functions[&recursive_function_id]; + function.runtime() == RuntimeType::Brillig }) - .map(|(id, _)| *id) - .collect::>(); + .collect(); - entry_points.insert(ssa.main_id); - entry_points + std::iter::once(ssa.main_id) + .chain(acir_entry_points) + .chain(brillig_entry_points) + .chain(brillig_recursive_functions) + .collect() } impl InlineContext { @@ -147,6 +229,7 @@ impl InlineContext { ssa: &Ssa, entry_point: FunctionId, no_predicates_is_entry_point: bool, + recursive_functions: BTreeSet, ) -> InlineContext { let source = &ssa.functions[&entry_point]; let mut builder = FunctionBuilder::new(source.name().to_owned(), entry_point); @@ -157,6 +240,7 @@ impl InlineContext { entry_point, call_stack: CallStack::new(), no_predicates_is_entry_point, + recursive_functions, } } @@ -391,18 +475,10 @@ impl<'function> PerFunctionContext<'function> { match &self.source_function.dfg[*id] { Instruction::Call { func, arguments } => match self.get_function(*func) { Some(func_id) => { - let function = &ssa.functions[&func_id]; - // If we have not already finished the flattening pass, functions marked - // to not have predicates should be marked as entry points unless we are inlining into brillig. - let entry_point = &ssa.functions[&self.context.entry_point]; - let no_predicates_is_entry_point = - self.context.no_predicates_is_entry_point - && function.is_no_predicates() - && !matches!(entry_point.runtime(), RuntimeType::Brillig); - if function.runtime().is_entry_point() || no_predicates_is_entry_point { - self.push_instruction(*id); - } else { + if self.should_inline_call(ssa, func_id) { self.inline_function(ssa, *id, func_id, arguments); + } else { + self.push_instruction(*id); } } None => self.push_instruction(*id), @@ -412,6 +488,24 @@ impl<'function> PerFunctionContext<'function> { } } + fn should_inline_call(&self, ssa: &Ssa, called_func_id: FunctionId) -> bool { + let function = &ssa.functions[&called_func_id]; + + if let RuntimeType::Acir(inline_type) = function.runtime() { + // If the called function is acir, we inline if it's not an entry point + + // If we have not already finished the flattening pass, functions marked + // to not have predicates should be marked as entry points. + let no_predicates_is_entry_point = + self.context.no_predicates_is_entry_point && function.is_no_predicates(); + !inline_type.is_entry_point() && !no_predicates_is_entry_point + } else { + // If the called function is brillig, we inline only if it's into brillig and the function is not recursive + ssa.functions[&self.context.entry_point].runtime() == RuntimeType::Brillig + && !self.context.recursive_functions.contains(&called_func_id) + } + } + /// Inline a function call and remember the inlined return values in the values map fn inline_function( &mut self, diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index f6c3f022bfc..4e5fa262696 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -18,5 +18,6 @@ mod remove_bit_shifts; mod remove_enable_side_effects; mod remove_if_else; mod resolve_is_unconstrained; +mod runtime_separation; mod simplify_cfg; mod unrolling; diff --git a/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs b/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs new file mode 100644 index 00000000000..c0c9c0a1372 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs @@ -0,0 +1,348 @@ +use std::collections::BTreeSet; + +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::ssa::{ + ir::{ + function::{Function, FunctionId, RuntimeType}, + instruction::Instruction, + value::{Value, ValueId}, + }, + ssa_gen::Ssa, +}; + +impl Ssa { + /// This optimization step separates the runtime of the functions in the SSA. + /// After this step, all functions with runtime `Acir` will be converted to Acir and + /// the functions with runtime `Brillig` will be converted to Brillig. + /// It does so by cloning all ACIR functions called from a Brillig context + /// and changing the runtime of the cloned functions to Brillig. + /// This pass needs to run after functions as values have been resolved (defunctionalization). + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn separate_runtime(mut self) -> Self { + RuntimeSeparatorContext::separate_runtime(&mut self); + self + } +} + +#[derive(Debug, Default)] +struct RuntimeSeparatorContext { + // Original functions to clone to brillig + acir_functions_called_from_brillig: BTreeSet, + // Tracks the original => cloned version + mapped_functions: HashMap, +} + +impl RuntimeSeparatorContext { + pub(crate) fn separate_runtime(ssa: &mut Ssa) { + let mut runtime_separator = RuntimeSeparatorContext::default(); + + // We first collect all the acir functions called from a brillig context by exploring the SSA recursively + let mut processed_functions = HashSet::default(); + runtime_separator.collect_acir_functions_called_from_brillig( + ssa, + ssa.main_id, + false, + &mut processed_functions, + ); + + // Now we clone the relevant acir functions and change their runtime to brillig + runtime_separator.convert_acir_functions_called_from_brillig_to_brillig(ssa); + + // Now we update any calls within a brillig context to the mapped functions + runtime_separator.replace_calls_to_mapped_functions(ssa); + + // Some functions might be unreachable now (for example an acir function only called from brillig) + prune_unreachable_functions(ssa); + } + + fn collect_acir_functions_called_from_brillig( + &mut self, + ssa: &Ssa, + current_func_id: FunctionId, + mut within_brillig: bool, + processed_functions: &mut HashSet<(/* within_brillig */ bool, FunctionId)>, + ) { + // Processed functions needs the within brillig flag, since it is possible to call the same function from both brillig and acir + if processed_functions.contains(&(within_brillig, current_func_id)) { + return; + } + processed_functions.insert((within_brillig, current_func_id)); + + let func = &ssa.functions[¤t_func_id]; + if func.runtime() == RuntimeType::Brillig { + within_brillig = true; + } + + let called_functions = called_functions(func); + + if within_brillig { + for called_func_id in called_functions.iter() { + let called_func = &ssa.functions[&called_func_id]; + if matches!(called_func.runtime(), RuntimeType::Acir(_)) { + self.acir_functions_called_from_brillig.insert(*called_func_id); + } + } + } + + for called_func_id in called_functions.into_iter() { + self.collect_acir_functions_called_from_brillig( + ssa, + called_func_id, + within_brillig, + processed_functions, + ); + } + } + + fn convert_acir_functions_called_from_brillig_to_brillig(&mut self, ssa: &mut Ssa) { + for acir_func_id in self.acir_functions_called_from_brillig.iter() { + let cloned_id = ssa.clone_fn(*acir_func_id); + let new_func = + ssa.functions.get_mut(&cloned_id).expect("Cloned function should exist in SSA"); + new_func.set_runtime(RuntimeType::Brillig); + self.mapped_functions.insert(*acir_func_id, cloned_id); + } + } + + fn replace_calls_to_mapped_functions(&self, ssa: &mut Ssa) { + for (_function_id, func) in ssa.functions.iter_mut() { + if func.runtime() == RuntimeType::Brillig { + for called_func_value_id in called_functions_values(func).iter() { + let Value::Function(called_func_id) = &func.dfg[*called_func_value_id] else { + unreachable!("Value should be a function") + }; + if let Some(mapped_func_id) = self.mapped_functions.get(called_func_id) { + let mapped_value_id = func.dfg.import_function(*mapped_func_id); + func.dfg.set_value_from_id(*called_func_value_id, mapped_value_id); + } + } + } + } + } +} + +// We only consider direct calls to functions since functions as values should have been resolved +fn called_functions_values(func: &Function) -> BTreeSet { + let mut called_function_ids = BTreeSet::default(); + for block_id in func.reachable_blocks() { + for instruction_id in func.dfg[block_id].instructions() { + let Instruction::Call { func: called_value_id, .. } = &func.dfg[*instruction_id] else { + continue; + }; + + if let Value::Function(_) = func.dfg[*called_value_id] { + called_function_ids.insert(*called_value_id); + } + } + } + + called_function_ids +} + +fn called_functions(func: &Function) -> BTreeSet { + called_functions_values(func) + .into_iter() + .map(|value_id| { + let Value::Function(func_id) = func.dfg[value_id] else { + unreachable!("Value should be a function") + }; + func_id + }) + .collect() +} + +fn collect_reachable_functions( + ssa: &Ssa, + current_func_id: FunctionId, + reachable_functions: &mut HashSet, +) { + if reachable_functions.contains(¤t_func_id) { + return; + } + reachable_functions.insert(current_func_id); + + let func = &ssa.functions[¤t_func_id]; + let called_functions = called_functions(func); + + for called_func_id in called_functions.iter() { + collect_reachable_functions(ssa, *called_func_id, reachable_functions); + } +} + +fn prune_unreachable_functions(ssa: &mut Ssa) { + let mut reachable_functions = HashSet::default(); + collect_reachable_functions(ssa, ssa.main_id, &mut reachable_functions); + + ssa.functions.retain(|id, _value| reachable_functions.contains(id)); +} + +#[cfg(test)] +mod test { + use std::collections::BTreeSet; + + use noirc_frontend::monomorphization::ast::InlineType; + + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{ + function::{Function, FunctionId, RuntimeType}, + map::Id, + types::Type, + }, + opt::runtime_separation::called_functions, + ssa_gen::Ssa, + }; + + #[test] + fn basic_runtime_separation() { + // brillig fn foo { + // b0(): + // v0 = call bar() + // return v0 + // } + // acir fn bar { + // b0(): + // return 72 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("foo".into(), foo_id); + builder.current_function.set_runtime(RuntimeType::Brillig); + + let bar_id = Id::test_new(1); + let bar = builder.import_function(bar_id); + let results = builder.insert_call(bar, Vec::new(), vec![Type::field()]).to_vec(); + builder.terminate_with_return(results); + + builder.new_function("bar".into(), bar_id, InlineType::default()); + let expected_return = 72u128; + let seventy_two = builder.field_constant(expected_return); + builder.terminate_with_return(vec![seventy_two]); + + let ssa = builder.finish(); + assert_eq!(ssa.functions.len(), 2); + + // Expected result + // brillig fn foo { + // b0(): + // v0 = call bar() + // return v0 + // } + // brillig fn bar { + // b0(): + // return 72 + // } + let separated = ssa.separate_runtime(); + + // The original bar function must have been pruned + assert_eq!(separated.functions.len(), 2); + + // All functions should be brillig now + for func in separated.functions.values() { + assert_eq!(func.runtime(), RuntimeType::Brillig); + } + } + + fn find_func_by_name<'ssa>( + ssa: &'ssa Ssa, + funcs: &BTreeSet, + name: &str, + ) -> &'ssa Function { + funcs + .iter() + .find_map(|id| { + let func = ssa.functions.get(id).unwrap(); + if func.name() == name { + Some(func) + } else { + None + } + }) + .unwrap() + } + + #[test] + fn same_function_shared_acir_brillig() { + // acir fn foo { + // b0(): + // v0 = call bar() + // v1 = call baz() + // return v0, v1 + // } + // brillig fn bar { + // b0(): + // v0 = call baz() + // return v0 + // } + // acir fn baz { + // b0(): + // return 72 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("foo".into(), foo_id); + + let bar_id = Id::test_new(1); + let baz_id = Id::test_new(2); + let bar = builder.import_function(bar_id); + let baz = builder.import_function(baz_id); + let v0 = builder.insert_call(bar, Vec::new(), vec![Type::field()]).to_vec(); + let v1 = builder.insert_call(baz, Vec::new(), vec![Type::field()]).to_vec(); + builder.terminate_with_return(vec![v0[0], v1[0]]); + + builder.new_brillig_function("bar".into(), bar_id); + let baz = builder.import_function(baz_id); + let v0 = builder.insert_call(baz, Vec::new(), vec![Type::field()]).to_vec(); + builder.terminate_with_return(v0); + + builder.new_function("baz".into(), baz_id, InlineType::default()); + let expected_return = 72u128; + let seventy_two = builder.field_constant(expected_return); + builder.terminate_with_return(vec![seventy_two]); + + let ssa = builder.finish(); + assert_eq!(ssa.functions.len(), 3); + + // Expected result + // acir fn foo { + // b0(): + // v0 = call bar() + // v1 = call baz() <- baz_acir + // return v0, v1 + // } + // brillig fn bar { + // b0(): + // v0 = call baz() <- baz_brillig + // return v0 + // } + // acir fn baz { + // b0(): + // return 72 + // } + // brillig fn baz { + // b0(): + // return 72 + // } + let separated = ssa.separate_runtime(); + + // The original baz function must have been duplicated + assert_eq!(separated.functions.len(), 4); + + let main_function = separated.functions.get(&separated.main_id).unwrap(); + assert_eq!(main_function.runtime(), RuntimeType::Acir(InlineType::Inline)); + + let main_calls = called_functions(main_function); + assert_eq!(main_calls.len(), 2); + + let bar = find_func_by_name(&separated, &main_calls, "bar"); + let baz_acir = find_func_by_name(&separated, &main_calls, "baz"); + + assert_eq!(baz_acir.runtime(), RuntimeType::Acir(InlineType::Inline)); + assert_eq!(bar.runtime(), RuntimeType::Brillig); + + let bar_calls = called_functions(bar); + assert_eq!(bar_calls.len(), 1); + + let baz_brillig = find_func_by_name(&separated, &bar_calls, "baz"); + assert_eq!(baz_brillig.runtime(), RuntimeType::Brillig); + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 21178c55c73..7a77aa76101 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -80,6 +80,14 @@ impl Ssa { self.functions.insert(new_id, function); new_id } + + /// Clones an already existing function with a fresh id + pub(crate) fn clone_fn(&mut self, existing_function_id: FunctionId) -> FunctionId { + let new_id = self.next_id.next(); + let function = Function::clone_with_id(new_id, &self.functions[&existing_function_id]); + self.functions.insert(new_id, function); + new_id + } } impl Display for Ssa { diff --git a/test_programs/execution_success/acir_inside_brillig_recursion/Nargo.toml b/test_programs/execution_success/acir_inside_brillig_recursion/Nargo.toml new file mode 100644 index 00000000000..462532bb484 --- /dev/null +++ b/test_programs/execution_success/acir_inside_brillig_recursion/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "acir_inside_brillig_recursion" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml b/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml @@ -0,0 +1 @@ + diff --git a/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr b/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr new file mode 100644 index 00000000000..92f8524a771 --- /dev/null +++ b/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr @@ -0,0 +1,15 @@ +fn main() { + assert_eq(fibonacci(3), fibonacci_hint(3)); +} + +unconstrained fn fibonacci_hint(x: u32) -> u32 { + fibonacci(x) +} + +fn fibonacci(x: u32) -> u32 { + if x <= 1 { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +} diff --git a/tooling/debugger/ignored-tests.txt b/tooling/debugger/ignored-tests.txt index a6d3c9a3a94..a9193896589 100644 --- a/tooling/debugger/ignored-tests.txt +++ b/tooling/debugger/ignored-tests.txt @@ -1,28 +1,17 @@ -array_dynamic_blackbox_input bigint -bit_shifts_comptime brillig_references brillig_to_bytes_integration debug_logs -double_verify_nested_proof -double_verify_proof -double_verify_proof_recursive -modulus -references -scalar_mul -signed_comparison -to_bytes_integration +fold_after_inlined_calls fold_basic fold_basic_nested_call fold_call_witness_condition -fold_after_inlined_calls -fold_numeric_generic_poseidon -no_predicates_basic -no_predicates_numeric_generic_poseidon -regression_4709 +fold_complex_outputs fold_distinct_return fold_fibonacci -fold_complex_outputs -slice_init_with_complex_type -hashmap -is_unconstrained \ No newline at end of file +fold_numeric_generic_poseidon +is_unconstrained +modulus +references +regression_4709 +to_bytes_integration \ No newline at end of file