From 59f8b1ab16050361a07c97630a254486afafc08b Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 4 Feb 2025 17:54:11 +0000 Subject: [PATCH 01/28] add regression test global_var_entry_point_used_in_another_entry and fix in SSA --- .../brillig/brillig_gen/brillig_globals.rs | 68 ++++++++++++++++- .../src/brillig/brillig_ir/entry_point.rs | 1 + compiler/noirc_evaluator/src/ssa.rs | 3 + .../noirc_evaluator/src/ssa/ir/function.rs | 15 ++++ .../src/ssa/opt/brillig_entry_points.rs | 74 +++++++++++++++++++ compiler/noirc_evaluator/src/ssa/opt/mod.rs | 1 + .../Nargo.toml | 6 ++ .../Prover.toml | 2 + .../src/main.nr | 27 +++++++ .../src/consts.nr | 2 +- 10 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs create mode 100644 test_programs/execution_success/global_var_entry_point_used_in_another_entry/Nargo.toml create mode 100644 test_programs/execution_success/global_var_entry_point_used_in_another_entry/Prover.toml create mode 100644 test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 6f5645485a2..c5dedb5b162 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -37,6 +37,70 @@ pub(crate) struct BrilligGlobals { /// Mapping of SSA value ids to their Brillig allocations pub(crate) type SsaToBrilligGlobals = HashMap; +pub(crate) fn get_brillig_entry_points( + functions: &BTreeMap, +) -> HashMap> { + let mut brillig_entry_points = HashMap::default(); + let acir_functions = functions.iter().filter(|(_, func)| func.runtime().is_acir()); + for (_, function) in acir_functions { + for block_id in function.reachable_blocks() { + for instruction_id in function.dfg[block_id].instructions() { + let instruction = &function.dfg[*instruction_id]; + let Instruction::Call { func: func_id, arguments: _ } = instruction else { + continue; + }; + + let func_value = &function.dfg[*func_id]; + let Value::Function(func_id) = func_value else { continue }; + + let called_function = &functions[func_id]; + if called_function.runtime().is_acir() { + continue; + } + + // We have now found a Brillig entry point. + brillig_entry_points.insert(*func_id, HashSet::default()); + build_entry_points_map_recursive( + functions, + *func_id, + called_function, + &mut brillig_entry_points, + im::HashSet::new(), + ); + } + } + } + brillig_entry_points +} + +pub(crate) fn build_entry_points_map_recursive( + functions: &BTreeMap, + entry_point: FunctionId, + called_function: &Function, + brillig_entry_points: &mut HashMap>, + mut explored_functions: im::HashSet, +) { + if explored_functions.insert(called_function.id()).is_some() { + return; + } + + let inner_calls = called_functions_vec(called_function).into_iter().collect::>(); + + for inner_call in inner_calls { + if let Some(inner_calls) = brillig_entry_points.get_mut(&entry_point) { + inner_calls.insert(inner_call); + } + + build_entry_points_map_recursive( + functions, + entry_point, + &functions[&inner_call], + brillig_entry_points, + explored_functions.clone(), + ); + } +} + impl BrilligGlobals { pub(crate) fn new( functions: &BTreeMap, @@ -91,6 +155,9 @@ impl BrilligGlobals { im::HashSet::new(), ); } + // dbg!(brillig_entry_points.clone()); + // let brillig_entry_points_set = get_brillig_entry_points(functions); + // dbg!(brillig_entry_points_set.clone()); Self { used_globals, brillig_entry_points, ..Default::default() } } @@ -198,7 +265,6 @@ impl BrilligGlobals { } else if let Some(globals) = self.entry_point_globals_map.get(&brillig_function_id) { // If there is no mapping from an inner call to an entry point, that means `brillig_function_id` // is itself an entry point and we can fetch the global allocations directly from `self.entry_point_globals_map`. - // vec![globals] globals_allocations.extend(globals); } else { unreachable!( diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 6d4cc814d3e..9e4e6bc159a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -25,6 +25,7 @@ impl BrilligContext { globals_init: bool, globals_memory_size: usize, ) -> BrilligArtifact { + println!("codegen entry point {target_function}"); let mut context = BrilligContext::new(false); context.globals_memory_size = Some(globals_memory_size); diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index c17fc2d0b7a..df33030a010 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -201,6 +201,9 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result Self { + let dfg = another.dfg.clone(); + let entry_block = another.entry_block; + Self { name: another.name.clone(), id: None, entry_block, dfg } + } + /// Takes the signature (function name & runtime) from a function but does not copy the body. pub(crate) fn clone_signature(id: FunctionId, another: &Function) -> Self { let mut new_function = Function::new(another.name.clone(), id); @@ -129,6 +137,13 @@ impl Function { self.id.expect("FunctionId should be initialized") } + /// Set the id of the function. + /// The id should normally be set on creation of a `Function` + /// and this method should be used with caution. + pub(crate) fn set_id(&mut self, id: FunctionId) { + self.id = Some(id); + } + /// Runtime type of the function. pub(crate) fn runtime(&self) -> RuntimeType { self.dfg.runtime() diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs new file mode 100644 index 00000000000..e9f7ce0cd4a --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -0,0 +1,74 @@ +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::{ + brillig::brillig_gen::brillig_globals::get_brillig_entry_points, + ssa::{ + ir::{ + function::{Function, FunctionId}, + instruction::Instruction, + value::Value, + }, + Ssa, + }, +}; + +impl Ssa { + pub(crate) fn duplicate_reused_entry_points(mut self) -> Ssa { + let brillig_entry_points = get_brillig_entry_points(&self.functions); + + let entry_points = brillig_entry_points.keys().copied().collect::>(); + + let mut calls_to_update: HashMap = HashMap::default(); + let mut functions_to_update: HashSet = HashSet::default(); + for (entry_point, inner_calls) in brillig_entry_points { + for inner_call in inner_calls { + if entry_points.contains(&inner_call) { + if calls_to_update.get(&inner_call).is_some() { + functions_to_update.insert(entry_point); + continue; + } + // Must clone the function before `add_fn` as the method borrows `self` mutably + // while `clone_no_id` borrows a function immutably. + let mut cloned_function = Function::clone_no_id(&self.functions[&inner_call]); + + self.add_fn(|id| { + cloned_function.set_id(id); + + calls_to_update.insert(inner_call, id); + functions_to_update.insert(entry_point); + + cloned_function + }); + } + } + } + + for func_id in functions_to_update { + let function = self.functions.get_mut(&func_id).expect("ICE: Function does not exist"); + for block_id in function.reachable_blocks() { + #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here + for instruction_id in function.dfg[block_id].instructions().to_vec() { + let instruction = function.dfg[instruction_id].clone(); + let Instruction::Call { func: func_id, arguments } = instruction else { + continue; + }; + + let func_value = &function.dfg[func_id]; + let Value::Function(func_id) = func_value else { continue }; + + if let Some(new_id) = calls_to_update.get(func_id) { + let new_function_value_id = function.dfg.import_function(*new_id); + function.dfg[instruction_id] = Instruction::Call { + func: new_function_value_id, + arguments: arguments.clone(), + }; + } else { + continue; + } + } + } + } + + self + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 7ec419890c0..73e291b07f3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -7,6 +7,7 @@ mod array_set; mod as_slice_length; mod assert_constant; +mod brillig_entry_points; mod constant_folding; mod defunctionalize; mod die; diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Nargo.toml b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Nargo.toml new file mode 100644 index 00000000000..2327a6e1097 --- /dev/null +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "global_var_entry_point_used_in_another_entry" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Prover.toml b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Prover.toml new file mode 100644 index 00000000000..4c144083f00 --- /dev/null +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/Prover.toml @@ -0,0 +1,2 @@ +x = 0 +y = 1 diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr new file mode 100644 index 00000000000..21a4dc98dc0 --- /dev/null +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr @@ -0,0 +1,27 @@ +global ONE: Field = 1; +global TWO: Field = 2; + +fn main(x: Field, y: pub Field) { + /// Safety: testing context + unsafe { + entry_point_no_global(x, y); + entry_point_inner_func_globals(x, y); + entry_point_one_global(x, y); + } +} + +unconstrained fn entry_point_no_global(x: Field, y: Field) { + assert(x + y != 100); +} + +unconstrained fn entry_point_one_global(x: Field, y: Field) { + let z = TWO + x + y; + assert(z == 3); +} + +unconstrained fn entry_point_inner_func_globals(x: Field, y: Field) { + let z = ONE + x + y; + assert(z == 2); + entry_point_one_global(x, y) +} + diff --git a/test_programs/execution_success/global_var_regression_entry_points/src/consts.nr b/test_programs/execution_success/global_var_regression_entry_points/src/consts.nr index 7ad6a4a54d1..43b580cf781 100644 --- a/test_programs/execution_success/global_var_regression_entry_points/src/consts.nr +++ b/test_programs/execution_success/global_var_regression_entry_points/src/consts.nr @@ -1,4 +1,4 @@ -global EXPONENTIATE: [[Field; 257]; 257] = [ +pub(crate) global EXPONENTIATE: [[Field; 257]; 257] = [ [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, From ccc0c0eb308cc56a2955ec3ab46d8a019f0a49a6 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 4 Feb 2025 17:57:42 +0000 Subject: [PATCH 02/28] delete comments --- .../noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index c5dedb5b162..13235e789ed 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -155,9 +155,6 @@ impl BrilligGlobals { im::HashSet::new(), ); } - // dbg!(brillig_entry_points.clone()); - // let brillig_entry_points_set = get_brillig_entry_points(functions); - // dbg!(brillig_entry_points_set.clone()); Self { used_globals, brillig_entry_points, ..Default::default() } } From d9fd935699a04fb0407fca1e41ae366e35d19f1d Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 4 Feb 2025 18:11:48 +0000 Subject: [PATCH 03/28] delete bad print --- compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 9e4e6bc159a..6d4cc814d3e 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -25,7 +25,6 @@ impl BrilligContext { globals_init: bool, globals_memory_size: usize, ) -> BrilligArtifact { - println!("codegen entry point {target_function}"); let mut context = BrilligContext::new(false); context.globals_memory_size = Some(globals_memory_size); From b5226893d8c113b0160d8c61bce02fea0a601efa Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Feb 2025 09:44:22 +0000 Subject: [PATCH 04/28] handle multiple dups and nested dups, and expand integration test --- .../src/ssa/opt/brillig_entry_points.rs | 52 +++++++++++++------ .../src/main.nr | 19 ++++++- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index e9f7ce0cd4a..5c82b5714f0 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -15,31 +15,51 @@ use crate::{ impl Ssa { pub(crate) fn duplicate_reused_entry_points(mut self) -> Ssa { let brillig_entry_points = get_brillig_entry_points(&self.functions); - let entry_points = brillig_entry_points.keys().copied().collect::>(); let mut calls_to_update: HashMap = HashMap::default(); let mut functions_to_update: HashSet = HashSet::default(); - for (entry_point, inner_calls) in brillig_entry_points { - for inner_call in inner_calls { - if entry_points.contains(&inner_call) { - if calls_to_update.get(&inner_call).is_some() { - functions_to_update.insert(entry_point); + + let mut functions_to_clone_map: HashMap> = + HashMap::default(); + for (entry_point, function) in self.functions.iter() { + for block in function.reachable_blocks() { + for &instruction_id in function.dfg[block].instructions() { + let instruction = &function.dfg[instruction_id]; + let Instruction::Call { func: func_id, .. } = instruction else { continue; - } - // Must clone the function before `add_fn` as the method borrows `self` mutably - // while `clone_no_id` borrows a function immutably. - let mut cloned_function = Function::clone_no_id(&self.functions[&inner_call]); + }; - self.add_fn(|id| { - cloned_function.set_id(id); + let func_value = &function.dfg[*func_id]; + let Value::Function(called_func_id) = func_value else { continue }; - calls_to_update.insert(inner_call, id); - functions_to_update.insert(entry_point); + if function.dfg.runtime().is_brillig() && entry_points.contains(called_func_id) + { + let cloned_function = + Function::clone_no_id(&self.functions[called_func_id]); + functions_to_clone_map + .entry(*entry_point) + .or_default() + .push((cloned_function, *called_func_id)); + } + } + } + } - cloned_function - }); + for (entry_point, functions_to_clone) in functions_to_clone_map { + for (mut cloned_function, old_id) in functions_to_clone { + if calls_to_update.get(&old_id).is_some() { + continue; } + self.add_fn(|id| { + calls_to_update.insert(old_id, id); + + cloned_function.set_id(id); + + functions_to_update.insert(entry_point); + + cloned_function + }); } } diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr index 21a4dc98dc0..e9de353a90b 100644 --- a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr @@ -1,5 +1,6 @@ global ONE: Field = 1; global TWO: Field = 2; +global THREE: Field = 3; fn main(x: Field, y: pub Field) { /// Safety: testing context @@ -7,6 +8,7 @@ fn main(x: Field, y: pub Field) { entry_point_no_global(x, y); entry_point_inner_func_globals(x, y); entry_point_one_global(x, y); + entry_point_one_diff_global(x, y); } } @@ -20,8 +22,23 @@ unconstrained fn entry_point_one_global(x: Field, y: Field) { } unconstrained fn entry_point_inner_func_globals(x: Field, y: Field) { + wrapper(x, y); +} + +// Test that we duplicate Brillig entry points called within +// another entry point's inner calls +unconstrained fn wrapper(x: Field, y: Field) { let z = ONE + x + y; assert(z == 2); - entry_point_one_global(x, y) + entry_point_one_global(x, y); + // Test that we handle repeated entry point calls + // `entry_point_one_diff_global` should be duplicated and the duplicated function + // should use the globals from `entry_point_inner_func_globals` + entry_point_one_diff_global(y, x); +} + +unconstrained fn entry_point_one_diff_global(x: Field, y: Field) { + let z = THREE + x + y; + assert(z == 4); } From d55fe0d042ca4a410ffcf880317c06762d62c6ef Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Feb 2025 09:52:09 +0000 Subject: [PATCH 05/28] nargo fmt --- .../global_var_entry_point_used_in_another_entry/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr index e9de353a90b..13094cfb662 100644 --- a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr @@ -25,7 +25,7 @@ unconstrained fn entry_point_inner_func_globals(x: Field, y: Field) { wrapper(x, y); } -// Test that we duplicate Brillig entry points called within +// Test that we duplicate Brillig entry points called within // another entry point's inner calls unconstrained fn wrapper(x: Field, y: Field) { let z = ONE + x + y; From 8dad19a3af6e502595a66fe42ef6a2a58e3f65f2 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Feb 2025 13:02:27 +0000 Subject: [PATCH 06/28] do not duplicate for recursive funcs --- .../src/brillig/brillig_gen/brillig_globals.rs | 12 ++++++++---- .../src/ssa/opt/brillig_entry_points.rs | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 13235e789ed..4fce7a6a94a 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -249,7 +249,15 @@ impl BrilligGlobals { let entry_points = self.inner_call_to_entry_point.get(&brillig_function_id); let mut globals_allocations = HashMap::default(); + if let Some(globals) = self.entry_point_globals_map.get(&brillig_function_id) { + // Check whether `brillig_function_id` is itself an entry point. + // If so, return the global allocations directly from `self.entry_point_globals_map`. + globals_allocations.extend(globals); + return globals_allocations; + } + if let Some(entry_points) = entry_points { + assert!(self.entry_point_globals_map.get(&brillig_function_id).is_none()); // A Brillig function is used by multiple entry points. Fetch both globals allocations // in case one is used by the internal call. let entry_point_allocations = entry_points @@ -259,10 +267,6 @@ impl BrilligGlobals { for map in entry_point_allocations { globals_allocations.extend(map); } - } else if let Some(globals) = self.entry_point_globals_map.get(&brillig_function_id) { - // If there is no mapping from an inner call to an entry point, that means `brillig_function_id` - // is itself an entry point and we can fetch the global allocations directly from `self.entry_point_globals_map`. - globals_allocations.extend(globals); } else { unreachable!( "ICE: Expected global allocation to be set for function {brillig_function_id}" diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 5c82b5714f0..98d42ef838e 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -33,7 +33,9 @@ impl Ssa { let func_value = &function.dfg[*func_id]; let Value::Function(called_func_id) = func_value else { continue }; - if function.dfg.runtime().is_brillig() && entry_points.contains(called_func_id) + if function.dfg.runtime().is_brillig() + && entry_points.contains(called_func_id) + && *called_func_id != function.id() { let cloned_function = Function::clone_no_id(&self.functions[called_func_id]); From 7eba496c79169f97e45542324e2b4d00135e6e63 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 15:04:00 +0000 Subject: [PATCH 07/28] more regression tests and improved duplication for nested funcs and multiple entry points --- .../brillig/brillig_gen/brillig_globals.rs | 1 + compiler/noirc_evaluator/src/ssa.rs | 3 + .../noirc_evaluator/src/ssa/ir/function.rs | 10 +- .../src/ssa/opt/brillig_entry_points.rs | 130 +- .../Nargo.toml | 6 + .../Prover.toml | 2 + .../src/main.nr | 28 + .../Nargo.toml | 6 + .../Prover.toml | 2 + .../src/main.nr | 55 + .../ssa.txt | 1759 +++++++++++++++++ 11 files changed, 1964 insertions(+), 38 deletions(-) create mode 100644 test_programs/execution_success/global_var_func_with_multiple_entry_points/Nargo.toml create mode 100644 test_programs/execution_success/global_var_func_with_multiple_entry_points/Prover.toml create mode 100644 test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr create mode 100644 test_programs/execution_success/global_var_multiple_entry_points_nested/Nargo.toml create mode 100644 test_programs/execution_success/global_var_multiple_entry_points_nested/Prover.toml create mode 100644 test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr create mode 100644 test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 4fce7a6a94a..ed78117809f 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -258,6 +258,7 @@ impl BrilligGlobals { if let Some(entry_points) = entry_points { assert!(self.entry_point_globals_map.get(&brillig_function_id).is_none()); + assert_eq!(entry_points.len(), 1, "{brillig_function_id} has multiple entry points"); // A Brillig function is used by multiple entry points. Fetch both globals allocations // in case one is used by the internal call. let entry_point_allocations = entry_points diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index df33030a010..ac80bd54497 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -203,7 +203,10 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result Self { - let dfg = another.dfg.clone(); - let entry_block = another.entry_block; - Self { name: another.name.clone(), id: None, entry_block, dfg } - } + // pub(crate) fn clone_no_id(another: &Function) -> Self { + // let dfg = another.dfg.clone(); + // let entry_block = another.entry_block; + // Self { name: another.name.clone(), id: None, entry_block, dfg } + // } /// Takes the signature (function name & runtime) from a function but does not copy the body. pub(crate) fn clone_signature(id: FunctionId, another: &Function) -> Self { diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 98d42ef838e..2e3237e7c77 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -14,59 +14,87 @@ use crate::{ impl Ssa { pub(crate) fn duplicate_reused_entry_points(mut self) -> Ssa { + if self.main().runtime().is_brillig() { + return self; + } + let brillig_entry_points = get_brillig_entry_points(&self.functions); let entry_points = brillig_entry_points.keys().copied().collect::>(); + // dbg!(brillig_entry_points.clone()); + // dbg!(entry_points.clone()); + + // Map for fetching the correct entry point globals when compiling any function + let mut inner_call_to_entry_point: HashMap> = + HashMap::default(); - let mut calls_to_update: HashMap = HashMap::default(); - let mut functions_to_update: HashSet = HashSet::default(); + // We only need to generate globals for entry points + for (entry_point, entry_point_inner_calls) in brillig_entry_points.iter() { + let entry_point = *entry_point; + for inner_call in entry_point_inner_calls { + inner_call_to_entry_point.entry(*inner_call).or_default().push(entry_point); + } + } + dbg!(inner_call_to_entry_point.clone()); + + // Determine the number of times a function is called in different Brillig entry points + // let entry_point_call_counts = brillig_entry_points + // .values() + // .flat_map(|set| set.iter()) + // .fold(HashMap::default(), |mut counts, &function_id| { + // *counts.entry(function_id).or_insert(0) += 1; + // counts + // }); + // dbg!(entry_point_call_counts.clone()); + + let mut calls_to_update_w_entry: HashMap<(FunctionId, FunctionId), FunctionId> = + HashMap::default(); let mut functions_to_clone_map: HashMap> = HashMap::default(); - for (entry_point, function) in self.functions.iter() { - for block in function.reachable_blocks() { - for &instruction_id in function.dfg[block].instructions() { - let instruction = &function.dfg[instruction_id]; - let Instruction::Call { func: func_id, .. } = instruction else { - continue; - }; - let func_value = &function.dfg[*func_id]; - let Value::Function(called_func_id) = func_value else { continue }; - - if function.dfg.runtime().is_brillig() - && entry_points.contains(called_func_id) - && *called_func_id != function.id() - { - let cloned_function = - Function::clone_no_id(&self.functions[called_func_id]); - functions_to_clone_map - .entry(*entry_point) - .or_default() - .push((cloned_function, *called_func_id)); - } + for (inner_call, inner_call_entry_points) in inner_call_to_entry_point { + if inner_call_entry_points.len() > 1 { + for entry_point in inner_call_entry_points { + let cloned_function = self.functions[&inner_call].clone(); + functions_to_clone_map + .entry(entry_point) + .or_default() + .push((cloned_function, inner_call)); } + } else if entry_points.contains(&inner_call) { + // else { + // dbg!(inner_call); + let entry_point = inner_call_entry_points[0]; + // dbg!(entry_point); + let cloned_function = self.functions[&inner_call].clone(); + functions_to_clone_map + .entry(entry_point) + .or_default() + .push((cloned_function, inner_call)); } } for (entry_point, functions_to_clone) in functions_to_clone_map { for (mut cloned_function, old_id) in functions_to_clone { - if calls_to_update.get(&old_id).is_some() { - continue; - } self.add_fn(|id| { - calls_to_update.insert(old_id, id); - + calls_to_update_w_entry.insert((entry_point, old_id), id); + // println!("entry point {entry_point}, old_id {old_id}, new_id {id}"); cloned_function.set_id(id); - functions_to_update.insert(entry_point); - cloned_function }); } } - for func_id in functions_to_update { - let function = self.functions.get_mut(&func_id).expect("ICE: Function does not exist"); + // dbg!(calls_to_update_w_entry.clone()); + + let mut new_functions_map = HashMap::default(); + for (entry_point, inner_calls) in brillig_entry_points { + let new_entry_point = + new_functions_map.get(&entry_point).copied().unwrap_or(entry_point); + // println!("old entry point {entry_point}, new {new_entry_point}"); + let function = + self.functions.get_mut(&new_entry_point).expect("ICE: Function does not exist"); for block_id in function.reachable_blocks() { #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here for instruction_id in function.dfg[block_id].instructions().to_vec() { @@ -78,7 +106,10 @@ impl Ssa { let func_value = &function.dfg[func_id]; let Value::Function(func_id) = func_value else { continue }; - if let Some(new_id) = calls_to_update.get(func_id) { + // println!("call to {func_id} in entry {entry_point}"); + if let Some(new_id) = calls_to_update_w_entry.get(&(entry_point, *func_id)) { + println!("inserting {new_id}, for old {func_id}, entry {entry_point}"); + new_functions_map.insert(*func_id, *new_id); let new_function_value_id = function.dfg.import_function(*new_id); function.dfg[instruction_id] = Instruction::Call { func: new_function_value_id, @@ -89,6 +120,39 @@ impl Ssa { } } } + for inner_call in inner_calls { + let new_inner_call = + new_functions_map.get(&inner_call).copied().unwrap_or(inner_call); + // println!("old inner_call {inner_call}, new {new_inner_call}"); + let function = + self.functions.get_mut(&new_inner_call).expect("ICE: Function does not exist"); + + for block_id in function.reachable_blocks() { + #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here + for instruction_id in function.dfg[block_id].instructions().to_vec() { + let instruction = function.dfg[instruction_id].clone(); + let Instruction::Call { func: func_id, arguments } = instruction else { + continue; + }; + + let func_value = &function.dfg[func_id]; + let Value::Function(func_id) = func_value else { continue }; + // println!("call to {func_id} in {inner_call} from entry {entry_point}"); + if let Some(new_id) = calls_to_update_w_entry.get(&(entry_point, *func_id)) + { + // println!("inserting {new_id}, for old {func_id}, entry {entry_point}"); + new_functions_map.insert(*func_id, *new_id); + let new_function_value_id = function.dfg.import_function(*new_id); + function.dfg[instruction_id] = Instruction::Call { + func: new_function_value_id, + arguments: arguments.clone(), + }; + } else { + continue; + } + } + } + } } self diff --git a/test_programs/execution_success/global_var_func_with_multiple_entry_points/Nargo.toml b/test_programs/execution_success/global_var_func_with_multiple_entry_points/Nargo.toml new file mode 100644 index 00000000000..4d2bd864e8b --- /dev/null +++ b/test_programs/execution_success/global_var_func_with_multiple_entry_points/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "global_var_func_with_multiple_entry_points" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/global_var_func_with_multiple_entry_points/Prover.toml b/test_programs/execution_success/global_var_func_with_multiple_entry_points/Prover.toml new file mode 100644 index 00000000000..4c144083f00 --- /dev/null +++ b/test_programs/execution_success/global_var_func_with_multiple_entry_points/Prover.toml @@ -0,0 +1,2 @@ +x = 0 +y = 1 diff --git a/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr new file mode 100644 index 00000000000..7570aed8bf1 --- /dev/null +++ b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr @@ -0,0 +1,28 @@ +global ONE: Field = 1; +global TWO: Field = 2; +global THREE: Field = 3; + +fn main(x: Field, y: pub Field) { + /// Safety: testing context + unsafe { + entry_point_one(x, y); + entry_point_two(x, y); + } +} + +unconstrained fn entry_point_one(x: Field, y: Field) { + let z = ONE + x + y; + assert(z == 2); + inner_func(x, y); +} + +unconstrained fn entry_point_two(x: Field, y: Field) { + let z = TWO + x + y; + assert(z == 3); + inner_func(x, y); +} + +unconstrained fn inner_func(x: Field, y: Field) { + let z = THREE + x + y; + assert(z == 4); +} \ No newline at end of file diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/Nargo.toml b/test_programs/execution_success/global_var_multiple_entry_points_nested/Nargo.toml new file mode 100644 index 00000000000..e7ef4a6f36d --- /dev/null +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "global_var_multiple_entry_points_nested" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/Prover.toml b/test_programs/execution_success/global_var_multiple_entry_points_nested/Prover.toml new file mode 100644 index 00000000000..4c144083f00 --- /dev/null +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/Prover.toml @@ -0,0 +1,2 @@ +x = 0 +y = 1 diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr new file mode 100644 index 00000000000..3e755bf58ee --- /dev/null +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr @@ -0,0 +1,55 @@ +global ONE: Field = 1; +global TWO: Field = 2; +global THREE: Field = 3; + +fn main(x: Field, y: pub Field) { + /// Safety: testing context + unsafe { + // entry_point_one(x, y); + entry_point_two(x, y); + entry_point_three(x, y); + // entry_point_four(x, y); + } +} + +// unconstrained fn entry_point_one(x: Field, y: Field) { +// let z = ONE + x + y; +// assert(z == 2); +// inner_func_one(x, y); +// } + +unconstrained fn entry_point_two(x: Field, y: Field) { + let z = TWO + x + y; + assert(z == 3); + inner_func_two(x, y); +} + +// Identical to `entry_point_two` +unconstrained fn entry_point_three(x: Field, y: Field) { + let z = TWO + x + y; + assert(z == 3); + inner_func_two(x, y); +} + +// Identical to `entry_point_two` +// unconstrained fn entry_point_four(x: Field, y: Field) { +// let z = TWO + x + y; +// assert(z == 3); +// inner_func_two(x, y); +// } + +unconstrained fn inner_func_one(x: Field, y: Field) { + let z = ONE + x + y; + assert(z == 2); +} + +unconstrained fn inner_func_two(x: Field, y: Field) { + let z = TWO + x + y; + assert(z == 3); + nested_inner_func(x, y); +} + +unconstrained fn nested_inner_func(x: Field, y: Field) { + let z = THREE + x + y; + assert(z == 4); +} \ No newline at end of file diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt b/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt new file mode 100644 index 00000000000..7ebc66ee973 --- /dev/null +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt @@ -0,0 +1,1759 @@ +After Initial SSA: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Removing Unreachable Functions (1st): +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Defunctionalization: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Inlining simple functions: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Removing Paired rc_inc & rc_decs: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Preprocessing Functions: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Inlining (1st): +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Mem2Reg (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Simplifying (1st): +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After `as_slice` optimization: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Removing Unreachable Functions (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After `static_assert` and `assert_constant`: +g0 = Field 2 +g1 = Field 3 + +acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Purity Analysis: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Loop Invariant Code Motion: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Unrolling: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Simplifying (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Mem2Reg (3rd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Flattening: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Removing Bit Shifts: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Mem2Reg (4th): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Inlining (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Remove IfElse: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Purity Analysis (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Constant Folding: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After EnableSideEffectsIf removal: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Constraint Folding: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +After Adding constrain not equal: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} + +entry_point f1, old_id f4, new_id f5 +entry_point f1, old_id f3, new_id f6 +entry_point f2, old_id f4, new_id f7 +entry_point f2, old_id f3, new_id f8 +entry_point: f1 +{ + Id( + 4, + ), + Id( + 3, + ), +} +call to f3 in entry f1 +new_id f6 +call to f4 in f6 from entry f1 +new_id f5 +entry_point: f2 +{ + Id( + 4, + ), + Id( + 3, + ), +} +call to f3 in entry f2 +new_id f8 +call to f4 in f8 from entry f2 +new_id f7 +After Brillig Entry Point Duplication: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f8(v2, v3) + return +} +brillig(inline) predicate_pure fn inner_func_two f3 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} +brillig(inline) fn nested_inner_func f7 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f8 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f7(v2, v3) + return +} + +After Removing Unreachable Functions (3rd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + v7 = eq v5, Field 4 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + v6 = eq v5, Field 3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Dead Instruction Elimination (1st): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Simplifying (3rd):: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Array Set Optimizations: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Inlining Brillig Calls Inlining: +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Removing Unreachable Functions (3rd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +After Dead Instruction Elimination (2nd): +g0 = Field 2 +g1 = Field 3 + +acir(inline) predicate_pure fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_two f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return +} +brillig(inline) predicate_pure fn entry_point_three f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return +} +brillig(inline) predicate_pure fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) predicate_pure fn inner_func_two f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return +} +brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return +} +brillig(inline) fn inner_func_two f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return +} + +[global_var_multiple_entry_points_nested] Circuit witness successfully solved +[global_var_multiple_entry_points_nested] Witness saved to /mnt/user-data/maxim/noir/test_programs/execution_success/global_var_multiple_entry_points_nested/target/global_var_multiple_entry_points_nested.gz From f0511c749fbbbeecb34d543fb915db7e8ed1e290 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 15:04:37 +0000 Subject: [PATCH 08/28] do not commit debug ssa file --- .../ssa.txt | 1759 ----------------- 1 file changed, 1759 deletions(-) delete mode 100644 test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt b/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt deleted file mode 100644 index 7ebc66ee973..00000000000 --- a/test_programs/execution_success/global_var_multiple_entry_points_nested/ssa.txt +++ /dev/null @@ -1,1759 +0,0 @@ -After Initial SSA: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Removing Unreachable Functions (1st): -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Defunctionalization: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Inlining simple functions: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Removing Paired rc_inc & rc_decs: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Preprocessing Functions: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Inlining (1st): -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Mem2Reg (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Simplifying (1st): -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After `as_slice` optimization: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Removing Unreachable Functions (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After `static_assert` and `assert_constant`: -g0 = Field 2 -g1 = Field 3 - -acir(inline) fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Purity Analysis: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Loop Invariant Code Motion: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Unrolling: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Simplifying (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Mem2Reg (3rd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Flattening: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Removing Bit Shifts: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Mem2Reg (4th): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Inlining (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Remove IfElse: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Purity Analysis (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Constant Folding: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After EnableSideEffectsIf removal: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Constraint Folding: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -After Adding constrain not equal: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} - -entry_point f1, old_id f4, new_id f5 -entry_point f1, old_id f3, new_id f6 -entry_point f2, old_id f4, new_id f7 -entry_point f2, old_id f3, new_id f8 -entry_point: f1 -{ - Id( - 4, - ), - Id( - 3, - ), -} -call to f3 in entry f1 -new_id f6 -call to f4 in f6 from entry f1 -new_id f5 -entry_point: f2 -{ - Id( - 4, - ), - Id( - 3, - ), -} -call to f3 in entry f2 -new_id f8 -call to f4 in f8 from entry f2 -new_id f7 -After Brillig Entry Point Duplication: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f8(v2, v3) - return -} -brillig(inline) predicate_pure fn inner_func_two f3 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f4 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} -brillig(inline) fn nested_inner_func f7 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f8 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f7(v2, v3) - return -} - -After Removing Unreachable Functions (3rd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - v7 = eq v5, Field 4 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - v6 = eq v5, Field 3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Dead Instruction Elimination (1st): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Simplifying (3rd):: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Array Set Optimizations: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Inlining Brillig Calls Inlining: -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Removing Unreachable Functions (3rd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -After Dead Instruction Elimination (2nd): -g0 = Field 2 -g1 = Field 3 - -acir(inline) predicate_pure fn main f0 { - b0(v2: Field, v3: Field): - call f1(v2, v3) - call f2(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_two f1 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f4(v2, v3) - return -} -brillig(inline) predicate_pure fn entry_point_three f2 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f6(v2, v3) - return -} -brillig(inline) predicate_pure fn nested_inner_func f3 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) predicate_pure fn inner_func_two f4 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f3(v2, v3) - return -} -brillig(inline) fn nested_inner_func f5 { - b0(v2: Field, v3: Field): - v4 = add Field 3, v2 - v5 = add v4, v3 - constrain v5 == Field 4 - return -} -brillig(inline) fn inner_func_two f6 { - b0(v2: Field, v3: Field): - v4 = add Field 2, v2 - v5 = add v4, v3 - constrain v5 == Field 3 - call f5(v2, v3) - return -} - -[global_var_multiple_entry_points_nested] Circuit witness successfully solved -[global_var_multiple_entry_points_nested] Witness saved to /mnt/user-data/maxim/noir/test_programs/execution_success/global_var_multiple_entry_points_nested/target/global_var_multiple_entry_points_nested.gz From a850ec0e94c3935172212955c89e7268dc68ed06 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 15:13:25 +0000 Subject: [PATCH 09/28] nargo fmt --- .../global_var_func_with_multiple_entry_points/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr index 7570aed8bf1..ae3d0fe7a64 100644 --- a/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr +++ b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr @@ -25,4 +25,4 @@ unconstrained fn entry_point_two(x: Field, y: Field) { unconstrained fn inner_func(x: Field, y: Field) { let z = THREE + x + y; assert(z == 4); -} \ No newline at end of file +} From 937d2a2ea8ecaa2295c869f48ae4825debdb4d50 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 15:22:37 +0000 Subject: [PATCH 10/28] nargo fmt --- .../global_var_multiple_entry_points_nested/src/main.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr index 3e755bf58ee..a706b7cce05 100644 --- a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr @@ -52,4 +52,4 @@ unconstrained fn inner_func_two(x: Field, y: Field) { unconstrained fn nested_inner_func(x: Field, y: Field) { let z = THREE + x + y; assert(z == 4); -} \ No newline at end of file +} From 13bf7bfc8ca3829d8f3c3fa882ec56afbd0615d4 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 18:47:57 +0000 Subject: [PATCH 11/28] cleanup brillig entry points pass --- compiler/noirc_evaluator/src/ssa.rs | 13 +- .../noirc_evaluator/src/ssa/ir/function.rs | 10 +- .../src/ssa/opt/brillig_entry_points.rs | 138 +++++++----------- 3 files changed, 61 insertions(+), 100 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index ac80bd54497..c499ca3440e 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -138,8 +138,8 @@ pub(crate) fn optimize_into_acir( .run_pass(|ssa| ssa.fold_constants_with_brillig(&brillig), "Inlining Brillig Calls Inlining") // It could happen that we inlined all calls to a given brillig function. // In that case it's unused so we can remove it. This is what we check next. - .run_pass(Ssa::remove_unreachable_functions, "Removing Unreachable Functions (3rd)") - .run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (2nd)") + .run_pass(Ssa::remove_unreachable_functions, "Removing Unreachable Functions (2nd)") + .run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (3rd)") .finish(); drop(ssa_gen_span_guard); @@ -201,15 +201,14 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result Self { - // let dfg = another.dfg.clone(); - // let entry_block = another.entry_block; - // Self { name: another.name.clone(), id: None, entry_block, dfg } - // } + pub(crate) fn clone_no_id(another: &Function) -> Self { + let dfg = another.dfg.clone(); + let entry_block = another.entry_block; + Self { name: another.name.clone(), id: None, entry_block, dfg } + } /// Takes the signature (function name & runtime) from a function but does not copy the body. pub(crate) fn clone_signature(id: FunctionId, another: &Function) -> Self { diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 2e3237e7c77..64e40aa3f06 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -20,8 +20,6 @@ impl Ssa { let brillig_entry_points = get_brillig_entry_points(&self.functions); let entry_points = brillig_entry_points.keys().copied().collect::>(); - // dbg!(brillig_entry_points.clone()); - // dbg!(entry_points.clone()); // Map for fetching the correct entry point globals when compiling any function let mut inner_call_to_entry_point: HashMap> = @@ -35,126 +33,90 @@ impl Ssa { } } - dbg!(inner_call_to_entry_point.clone()); - - // Determine the number of times a function is called in different Brillig entry points - // let entry_point_call_counts = brillig_entry_points - // .values() - // .flat_map(|set| set.iter()) - // .fold(HashMap::default(), |mut counts, &function_id| { - // *counts.entry(function_id).or_insert(0) += 1; - // counts - // }); - // dbg!(entry_point_call_counts.clone()); - - let mut calls_to_update_w_entry: HashMap<(FunctionId, FunctionId), FunctionId> = - HashMap::default(); let mut functions_to_clone_map: HashMap> = HashMap::default(); + let mut add_function_to_clone = |entry_point: FunctionId, inner_call: FunctionId| { + let cloned_function = Function::clone_no_id(&self.functions[&inner_call]); + functions_to_clone_map + .entry(entry_point) + .or_default() + .push((cloned_function, inner_call)); + }; + for (inner_call, inner_call_entry_points) in inner_call_to_entry_point { if inner_call_entry_points.len() > 1 { for entry_point in inner_call_entry_points { - let cloned_function = self.functions[&inner_call].clone(); - functions_to_clone_map - .entry(entry_point) - .or_default() - .push((cloned_function, inner_call)); + add_function_to_clone(entry_point, inner_call); } } else if entry_points.contains(&inner_call) { - // else { - // dbg!(inner_call); - let entry_point = inner_call_entry_points[0]; - // dbg!(entry_point); - let cloned_function = self.functions[&inner_call].clone(); - functions_to_clone_map - .entry(entry_point) - .or_default() - .push((cloned_function, inner_call)); + add_function_to_clone(inner_call_entry_points[0], inner_call); } } + // Maps (entry point, callee function) -> new callee function id + let mut calls_to_update: HashMap<(FunctionId, FunctionId), FunctionId> = HashMap::default(); for (entry_point, functions_to_clone) in functions_to_clone_map { for (mut cloned_function, old_id) in functions_to_clone { self.add_fn(|id| { - calls_to_update_w_entry.insert((entry_point, old_id), id); - // println!("entry point {entry_point}, old_id {old_id}, new_id {id}"); + calls_to_update.insert((entry_point, old_id), id); cloned_function.set_id(id); - cloned_function }); } } - // dbg!(calls_to_update_w_entry.clone()); - let mut new_functions_map = HashMap::default(); for (entry_point, inner_calls) in brillig_entry_points { let new_entry_point = new_functions_map.get(&entry_point).copied().unwrap_or(entry_point); - // println!("old entry point {entry_point}, new {new_entry_point}"); let function = self.functions.get_mut(&new_entry_point).expect("ICE: Function does not exist"); - for block_id in function.reachable_blocks() { - #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here - for instruction_id in function.dfg[block_id].instructions().to_vec() { - let instruction = function.dfg[instruction_id].clone(); - let Instruction::Call { func: func_id, arguments } = instruction else { - continue; - }; - - let func_value = &function.dfg[func_id]; - let Value::Function(func_id) = func_value else { continue }; - - // println!("call to {func_id} in entry {entry_point}"); - if let Some(new_id) = calls_to_update_w_entry.get(&(entry_point, *func_id)) { - println!("inserting {new_id}, for old {func_id}, entry {entry_point}"); - new_functions_map.insert(*func_id, *new_id); - let new_function_value_id = function.dfg.import_function(*new_id); - function.dfg[instruction_id] = Instruction::Call { - func: new_function_value_id, - arguments: arguments.clone(), - }; - } else { - continue; - } - } - } + update_function_calls(function, entry_point, &mut new_functions_map, &calls_to_update); for inner_call in inner_calls { let new_inner_call = new_functions_map.get(&inner_call).copied().unwrap_or(inner_call); - // println!("old inner_call {inner_call}, new {new_inner_call}"); let function = self.functions.get_mut(&new_inner_call).expect("ICE: Function does not exist"); - - for block_id in function.reachable_blocks() { - #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here - for instruction_id in function.dfg[block_id].instructions().to_vec() { - let instruction = function.dfg[instruction_id].clone(); - let Instruction::Call { func: func_id, arguments } = instruction else { - continue; - }; - - let func_value = &function.dfg[func_id]; - let Value::Function(func_id) = func_value else { continue }; - // println!("call to {func_id} in {inner_call} from entry {entry_point}"); - if let Some(new_id) = calls_to_update_w_entry.get(&(entry_point, *func_id)) - { - // println!("inserting {new_id}, for old {func_id}, entry {entry_point}"); - new_functions_map.insert(*func_id, *new_id); - let new_function_value_id = function.dfg.import_function(*new_id); - function.dfg[instruction_id] = Instruction::Call { - func: new_function_value_id, - arguments: arguments.clone(), - }; - } else { - continue; - } - } - } + update_function_calls( + function, + entry_point, + &mut new_functions_map, + &calls_to_update, + ); } } self } } + +fn update_function_calls( + function: &mut Function, + entry_point: FunctionId, + new_functions_map: &mut HashMap, + // Maps (entry point, callee function) -> new callee function id + calls_to_update: &HashMap<(FunctionId, FunctionId), FunctionId>, +) { + for block_id in function.reachable_blocks() { + #[allow(clippy::unnecessary_to_owned)] // clippy is wrong here + for instruction_id in function.dfg[block_id].instructions().to_vec() { + let instruction = function.dfg[instruction_id].clone(); + let Instruction::Call { func: func_id, arguments } = instruction else { + continue; + }; + + let func_value = &function.dfg[func_id]; + let Value::Function(func_id) = func_value else { continue }; + + if let Some(new_id) = calls_to_update.get(&(entry_point, *func_id)) { + new_functions_map.insert(*func_id, *new_id); + let new_function_value_id = function.dfg.import_function(*new_id); + function.dfg[instruction_id] = + Instruction::Call { func: new_function_value_id, arguments: arguments.clone() }; + } else { + continue; + } + } + } +} From e3237200c049728f781c4a52b8871a4ccc005ddd Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 6 Feb 2025 19:42:12 +0000 Subject: [PATCH 12/28] cleanup and DRY in various places --- .../brillig/brillig_gen/brillig_globals.rs | 186 ++------------- compiler/noirc_evaluator/src/brillig/mod.rs | 4 +- .../src/ssa/opt/brillig_entry_points.rs | 217 +++++++++++++----- compiler/noirc_evaluator/src/ssa/opt/mod.rs | 2 +- 4 files changed, 188 insertions(+), 221 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index ed78117809f..0c5a7bde461 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -6,9 +6,9 @@ use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use super::{ BrilligArtifact, BrilligBlock, BrilligVariable, Function, FunctionContext, Label, ValueId, }; -use crate::brillig::{ - brillig_ir::BrilligContext, called_functions_vec, Brillig, DataFlowGraph, FunctionId, - Instruction, Value, +use crate::{ + brillig::{brillig_ir::BrilligContext, Brillig, DataFlowGraph, FunctionId}, + ssa::opt::brillig_entry_points::{build_inner_call_to_entry_points, get_brillig_entry_points}, }; /// Context structure for generating Brillig globals @@ -37,169 +37,33 @@ pub(crate) struct BrilligGlobals { /// Mapping of SSA value ids to their Brillig allocations pub(crate) type SsaToBrilligGlobals = HashMap; -pub(crate) fn get_brillig_entry_points( - functions: &BTreeMap, -) -> HashMap> { - let mut brillig_entry_points = HashMap::default(); - let acir_functions = functions.iter().filter(|(_, func)| func.runtime().is_acir()); - for (_, function) in acir_functions { - for block_id in function.reachable_blocks() { - for instruction_id in function.dfg[block_id].instructions() { - let instruction = &function.dfg[*instruction_id]; - let Instruction::Call { func: func_id, arguments: _ } = instruction else { - continue; - }; - - let func_value = &function.dfg[*func_id]; - let Value::Function(func_id) = func_value else { continue }; - - let called_function = &functions[func_id]; - if called_function.runtime().is_acir() { - continue; - } - - // We have now found a Brillig entry point. - brillig_entry_points.insert(*func_id, HashSet::default()); - build_entry_points_map_recursive( - functions, - *func_id, - called_function, - &mut brillig_entry_points, - im::HashSet::new(), - ); - } - } - } - brillig_entry_points -} - -pub(crate) fn build_entry_points_map_recursive( - functions: &BTreeMap, - entry_point: FunctionId, - called_function: &Function, - brillig_entry_points: &mut HashMap>, - mut explored_functions: im::HashSet, -) { - if explored_functions.insert(called_function.id()).is_some() { - return; - } - - let inner_calls = called_functions_vec(called_function).into_iter().collect::>(); - - for inner_call in inner_calls { - if let Some(inner_calls) = brillig_entry_points.get_mut(&entry_point) { - inner_calls.insert(inner_call); - } - - build_entry_points_map_recursive( - functions, - entry_point, - &functions[&inner_call], - brillig_entry_points, - explored_functions.clone(), - ); - } -} - impl BrilligGlobals { pub(crate) fn new( functions: &BTreeMap, mut used_globals: HashMap>, main_id: FunctionId, ) -> Self { - let mut brillig_entry_points = HashMap::default(); - let acir_functions = functions.iter().filter(|(_, func)| func.runtime().is_acir()); - for (_, function) in acir_functions { - for block_id in function.reachable_blocks() { - for instruction_id in function.dfg[block_id].instructions() { - let instruction = &function.dfg[*instruction_id]; - let Instruction::Call { func: func_id, arguments: _ } = instruction else { - continue; - }; - - let func_value = &function.dfg[*func_id]; - let Value::Function(func_id) = func_value else { continue }; - - let called_function = &functions[func_id]; - if called_function.runtime().is_acir() { - continue; - } - - // We have now found a Brillig entry point. - // Let's recursively build a call graph to determine any functions - // whose parent is this entry point and any globals used in those internal calls. - brillig_entry_points.insert(*func_id, HashSet::default()); - Self::mark_entry_points_calls_recursive( - functions, - *func_id, - called_function, - &mut used_globals, - &mut brillig_entry_points, - im::HashSet::new(), - ); - } + let brillig_entry_points = get_brillig_entry_points(functions, main_id); + + // Mark any globals used in a Brillig entry point. + // Using the information collected we can determine which globals + // an entry point must initialize. + for (entry_point, entry_point_inner_calls) in brillig_entry_points.iter() { + for inner_call in entry_point_inner_calls.iter() { + let inner_globals = used_globals + .get(inner_call) + .expect("Should have a slot for each function") + .clone(); + used_globals + .get_mut(entry_point) + .expect("ICE: should have func") + .extend(inner_globals); } } - // If main has been marked as Brillig, it is itself an entry point. - // Run the same analysis from above on main. - let main_func = &functions[&main_id]; - if main_func.runtime().is_brillig() { - brillig_entry_points.insert(main_id, HashSet::default()); - Self::mark_entry_points_calls_recursive( - functions, - main_id, - main_func, - &mut used_globals, - &mut brillig_entry_points, - im::HashSet::new(), - ); - } + let inner_call_to_entry_point = build_inner_call_to_entry_points(&brillig_entry_points); - Self { used_globals, brillig_entry_points, ..Default::default() } - } - - /// Recursively mark any functions called in an entry point as well as - /// any globals used in those functions. - /// Using the information collected we can determine which globals - /// an entry point must initialize. - fn mark_entry_points_calls_recursive( - functions: &BTreeMap, - entry_point: FunctionId, - called_function: &Function, - used_globals: &mut HashMap>, - brillig_entry_points: &mut HashMap>, - mut explored_functions: im::HashSet, - ) { - if explored_functions.insert(called_function.id()).is_some() { - return; - } - - let inner_calls = called_functions_vec(called_function).into_iter().collect::>(); - - for inner_call in inner_calls { - let inner_globals = used_globals - .get(&inner_call) - .expect("Should have a slot for each function") - .clone(); - used_globals - .get_mut(&entry_point) - .expect("ICE: should have func") - .extend(inner_globals); - - if let Some(inner_calls) = brillig_entry_points.get_mut(&entry_point) { - inner_calls.insert(inner_call); - } - - Self::mark_entry_points_calls_recursive( - functions, - entry_point, - &functions[&inner_call], - used_globals, - brillig_entry_points, - explored_functions.clone(), - ); - } + Self { used_globals, brillig_entry_points, inner_call_to_entry_point, ..Default::default() } } pub(crate) fn declare_globals( @@ -208,18 +72,11 @@ impl BrilligGlobals { brillig: &mut Brillig, enable_debug_trace: bool, ) { - // Map for fetching the correct entry point globals when compiling any function - let mut inner_call_to_entry_point: HashMap> = - HashMap::default(); let mut entry_point_globals_map = HashMap::default(); // We only need to generate globals for entry points - for (entry_point, entry_point_inner_calls) in self.brillig_entry_points.iter() { + for (entry_point, _) in self.brillig_entry_points.iter() { let entry_point = *entry_point; - for inner_call in entry_point_inner_calls { - inner_call_to_entry_point.entry(*inner_call).or_default().push(entry_point); - } - let used_globals = self.used_globals.remove(&entry_point).unwrap_or_default(); let (artifact, brillig_globals, globals_size) = convert_ssa_globals(enable_debug_trace, globals_dfg, &used_globals, entry_point); @@ -230,7 +87,6 @@ impl BrilligGlobals { brillig.globals_memory_size.insert(entry_point, globals_size); } - self.inner_call_to_entry_point = inner_call_to_entry_point; self.entry_point_globals_map = entry_point_globals_map; } diff --git a/compiler/noirc_evaluator/src/brillig/mod.rs b/compiler/noirc_evaluator/src/brillig/mod.rs index 791f6a466cf..99cb6877f16 100644 --- a/compiler/noirc_evaluator/src/brillig/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/mod.rs @@ -17,10 +17,8 @@ use crate::ssa::{ ir::{ dfg::DataFlowGraph, function::{Function, FunctionId}, - instruction::Instruction, - value::{Value, ValueId}, + value::ValueId, }, - opt::inlining::called_functions_vec, ssa_gen::Ssa, }; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 64e40aa3f06..df73bb67a2d 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -1,81 +1,47 @@ +use std::collections::BTreeMap; + use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; -use crate::{ - brillig::brillig_gen::brillig_globals::get_brillig_entry_points, - ssa::{ - ir::{ - function::{Function, FunctionId}, - instruction::Instruction, - value::Value, - }, - Ssa, +use crate::ssa::{ + ir::{ + function::{Function, FunctionId}, + instruction::Instruction, + value::Value, }, + Ssa, }; +use super::inlining::called_functions_vec; + impl Ssa { pub(crate) fn duplicate_reused_entry_points(mut self) -> Ssa { if self.main().runtime().is_brillig() { return self; } - let brillig_entry_points = get_brillig_entry_points(&self.functions); + let brillig_entry_points = get_brillig_entry_points(&self.functions, self.main_id); let entry_points = brillig_entry_points.keys().copied().collect::>(); - // Map for fetching the correct entry point globals when compiling any function - let mut inner_call_to_entry_point: HashMap> = - HashMap::default(); + let inner_call_to_entry_point = build_inner_call_to_entry_points(&brillig_entry_points); - // We only need to generate globals for entry points - for (entry_point, entry_point_inner_calls) in brillig_entry_points.iter() { - let entry_point = *entry_point; - for inner_call in entry_point_inner_calls { - inner_call_to_entry_point.entry(*inner_call).or_default().push(entry_point); - } - } - - let mut functions_to_clone_map: HashMap> = - HashMap::default(); - - let mut add_function_to_clone = |entry_point: FunctionId, inner_call: FunctionId| { - let cloned_function = Function::clone_no_id(&self.functions[&inner_call]); - functions_to_clone_map - .entry(entry_point) - .or_default() - .push((cloned_function, inner_call)); - }; - - for (inner_call, inner_call_entry_points) in inner_call_to_entry_point { - if inner_call_entry_points.len() > 1 { - for entry_point in inner_call_entry_points { - add_function_to_clone(entry_point, inner_call); - } - } else if entry_points.contains(&inner_call) { - add_function_to_clone(inner_call_entry_points[0], inner_call); - } - } + let functions_to_clone_map = + build_functions_to_clone(&self.functions, inner_call_to_entry_point, entry_points); - // Maps (entry point, callee function) -> new callee function id - let mut calls_to_update: HashMap<(FunctionId, FunctionId), FunctionId> = HashMap::default(); - for (entry_point, functions_to_clone) in functions_to_clone_map { - for (mut cloned_function, old_id) in functions_to_clone { - self.add_fn(|id| { - calls_to_update.insert((entry_point, old_id), id); - cloned_function.set_id(id); - cloned_function - }); - } - } + let calls_to_update = build_calls_to_update(&mut self, functions_to_clone_map); let mut new_functions_map = HashMap::default(); for (entry_point, inner_calls) in brillig_entry_points { let new_entry_point = new_functions_map.get(&entry_point).copied().unwrap_or(entry_point); + let function = self.functions.get_mut(&new_entry_point).expect("ICE: Function does not exist"); update_function_calls(function, entry_point, &mut new_functions_map, &calls_to_update); + for inner_call in inner_calls { let new_inner_call = new_functions_map.get(&inner_call).copied().unwrap_or(inner_call); + let function = self.functions.get_mut(&new_inner_call).expect("ICE: Function does not exist"); update_function_calls( @@ -91,6 +57,53 @@ impl Ssa { } } +fn build_functions_to_clone( + functions: &BTreeMap, + inner_call_to_entry_point: HashMap>, + entry_points: HashSet, +) -> HashMap> { + let mut functions_to_clone_map: HashMap> = + HashMap::default(); + + let mut add_function_to_clone = |entry_point: FunctionId, inner_call: FunctionId| { + let cloned_function = Function::clone_no_id(&functions[&inner_call]); + functions_to_clone_map.entry(entry_point).or_default().push((cloned_function, inner_call)); + }; + + for (inner_call, inner_call_entry_points) in inner_call_to_entry_point { + if inner_call_entry_points.len() > 1 { + for entry_point in inner_call_entry_points { + add_function_to_clone(entry_point, inner_call); + } + } else if entry_points.contains(&inner_call) { + add_function_to_clone(inner_call_entry_points[0], inner_call); + } + } + + functions_to_clone_map +} + +// Clones new functions and returns a mapping representing the calls to update. +// Returns a map of (entry point, callee function) -> new callee function id. +fn build_calls_to_update( + ssa: &mut Ssa, + functions_to_clone_map: HashMap>, +) -> HashMap<(FunctionId, FunctionId), FunctionId> { + let mut calls_to_update: HashMap<(FunctionId, FunctionId), FunctionId> = HashMap::default(); + + for (entry_point, functions_to_clone) in functions_to_clone_map { + for (mut cloned_function, old_id) in functions_to_clone { + ssa.add_fn(|id| { + calls_to_update.insert((entry_point, old_id), id); + cloned_function.set_id(id); + cloned_function + }); + } + } + + calls_to_update +} + fn update_function_calls( function: &mut Function, entry_point: FunctionId, @@ -120,3 +133,103 @@ fn update_function_calls( } } } + +/// Returns a map of Brillig entry points to all functions called in that entry point. +/// This includes any nested calls as well, as we want to be able to associate +/// any Brillig function with the appropriate global allocations. +pub(crate) fn get_brillig_entry_points( + functions: &BTreeMap, + main_id: FunctionId, +) -> HashMap> { + let mut brillig_entry_points = HashMap::default(); + let acir_functions = functions.iter().filter(|(_, func)| func.runtime().is_acir()); + for (_, function) in acir_functions { + for block_id in function.reachable_blocks() { + for instruction_id in function.dfg[block_id].instructions() { + let instruction = &function.dfg[*instruction_id]; + let Instruction::Call { func: func_id, arguments: _ } = instruction else { + continue; + }; + + let func_value = &function.dfg[*func_id]; + let Value::Function(func_id) = func_value else { continue }; + + let called_function = &functions[func_id]; + if called_function.runtime().is_acir() { + continue; + } + + // We have now found a Brillig entry point. + brillig_entry_points.insert(*func_id, HashSet::default()); + build_entry_points_map_recursive( + functions, + *func_id, + called_function, + &mut brillig_entry_points, + im::HashSet::new(), + ); + } + } + } + + // If main has been marked as Brillig, it is itself an entry point. + // Run the same analysis from above on main. + let main_func = &functions[&main_id]; + if main_func.runtime().is_brillig() { + brillig_entry_points.insert(main_id, HashSet::default()); + build_entry_points_map_recursive( + functions, + main_id, + &functions[&main_id], + &mut brillig_entry_points, + im::HashSet::new(), + ); + } + + brillig_entry_points +} + +/// Recursively mark any functions called in an entry point +fn build_entry_points_map_recursive( + functions: &BTreeMap, + entry_point: FunctionId, + called_function: &Function, + brillig_entry_points: &mut HashMap>, + mut explored_functions: im::HashSet, +) { + if explored_functions.insert(called_function.id()).is_some() { + return; + } + + let inner_calls = called_functions_vec(called_function).into_iter().collect::>(); + + for inner_call in inner_calls { + if let Some(inner_calls) = brillig_entry_points.get_mut(&entry_point) { + inner_calls.insert(inner_call); + } + + build_entry_points_map_recursive( + functions, + entry_point, + &functions[&inner_call], + brillig_entry_points, + explored_functions.clone(), + ); + } +} + +pub(crate) fn build_inner_call_to_entry_points( + brillig_entry_points: &HashMap>, +) -> HashMap> { + // Map for fetching the correct entry point globals when compiling any function + let mut inner_call_to_entry_point: HashMap> = HashMap::default(); + + // We only need to generate globals for entry points + for (entry_point, entry_point_inner_calls) in brillig_entry_points.iter() { + for inner_call in entry_point_inner_calls { + inner_call_to_entry_point.entry(*inner_call).or_default().push(*entry_point); + } + } + + inner_call_to_entry_point +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 73e291b07f3..4d8a652b94d 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -7,7 +7,7 @@ mod array_set; mod as_slice_length; mod assert_constant; -mod brillig_entry_points; +pub(crate) mod brillig_entry_points; mod constant_folding; mod defunctionalize; mod die; From fa4db3e00dc4e35b5bd2e9ebc567c31920c33b69 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 10:17:47 +0000 Subject: [PATCH 13/28] use btree map and set --- .../src/brillig/brillig_gen/brillig_globals.rs | 4 ++-- .../src/ssa/opt/brillig_entry_points.rs | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 96a718448a3..b8597ece7e8 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use acvm::FieldElement; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; @@ -24,7 +24,7 @@ pub(crate) struct BrilligGlobals { /// Maps a Brillig entry point to all functions called in that entry point. /// This includes any nested calls as well, as we want to be able to associate /// any Brillig function with the appropriate global allocations. - brillig_entry_points: HashMap>, + brillig_entry_points: BTreeMap>, /// Maps an inner call to its Brillig entry point /// This is simply used to simplify fetching global allocations when compiling diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index df73bb67a2d..7c48103d31c 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; @@ -137,11 +137,12 @@ fn update_function_calls( /// Returns a map of Brillig entry points to all functions called in that entry point. /// This includes any nested calls as well, as we want to be able to associate /// any Brillig function with the appropriate global allocations. +/// TODO: return a BTreeMap pub(crate) fn get_brillig_entry_points( functions: &BTreeMap, main_id: FunctionId, -) -> HashMap> { - let mut brillig_entry_points = HashMap::default(); +) -> BTreeMap> { + let mut brillig_entry_points = BTreeMap::default(); let acir_functions = functions.iter().filter(|(_, func)| func.runtime().is_acir()); for (_, function) in acir_functions { for block_id in function.reachable_blocks() { @@ -160,7 +161,7 @@ pub(crate) fn get_brillig_entry_points( } // We have now found a Brillig entry point. - brillig_entry_points.insert(*func_id, HashSet::default()); + brillig_entry_points.insert(*func_id, BTreeSet::default()); build_entry_points_map_recursive( functions, *func_id, @@ -176,7 +177,7 @@ pub(crate) fn get_brillig_entry_points( // Run the same analysis from above on main. let main_func = &functions[&main_id]; if main_func.runtime().is_brillig() { - brillig_entry_points.insert(main_id, HashSet::default()); + brillig_entry_points.insert(main_id, BTreeSet::default()); build_entry_points_map_recursive( functions, main_id, @@ -194,7 +195,7 @@ fn build_entry_points_map_recursive( functions: &BTreeMap, entry_point: FunctionId, called_function: &Function, - brillig_entry_points: &mut HashMap>, + brillig_entry_points: &mut BTreeMap>, mut explored_functions: im::HashSet, ) { if explored_functions.insert(called_function.id()).is_some() { @@ -219,7 +220,7 @@ fn build_entry_points_map_recursive( } pub(crate) fn build_inner_call_to_entry_points( - brillig_entry_points: &HashMap>, + brillig_entry_points: &BTreeMap>, ) -> HashMap> { // Map for fetching the correct entry point globals when compiling any function let mut inner_call_to_entry_point: HashMap> = HashMap::default(); From d05a3e85b9b37b131accf095a3a2c87a16421683 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 11:00:29 +0000 Subject: [PATCH 14/28] fix brillig globals unit tests --- .../brillig/brillig_gen/brillig_globals.rs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index b8597ece7e8..4937aad45c6 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -227,11 +227,10 @@ mod tests { if func_id.to_u32() == 1 { assert_eq!( artifact.byte_code.len(), - 2, + 1, "Expected just a `Return`, but got more than a single opcode" ); - // TODO: Bring this back (https://github.com/noir-lang/noir/issues/7306) - // assert!(matches!(&artifact.byte_code[0], Opcode::Return)); + assert!(matches!(&artifact.byte_code[0], Opcode::Return)); } else if func_id.to_u32() == 2 { assert_eq!( artifact.byte_code.len(), @@ -345,17 +344,16 @@ mod tests { if func_id.to_u32() == 1 { assert_eq!( artifact.byte_code.len(), - 30, + 2, "Expected enough opcodes to initialize the globals" ); - // TODO: Bring this back (https://github.com/noir-lang/noir/issues/7306) - // let Opcode::Const { destination, bit_size, value } = &artifact.byte_code[0] else { - // panic!("First opcode is expected to be `Const`"); - // }; - // assert_eq!(destination.unwrap_direct(), GlobalSpace::start()); - // assert!(matches!(bit_size, BitSize::Field)); - // assert_eq!(*value, FieldElement::from(1u128)); - // assert!(matches!(&artifact.byte_code[1], Opcode::Return)); + let Opcode::Const { destination, bit_size, value } = &artifact.byte_code[0] else { + panic!("First opcode is expected to be `Const`"); + }; + assert_eq!(destination.unwrap_direct(), GlobalSpace::start()); + assert!(matches!(bit_size, BitSize::Field)); + assert_eq!(*value, FieldElement::from(1u128)); + assert!(matches!(&artifact.byte_code[1], Opcode::Return)); } else if func_id.to_u32() == 2 || func_id.to_u32() == 3 { // We want the entry point which uses globals (f2) and the entry point which calls f2 function internally (f3 through f4) // to have the same globals initialized. From 4160a95a8e40dbc96b78538fad85a3f437a22bc7 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 12:03:47 +0000 Subject: [PATCH 15/28] unit tests for brillig entry point analysis --- compiler/noirc_evaluator/src/ssa.rs | 5 +- .../src/ssa/opt/brillig_entry_points.rs | 317 +++++++++++++++++- 2 files changed, 319 insertions(+), 3 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 9dd897ab634..b35fdea9a5b 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -205,8 +205,9 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result Ssa { + pub(crate) fn brillig_entry_point_analysis(mut self) -> Ssa { if self.main().runtime().is_brillig() { return self; } @@ -234,3 +234,318 @@ pub(crate) fn build_inner_call_to_entry_points( inner_call_to_entry_point } + +#[cfg(test)] +mod tests { + use crate::ssa::opt::assert_normalized_ssa_equals; + + use super::Ssa; + + #[test] + fn duplicate_inner_call_with_multiple_entry_points() { + let src = " + g0 = Field 1 + g1 = Field 2 + g2 = Field 3 + + acir(inline) fn main f0 { + b0(v3: Field, v4: Field): + call f1(v3, v4) + call f2(v3, v4) + return + } + brillig(inline) fn entry_point_one f1 { + b0(v3: Field, v4: Field): + v5 = add g0, v3 + v6 = add v5, v4 + constrain v6 == Field 2 + call f3(v3, v4) + return + } + brillig(inline) fn entry_point_two f2 { + b0(v3: Field, v4: Field): + v5 = add g1, v3 + v6 = add v5, v4 + constrain v6 == Field 3 + call f3(v3, v4) + return + } + brillig(inline) fn inner_func f3 { + b0(v3: Field, v4: Field): + v5 = add g2, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + let ssa = ssa.brillig_entry_point_analysis(); + let ssa = ssa.remove_unreachable_functions(); + + // We expect `inner_func` to be duplicated + let expected = " + g0 = Field 1 + g1 = Field 2 + g2 = Field 3 + + acir(inline) fn main f0 { + b0(v3: Field, v4: Field): + call f1(v3, v4) + call f2(v3, v4) + return + } + brillig(inline) fn entry_point_one f1 { + b0(v3: Field, v4: Field): + v5 = add Field 1, v3 + v6 = add v5, v4 + constrain v6 == Field 2 + call f3(v3, v4) + return + } + brillig(inline) fn entry_point_two f2 { + b0(v3: Field, v4: Field): + v5 = add Field 2, v3 + v6 = add v5, v4 + constrain v6 == Field 3 + call f4(v3, v4) + return + } + brillig(inline) fn inner_func f3 { + b0(v3: Field, v4: Field): + v5 = add Field 3, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + brillig(inline) fn inner_func f4 { + b0(v3: Field, v4: Field): + v5 = add Field 3, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + "; + assert_normalized_ssa_equals(ssa, expected); + } + + #[test] + fn duplicate_inner_call_with_multiple_entry_points_nested() { + let src = " + g0 = Field 2 + g1 = Field 3 + + acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return + } + brillig(inline) fn entry_point_one f1 { + b0(v2: Field, v3: Field): + v4 = add g0, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return + } + brillig(inline) fn entry_point_two f2 { + b0(v2: Field, v3: Field): + v4 = add g0, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return + } + brillig(inline) fn inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add g0, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return + } + brillig(inline) fn nested_inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add g1, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + let ssa = ssa.brillig_entry_point_analysis(); + let ssa = ssa.remove_unreachable_functions(); + + // We expect both `inner_func` and `nested_inner_func` to be duplicated + let expected = " + g0 = Field 2 + g1 = Field 3 + + acir(inline) fn main f0 { + b0(v2: Field, v3: Field): + call f1(v2, v3) + call f2(v2, v3) + return + } + brillig(inline) fn entry_point_one f1 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f4(v2, v3) + return + } + brillig(inline) fn entry_point_two f2 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f6(v2, v3) + return + } + brillig(inline) fn nested_inner_func f3 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return + } + brillig(inline) fn inner_func f4 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f3(v2, v3) + return + } + brillig(inline) fn nested_inner_func f5 { + b0(v2: Field, v3: Field): + v4 = add Field 3, v2 + v5 = add v4, v3 + constrain v5 == Field 4 + return + } + brillig(inline) fn inner_func f6 { + b0(v2: Field, v3: Field): + v4 = add Field 2, v2 + v5 = add v4, v3 + constrain v5 == Field 3 + call f5(v2, v3) + return + } + "; + assert_normalized_ssa_equals(ssa, expected); + } + + #[test] + fn duplicate_entry_point_called_from_entry_points() { + // Check that we duplicate entry points that are also called from another entry point. + // In this test the entry points used in other entry points are f2 and f3. + // These functions are also called within the wrapper function f4, as we also want to make sure + // that we duplicate entry points called from another entry point's inner calls. + let src = " + g0 = Field 2 + g1 = Field 3 + g2 = Field 1 + + acir(inline) fn main f0 { + b0(v3: Field, v4: Field): + call f1(v3, v4) + call f2(v3, v4) + call f3(v3, v4) + return + } + brillig(inline) fn entry_point_inner_func_globals f1 { + b0(v3: Field, v4: Field): + call f4(v3, v4) + return + } + brillig(inline) fn entry_point_one_global f2 { + b0(v3: Field, v4: Field): + v5 = add g0, v3 + v6 = add v5, v4 + constrain v6 == Field 3 + return + } + brillig(inline) fn entry_point_one_diff_global f3 { + b0(v3: Field, v4: Field): + v5 = add g1, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + brillig(inline) fn wrapper f4 { + b0(v3: Field, v4: Field): + v5 = add g2, v3 + v6 = add v5, v4 + constrain v6 == Field 2 + call f2(v3, v4) + call f3(v4, v3) + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + let ssa = ssa.brillig_entry_point_analysis(); + + // We expect `entry_point_one_global` and `entry_point_one_diff_global` to be duplicated + let expected = " + g0 = Field 2 + g1 = Field 3 + g2 = Field 1 + + acir(inline) fn main f0 { + b0(v3: Field, v4: Field): + call f1(v3, v4) + call f2(v3, v4) + call f3(v3, v4) + return + } + brillig(inline) fn entry_point_inner_func_globals f1 { + b0(v3: Field, v4: Field): + call f4(v3, v4) + return + } + brillig(inline) fn entry_point_one_global f2 { + b0(v3: Field, v4: Field): + v5 = add Field 2, v3 + v6 = add v5, v4 + constrain v6 == Field 3 + return + } + brillig(inline) fn entry_point_one_diff_global f3 { + b0(v3: Field, v4: Field): + v5 = add Field 3, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + brillig(inline) fn wrapper f4 { + b0(v3: Field, v4: Field): + v5 = add Field 1, v3 + v6 = add v5, v4 + constrain v6 == Field 2 + call f5(v3, v4) + call f6(v4, v3) + return + } + brillig(inline) fn entry_point_one_global f5 { + b0(v3: Field, v4: Field): + v5 = add Field 2, v3 + v6 = add v5, v4 + constrain v6 == Field 3 + return + } + brillig(inline) fn entry_point_one_diff_global f6 { + b0(v3: Field, v4: Field): + v5 = add Field 3, v3 + v6 = add v5, v4 + constrain v6 == Field 4 + return + } + "; + assert_normalized_ssa_equals(ssa, expected); + } +} From abdc95646eee391116331ed2df7ce0c4adb796e0 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 12:11:46 +0000 Subject: [PATCH 16/28] cleanup regression test --- .../src/main.nr | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr index a706b7cce05..96d9ac42c66 100644 --- a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr @@ -1,49 +1,28 @@ -global ONE: Field = 1; global TWO: Field = 2; global THREE: Field = 3; fn main(x: Field, y: pub Field) { /// Safety: testing context unsafe { - // entry_point_one(x, y); + entry_point_one(x, y); entry_point_two(x, y); - entry_point_three(x, y); - // entry_point_four(x, y); } } -// unconstrained fn entry_point_one(x: Field, y: Field) { -// let z = ONE + x + y; -// assert(z == 2); -// inner_func_one(x, y); -// } - -unconstrained fn entry_point_two(x: Field, y: Field) { +unconstrained fn entry_point_one(x: Field, y: Field) { let z = TWO + x + y; assert(z == 3); - inner_func_two(x, y); + inner_func(x, y); } -// Identical to `entry_point_two` -unconstrained fn entry_point_three(x: Field, y: Field) { +// Identical to `entry_point_one` +unconstrained fn entry_point_two(x: Field, y: Field) { let z = TWO + x + y; assert(z == 3); - inner_func_two(x, y); -} - -// Identical to `entry_point_two` -// unconstrained fn entry_point_four(x: Field, y: Field) { -// let z = TWO + x + y; -// assert(z == 3); -// inner_func_two(x, y); -// } - -unconstrained fn inner_func_one(x: Field, y: Field) { - let z = ONE + x + y; - assert(z == 2); + inner_func(x, y); } -unconstrained fn inner_func_two(x: Field, y: Field) { +unconstrained fn inner_func(x: Field, y: Field) { let z = TWO + x + y; assert(z == 3); nested_inner_func(x, y); From 6302eea9f1dc96e47c2323ba0472e653a546d5a1 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 13:01:35 +0000 Subject: [PATCH 17/28] pass doc comments --- .../src/ssa/opt/brillig_entry_points.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 3829d1b58e3..9db9ede2df2 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -1,3 +1,64 @@ +//! The purpose of this pass is to perform function specialization of Brillig functions based upon +//! a function's entry points. Function specialization is performed through duplication of functions. +//! +//! This pass is done due to how globals are initialized for Brillig generation. +//! We allow multiple Brillig entry points (every call to Brillig from ACIR is an entry point), +//! and in order to avoid re-initializing globals used in one entry point but not another, +//! we set the globals initialization code based upon the globals used in a given entry point. +//! The ultimate goal is to optimize for runtime execution. +//! +//! However, doing the above on its own is insufficient as we allow entry points to be called from +//! other entry points and functions can be called across multiple entry points. +//! As all functions can potentially share entry points and use globals, the global allocations maps +//! generated for different entry points can conflict. +//! +//! To provide a more concrete example, let's take this program: +//! ``` +//! global ONE: Field = 1; +//! global TWO: Field = 2; +//! global THREE: Field = 3; +//! fn main(x: Field, y: pub Field) { +//! /// Safety: testing context +//! unsafe { +//! entry_point_one(x, y); +//! entry_point_two(x, y); +//! } +//! } +//! unconstrained fn entry_point_one(x: Field, y: Field) { +//! let z = ONE + x + y; +//! assert(z == 2); +//! inner_func(x, y); +//! } +//! unconstrained fn entry_point_two(x: Field, y: Field) { +//! let z = TWO + x + y; +//! assert(z == 3); +//! inner_func(x, y); +//! } +//! unconstrained fn inner_func(x: Field, y: Field) { +//! let z = THREE + x + y; +//! assert(z == 4); +//! } +//! ``` +//! The two entry points will have different global allocation maps: +//! ``` +//! GlobalInit(Id(1)): +//! CONST M32835 = 1 +//! CONST M32836 = 2 +//! CONST M32837 = 3 +//! RETURN +//! GlobalInit(Id(2)): +//! CONST M32835 = 2 +//! CONST M32836 = 3 +//! RETURN +//! ``` +//! It is then not clear when generating the bytecode for `inner_func` which global allocations map should be used, +//! and any choice will lead to an incorrect program. +//! If `inner_func` used the map for `entry_point_one` the bytecode generated would use `M32837` to represent `THREE`. +//! However, when `inner_func` is called from `entry_point_two`, the address for `THREE` is `M32836`. +//! +//! This pass will duplicate `inner_func` so that different functions are called by the different entry points. +//! The test module for this pass can be referenced to see how this function duplication looks in SSA. + use std::collections::{BTreeMap, BTreeSet}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; From cc4a8eb776291baa7bda547e48a4bde5d9b2c2af Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 13:12:41 +0000 Subject: [PATCH 18/28] reduce nesting --- .../brillig/brillig_gen/brillig_globals.rs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 4937aad45c6..8253793f933 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -102,33 +102,26 @@ impl BrilligGlobals { &self, brillig_function_id: FunctionId, ) -> SsaToBrilligGlobals { - let entry_points = self.inner_call_to_entry_point.get(&brillig_function_id); - - let mut globals_allocations = HashMap::default(); if let Some(globals) = self.entry_point_globals_map.get(&brillig_function_id) { // Check whether `brillig_function_id` is itself an entry point. // If so, return the global allocations directly from `self.entry_point_globals_map`. - globals_allocations.extend(globals); - return globals_allocations; + return globals.clone(); } - if let Some(entry_points) = entry_points { - assert!(self.entry_point_globals_map.get(&brillig_function_id).is_none()); - assert_eq!(entry_points.len(), 1, "{brillig_function_id} has multiple entry points"); - // A Brillig function is used by multiple entry points. Fetch both globals allocations - // in case one is used by the internal call. - let entry_point_allocations = entry_points - .iter() - .flat_map(|entry_point| self.entry_point_globals_map.get(entry_point)) - .collect::>(); - for map in entry_point_allocations { - globals_allocations.extend(map); - } - } else { + let entry_points = self.inner_call_to_entry_point.get(&brillig_function_id); + let Some(entry_points) = entry_points else { unreachable!( "ICE: Expected global allocation to be set for function {brillig_function_id}" ); + }; + + // Sanity check: We should have guaranteed earlier that an inner call has only a single entry point + assert_eq!(entry_points.len(), 1, "{brillig_function_id} has multiple entry points"); + let mut globals_allocations = HashMap::default(); + if let Some(globals) = self.entry_point_globals_map.get(&entry_points[0]) { + globals_allocations.extend(globals); } + globals_allocations } } From 0a78ca9dc0d1bf344c6f6e41329d12f4ff4c792a Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 14:52:29 +0000 Subject: [PATCH 19/28] do not clone when returning from get_brillig_globals --- .../src/brillig/brillig_gen/brillig_globals.rs | 12 +++++------- compiler/noirc_evaluator/src/brillig/mod.rs | 7 +++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index 8253793f933..4e147ce1083 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -94,18 +94,17 @@ impl BrilligGlobals { /// by any given Brillig function (non-entry point or entry point). /// The allocations available to a function are determined by its entry point. /// For a given function id input, this function will search for that function's - /// entry point (or multiple entry points) and fetch the global allocations - /// associated with those entry points. + /// entry point and fetch the global allocations associated with that entry point. /// These allocations can then be used when compiling the Brillig function /// and resolving global variables. pub(crate) fn get_brillig_globals( &self, brillig_function_id: FunctionId, - ) -> SsaToBrilligGlobals { + ) -> Option<&SsaToBrilligGlobals> { if let Some(globals) = self.entry_point_globals_map.get(&brillig_function_id) { // Check whether `brillig_function_id` is itself an entry point. // If so, return the global allocations directly from `self.entry_point_globals_map`. - return globals.clone(); + return Some(globals); } let entry_points = self.inner_call_to_entry_point.get(&brillig_function_id); @@ -117,12 +116,11 @@ impl BrilligGlobals { // Sanity check: We should have guaranteed earlier that an inner call has only a single entry point assert_eq!(entry_points.len(), 1, "{brillig_function_id} has multiple entry points"); - let mut globals_allocations = HashMap::default(); if let Some(globals) = self.entry_point_globals_map.get(&entry_points[0]) { - globals_allocations.extend(globals); + return Some(globals); } - globals_allocations + None } } diff --git a/compiler/noirc_evaluator/src/brillig/mod.rs b/compiler/noirc_evaluator/src/brillig/mod.rs index 99cb6877f16..5b4778a8ade 100644 --- a/compiler/noirc_evaluator/src/brillig/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/mod.rs @@ -110,10 +110,13 @@ impl Ssa { brillig_globals.declare_globals(&globals_dfg, &mut brillig, enable_debug_trace); for brillig_function_id in brillig_reachable_function_ids { - let globals_allocations = brillig_globals.get_brillig_globals(brillig_function_id); + let empty_allocations = HashMap::default(); + let globals_allocations = brillig_globals + .get_brillig_globals(brillig_function_id) + .unwrap_or(&empty_allocations); let func = &self.functions[&brillig_function_id]; - brillig.compile(func, enable_debug_trace, &globals_allocations); + brillig.compile(func, enable_debug_trace, globals_allocations); } brillig From f668b0bbd89e6733f983c5eef198f9b0274f7e07 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 7 Feb 2025 10:43:01 -0500 Subject: [PATCH 20/28] Update compiler/noirc_evaluator/src/ssa.rs --- compiler/noirc_evaluator/src/ssa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index b35fdea9a5b..cb3e064e4a5 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -138,7 +138,7 @@ pub(crate) fn optimize_into_acir( .run_pass(|ssa| ssa.fold_constants_with_brillig(&brillig), "Inlining Brillig Calls Inlining") // It could happen that we inlined all calls to a given brillig function. // In that case it's unused so we can remove it. This is what we check next. - .run_pass(Ssa::remove_unreachable_functions, "Removing Unreachable Functions (2nd)") + .run_pass(Ssa::remove_unreachable_functions, "Removing Unreachable Functions (4th)") .run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (3rd)") .finish(); From ee7d8cc9a5d9efbf9a50e52840b593f71ffa9412 Mon Sep 17 00:00:00 2001 From: Tom French Date: Mon, 17 Feb 2025 10:58:41 +0000 Subject: [PATCH 21/28] . --- .../global_var_entry_point_used_in_another_entry/src/main.nr | 2 +- .../global_var_func_with_multiple_entry_points/src/main.nr | 2 +- .../global_var_multiple_entry_points_nested/src/main.nr | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr index 13094cfb662..364c27cc4ab 100644 --- a/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr +++ b/test_programs/execution_success/global_var_entry_point_used_in_another_entry/src/main.nr @@ -3,7 +3,7 @@ global TWO: Field = 2; global THREE: Field = 3; fn main(x: Field, y: pub Field) { - /// Safety: testing context + // Safety: testing context unsafe { entry_point_no_global(x, y); entry_point_inner_func_globals(x, y); diff --git a/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr index ae3d0fe7a64..6ae68e21e79 100644 --- a/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr +++ b/test_programs/execution_success/global_var_func_with_multiple_entry_points/src/main.nr @@ -3,7 +3,7 @@ global TWO: Field = 2; global THREE: Field = 3; fn main(x: Field, y: pub Field) { - /// Safety: testing context + // Safety: testing context unsafe { entry_point_one(x, y); entry_point_two(x, y); diff --git a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr index 96d9ac42c66..57e6b872ac3 100644 --- a/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr +++ b/test_programs/execution_success/global_var_multiple_entry_points_nested/src/main.nr @@ -2,7 +2,7 @@ global TWO: Field = 2; global THREE: Field = 3; fn main(x: Field, y: pub Field) { - /// Safety: testing context + // Safety: testing context unsafe { entry_point_one(x, y); entry_point_two(x, y); From c7d6ed09298faa0a3e9cf3b8d3fbdcce034b3eec Mon Sep 17 00:00:00 2001 From: Tom French Date: Mon, 17 Feb 2025 11:29:39 +0000 Subject: [PATCH 22/28] . --- .../noirc_evaluator/src/ssa/opt/brillig_entry_points.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 9db9ede2df2..b76139ad5eb 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -144,8 +144,9 @@ fn build_functions_to_clone( functions_to_clone_map } -// Clones new functions and returns a mapping representing the calls to update. -// Returns a map of (entry point, callee function) -> new callee function id. +/// Clones new functions and returns a mapping representing the calls to update. +/// +/// Returns a map of (entry point, callee function) -> new callee function id. fn build_calls_to_update( ssa: &mut Ssa, functions_to_clone_map: HashMap>, @@ -280,6 +281,8 @@ fn build_entry_points_map_recursive( } } +/// Builds a mapping from a [`FunctionId`] to the set of [`FunctionId`s][`FunctionId`] of all the brillig entrypoints +/// from which this function is reachable. pub(crate) fn build_inner_call_to_entry_points( brillig_entry_points: &BTreeMap>, ) -> HashMap> { From 2b41e30347863d1e9b68ec1d9a0e667fd7eeed02 Mon Sep 17 00:00:00 2001 From: Tom French Date: Mon, 17 Feb 2025 12:09:11 +0000 Subject: [PATCH 23/28] . --- .../src/ssa/opt/brillig_entry_points.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index b76139ad5eb..70221aee658 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -183,15 +183,14 @@ fn update_function_calls( let func_value = &function.dfg[func_id]; let Value::Function(func_id) = func_value else { continue }; - - if let Some(new_id) = calls_to_update.get(&(entry_point, *func_id)) { - new_functions_map.insert(*func_id, *new_id); - let new_function_value_id = function.dfg.import_function(*new_id); - function.dfg[instruction_id] = - Instruction::Call { func: new_function_value_id, arguments: arguments.clone() }; - } else { + let Some(new_id) = calls_to_update.get(&(entry_point, *func_id)) else { continue; - } + }; + + new_functions_map.insert(*func_id, *new_id); + let new_function_value_id = function.dfg.import_function(*new_id); + function.dfg[instruction_id] = + Instruction::Call { func: new_function_value_id, arguments: arguments.clone() }; } } } @@ -227,7 +226,7 @@ pub(crate) fn get_brillig_entry_points( build_entry_points_map_recursive( functions, *func_id, - called_function, + *func_id, &mut brillig_entry_points, im::HashSet::new(), ); @@ -243,7 +242,7 @@ pub(crate) fn get_brillig_entry_points( build_entry_points_map_recursive( functions, main_id, - &functions[&main_id], + main_id, &mut brillig_entry_points, im::HashSet::new(), ); @@ -256,15 +255,16 @@ pub(crate) fn get_brillig_entry_points( fn build_entry_points_map_recursive( functions: &BTreeMap, entry_point: FunctionId, - called_function: &Function, + called_function: FunctionId, brillig_entry_points: &mut BTreeMap>, mut explored_functions: im::HashSet, ) { - if explored_functions.insert(called_function.id()).is_some() { + if explored_functions.insert(called_function).is_some() { return; } - let inner_calls = called_functions_vec(called_function).into_iter().collect::>(); + let inner_calls: HashSet = + called_functions_vec(&functions[&called_function]).into_iter().collect(); for inner_call in inner_calls { if let Some(inner_calls) = brillig_entry_points.get_mut(&entry_point) { @@ -274,7 +274,7 @@ fn build_entry_points_map_recursive( build_entry_points_map_recursive( functions, entry_point, - &functions[&inner_call], + inner_call, brillig_entry_points, explored_functions.clone(), ); From 456f19205ea02e2cbeacdf94eba4adbc52edaac5 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 18 Feb 2025 15:48:01 +0000 Subject: [PATCH 24/28] remove old todo --- compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 70221aee658..7f5796be248 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -198,7 +198,6 @@ fn update_function_calls( /// Returns a map of Brillig entry points to all functions called in that entry point. /// This includes any nested calls as well, as we want to be able to associate /// any Brillig function with the appropriate global allocations. -/// TODO: return a BTreeMap pub(crate) fn get_brillig_entry_points( functions: &BTreeMap, main_id: FunctionId, From ed0fdec5ef84467357bee5d0a523b7e1a788185c Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 18 Feb 2025 16:39:06 +0000 Subject: [PATCH 25/28] pr review, comments, streamline some logic: --- .../brillig/brillig_gen/brillig_globals.rs | 5 +- .../noirc_evaluator/src/ssa/ir/function.rs | 15 ------ .../src/ssa/opt/brillig_entry_points.rs | 48 +++++++++---------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs index e164ee78584..036ff8b746f 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_globals.rs @@ -29,7 +29,7 @@ pub(crate) struct BrilligGlobals { /// Maps an inner call to its Brillig entry point /// This is simply used to simplify fetching global allocations when compiling /// individual Brillig functions. - inner_call_to_entry_point: HashMap>, + inner_call_to_entry_point: HashMap>, /// Final map that associated an entry point with its Brillig global allocations entry_point_globals_map: HashMap, } @@ -116,7 +116,8 @@ impl BrilligGlobals { // Sanity check: We should have guaranteed earlier that an inner call has only a single entry point assert_eq!(entry_points.len(), 1, "{brillig_function_id} has multiple entry points"); - if let Some(globals) = self.entry_point_globals_map.get(&entry_points[0]) { + let entry_point = entry_points.first().expect("ICE: Inner call should have an entry point"); + if let Some(globals) = self.entry_point_globals_map.get(entry_point) { return Some(globals); } diff --git a/compiler/noirc_evaluator/src/ssa/ir/function.rs b/compiler/noirc_evaluator/src/ssa/ir/function.rs index 031d525643d..7db8e317c46 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/function.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/function.rs @@ -109,14 +109,6 @@ impl Function { Self { name: another.name.clone(), id: Some(id), entry_block, dfg } } - /// Creates a new function as a clone of the one passed in without an id. - /// It is expected that the function id is assigned after this method is called. - pub(crate) fn clone_no_id(another: &Function) -> Self { - let dfg = another.dfg.clone(); - let entry_block = another.entry_block; - Self { name: another.name.clone(), id: None, entry_block, dfg } - } - /// Takes the signature (function name & runtime) from a function but does not copy the body. pub(crate) fn clone_signature(id: FunctionId, another: &Function) -> Self { let mut new_function = Function::new(another.name.clone(), id); @@ -137,13 +129,6 @@ impl Function { self.id.expect("FunctionId should be initialized") } - /// Set the id of the function. - /// The id should normally be set on creation of a `Function` - /// and this method should be used with caution. - pub(crate) fn set_id(&mut self, id: FunctionId) { - self.id = Some(id); - } - /// Runtime type of the function. pub(crate) fn runtime(&self) -> RuntimeType { self.dfg.runtime() diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 7f5796be248..dda237956c3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -81,12 +81,8 @@ impl Ssa { } let brillig_entry_points = get_brillig_entry_points(&self.functions, self.main_id); - let entry_points = brillig_entry_points.keys().copied().collect::>(); - let inner_call_to_entry_point = build_inner_call_to_entry_points(&brillig_entry_points); - - let functions_to_clone_map = - build_functions_to_clone(&self.functions, inner_call_to_entry_point, entry_points); + let functions_to_clone_map = build_functions_to_clone(&brillig_entry_points); let calls_to_update = build_calls_to_update(&mut self, functions_to_clone_map); @@ -118,26 +114,25 @@ impl Ssa { } } +/// For every call site, we can determine the entry point for a given callee. +/// Once we know that we can determine which functions are in need of duplication. +/// We duplicate when the following occurs: +/// 1. A function is called from two different entry points +/// 2. An entry point function is called from another entry point fn build_functions_to_clone( - functions: &BTreeMap, - inner_call_to_entry_point: HashMap>, - entry_points: HashSet, -) -> HashMap> { - let mut functions_to_clone_map: HashMap> = - HashMap::default(); + brillig_entry_points: &BTreeMap>, +) -> HashMap> { + let inner_call_to_entry_point = build_inner_call_to_entry_points(brillig_entry_points); + let entry_points = brillig_entry_points.keys().copied().collect::>(); - let mut add_function_to_clone = |entry_point: FunctionId, inner_call: FunctionId| { - let cloned_function = Function::clone_no_id(&functions[&inner_call]); - functions_to_clone_map.entry(entry_point).or_default().push((cloned_function, inner_call)); - }; + let mut functions_to_clone_map: HashMap> = HashMap::default(); for (inner_call, inner_call_entry_points) in inner_call_to_entry_point { - if inner_call_entry_points.len() > 1 { + let should_clone = inner_call_entry_points.len() > 1 || entry_points.contains(&inner_call); + if should_clone { for entry_point in inner_call_entry_points { - add_function_to_clone(entry_point, inner_call); + functions_to_clone_map.entry(entry_point).or_default().push(inner_call); } - } else if entry_points.contains(&inner_call) { - add_function_to_clone(inner_call_entry_points[0], inner_call); } } @@ -149,16 +144,16 @@ fn build_functions_to_clone( /// Returns a map of (entry point, callee function) -> new callee function id. fn build_calls_to_update( ssa: &mut Ssa, - functions_to_clone_map: HashMap>, + functions_to_clone_map: HashMap>, ) -> HashMap<(FunctionId, FunctionId), FunctionId> { let mut calls_to_update: HashMap<(FunctionId, FunctionId), FunctionId> = HashMap::default(); for (entry_point, functions_to_clone) in functions_to_clone_map { - for (mut cloned_function, old_id) in functions_to_clone { + for old_id in functions_to_clone { + let function = ssa.functions[&old_id].clone(); ssa.add_fn(|id| { calls_to_update.insert((entry_point, old_id), id); - cloned_function.set_id(id); - cloned_function + Function::clone_with_id(id, &function) }); } } @@ -284,14 +279,15 @@ fn build_entry_points_map_recursive( /// from which this function is reachable. pub(crate) fn build_inner_call_to_entry_points( brillig_entry_points: &BTreeMap>, -) -> HashMap> { +) -> HashMap> { // Map for fetching the correct entry point globals when compiling any function - let mut inner_call_to_entry_point: HashMap> = HashMap::default(); + let mut inner_call_to_entry_point: HashMap> = + HashMap::default(); // We only need to generate globals for entry points for (entry_point, entry_point_inner_calls) in brillig_entry_points.iter() { for inner_call in entry_point_inner_calls { - inner_call_to_entry_point.entry(*inner_call).or_default().push(*entry_point); + inner_call_to_entry_point.entry(*inner_call).or_default().insert(*entry_point); } } From 1627d0b2a6e08e9a57fa69b917785d9331720ea8 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 18 Feb 2025 16:40:04 +0000 Subject: [PATCH 26/28] bump execution timeout --- .github/benchmark_projects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/benchmark_projects.yml b/.github/benchmark_projects.yml index 2035a18ee44..72957c913ed 100644 --- a/.github/benchmark_projects.yml +++ b/.github/benchmark_projects.yml @@ -74,7 +74,7 @@ projects: num_runs: 1 timeout: 60 compilation-timeout: 100 - execution-timeout: 35 + execution-timeout: 40 compilation-memory-limit: 7000 execution-memory-limit: 1500 rollup-merge: From 8930a900776155eb85a7b4e737e8e9bb8ca04501 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 18 Feb 2025 16:56:23 +0000 Subject: [PATCH 27/28] bump private-kernel-reset compile timeout by 1s --- .github/benchmark_projects.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/benchmark_projects.yml b/.github/benchmark_projects.yml index 72957c913ed..17eb7aa96a6 100644 --- a/.github/benchmark_projects.yml +++ b/.github/benchmark_projects.yml @@ -25,7 +25,7 @@ projects: path: noir-projects/noir-protocol-circuits/crates/private-kernel-reset num_runs: 5 timeout: 250 - compilation-timeout: 7 + compilation-timeout: 8 execution-timeout: 0.35 compilation-memory-limit: 750 execution-memory-limit: 300 From b807d0d7f079b8dc540c366057ff0d4d7c77012b Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 18 Feb 2025 13:10:30 -0500 Subject: [PATCH 28/28] Update compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index dda237956c3..e03f14b2721 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -185,7 +185,7 @@ fn update_function_calls( new_functions_map.insert(*func_id, *new_id); let new_function_value_id = function.dfg.import_function(*new_id); function.dfg[instruction_id] = - Instruction::Call { func: new_function_value_id, arguments: arguments.clone() }; + Instruction::Call { func: new_function_value_id, arguments }; } } }