From cacfc365215bc98943b4f56efbfbaf246fe5a72c Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 15 Sep 2025 20:44:10 +0000 Subject: [PATCH 1/8] some scaffolding to use acir parser for acir gen tests and switch over a couple tests --- .../noirc_evaluator/src/acir/tests/mod.rs | 99 +++++++++++++------ 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index 862eb81143c..fbc79199ab2 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -3,23 +3,25 @@ use acvm::{ acir::{ brillig::{BitSize, HeapVector, IntegerBitSize, MemoryAddress, Opcode as BrilligOpcode}, circuit::{ - ExpressionWidth, Opcode, OpcodeLocation, + ExpressionWidth, Opcode, OpcodeLocation, Program, brillig::BrilligFunctionId, opcodes::{AcirFunctionId, BlackBoxFuncCall}, }, native_types::{Witness, WitnessMap}, }, + assert_circuit_snapshot, blackbox_solver::StubbedBlackBoxSolver, pwg::{ACVM, ACVMStatus}, }; use noirc_errors::Location; -use noirc_frontend::monomorphization::ast::InlineType; +use noirc_frontend::{monomorphization::ast::InlineType, shared::Visibility}; use std::collections::BTreeMap; use crate::{ acir::{BrilligStdlibFunc, acir_context::BrilligStdLib, ssa::codegen_acir}, brillig::{Brillig, BrilligOptions, brillig_ir::artifact::GeneratedBrillig}, ssa::{ + ArtifactsAndWarnings, combine_artifacts, function_builder::FunctionBuilder, interpreter::value::Value, ir::{ @@ -739,6 +741,48 @@ fn check_brillig_calls( ); } +/// Test utility for converting [ACIR gen artifacts][crate::acir::ssa::Artifacts] +/// into the final [ACIR Program][Program] in order to use its parser and human-readable text format. +fn ssa_to_acir_program(src: &str) -> Program { + let ssa = Ssa::from_str(src).unwrap(); + + let arg_size_and_visibilities = ssa + .functions + .iter() + .filter(|(id, function)| { + function.runtime().is_acir() + && (**id == ssa.main_id || function.runtime().is_entry_point()) + }) + .map(|(_, function)| { + // Make all arguments private for the sake of simplicity. + let param_size: u32 = function + .parameters() + .iter() + .map(|param| function.dfg.type_of_value(*param).flattened_size()) + .sum(); + vec![(param_size, Visibility::Private)] + }) + .collect::>(); + + let brillig = ssa.to_brillig(&BrilligOptions::default()); + + let (acir_functions, _brillig_functions, _, _) = ssa + .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); + + let artifacts = + ArtifactsAndWarnings((acir_functions, vec![], vec![], BTreeMap::default()), vec![]); + let program = combine_artifacts( + artifacts, + &arg_size_and_visibilities, + BTreeMap::default(), + BTreeMap::default(), + BTreeMap::default(), + ) + .program; + program +} + #[test] fn unchecked_mul_should_not_have_range_check() { let src = " @@ -748,25 +792,18 @@ fn unchecked_mul_should_not_have_range_check() { return v3 } "; - let ssa = Ssa::from_str(src).unwrap(); - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (mut acir_functions, _brillig_functions, _, _) = ssa - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1); - - let opcodes = acir_functions[0].take_opcodes(); - - for opcode in opcodes { - if let Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) = opcode { - assert!( - input.to_witness().0 <= 1, - "only input witnesses should have range checks: {opcode:?}" - ); - } - } + let program = ssa_to_acir_program(src); + + assert_circuit_snapshot!(program, @r" + func 0 + current witness index : _2 + private parameters indices : [_0, _1] + public parameters indices : [] + return value indices : [_2] + BLACKBOX::RANGE [(_0, 32)] [] + BLACKBOX::RANGE [(_1, 32)] [] + EXPR [ (-1, _0, _1) (1, _2) 0 ] + "); } #[test] @@ -786,18 +823,18 @@ fn does_not_generate_memory_blocks_without_dynamic_accesses() { return } "; - let ssa = Ssa::from_str(src).unwrap(); - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (acir_functions, _brillig_functions, _, _) = ssa - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1); + let program = ssa_to_acir_program(src); // Check that no memory opcodes were emitted. - let main = &acir_functions[0]; - assert!(!main.opcodes().iter().any(|opcode| matches!(opcode, Opcode::MemoryOp { .. }))); + assert_circuit_snapshot!(program, @r" + func 0 + current witness index : _1 + private parameters indices : [_0, _1] + public parameters indices : [] + return value indices : [] + BRILLIG CALL func 0: inputs: [EXPR [ 2 ], [EXPR [ (1, _0) 0 ], EXPR [ (1, _1) 0 ]]], outputs: [] + EXPR [ (1, _0) 0 ] + "); } #[test] From 1a42163b66d2880082a1f51773757cff264c2e59 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 17:35:24 +0000 Subject: [PATCH 2/8] pull out brillig_call and call tests to their own modules and use acir parser --- .../src/acir/tests/brillig_call.rs | 357 ++++++++ .../noirc_evaluator/src/acir/tests/call.rs | 191 +++++ .../noirc_evaluator/src/acir/tests/mod.rs | 808 ++---------------- .../src/ssa/parser/into_ssa.rs | 16 +- 4 files changed, 630 insertions(+), 742 deletions(-) create mode 100644 compiler/noirc_evaluator/src/acir/tests/brillig_call.rs create mode 100644 compiler/noirc_evaluator/src/acir/tests/call.rs diff --git a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs new file mode 100644 index 00000000000..ec58316d9ef --- /dev/null +++ b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs @@ -0,0 +1,357 @@ +use std::collections::BTreeMap; + +use acvm::{ + FieldElement, + acir::circuit::{ExpressionWidth, Opcode, OpcodeLocation, brillig::BrilligFunctionId}, + assert_circuit_snapshot, +}; +use noirc_frontend::monomorphization::ast::InlineType; + +use crate::{ + acir::{ + acir_context::BrilligStdlibFunc, + tests::{build_basic_foo_with_return, ssa_to_acir_program_with_debug_info}, + }, + brillig::BrilligOptions, + ssa::{ + function_builder::FunctionBuilder, + ir::{ + instruction::BinaryOp, + map::Id, + types::{NumericType, Type}, + }, + }, +}; + +// Test that given multiple calls to the same brillig function we generate only one bytecode +// and the appropriate Brillig call opcodes are generated +#[test] +fn multiple_brillig_calls_one_bytecode() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field, v1: Field): + v4 = call f1(v0, v1) -> Field + v5 = call f1(v0, v1) -> Field + v6 = call f1(v0, v1) -> Field + v7 = call f2(v0, v1) -> Field + v8 = call f1(v0, v1) -> Field + v9 = call f2(v0, v1) -> Field + return + } + brillig(inline) fn foo f1 { + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + } + brillig(inline) fn foo f2 { + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + } + "; + let (program, debug) = ssa_to_acir_program_with_debug_info(src); + + let main_debug = &debug[0]; + // We have two normal Brillig functions that were called multiple times. + // We should have a single locations map for each function's debug metadata. + assert_eq!(main_debug.brillig_locations.len(), 2); + assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(0))); + assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(1))); + + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w7 + private parameters: [w0, w1] + public parameters: [] + return values: [] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w2] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w3] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w4] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w5] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w6] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w7] + "); +} + +// Test that given multiple primitive operations that are represented by Brillig directives (e.g. invert/quotient), +// we will only generate one bytecode and the appropriate Brillig call opcodes are generated. +#[test] +fn multiple_brillig_stdlib_calls() { + let src = " + acir(inline) fn main f0 { + b0(v0: u32, v1: u32, v2: u32): + v3 = div v0, v1 + constrain v3 == v2 + v4 = div v1, v2 + constrain v4 == u32 1 + return + }"; + let (program, debug) = ssa_to_acir_program_with_debug_info(src); + // We expect two brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + assert_eq!( + program.unconstrained_functions.len(), + 2, + "Should only have generated two Brillig functions" + ); + assert_eq!( + debug[0].brillig_locations.len(), + 0, + "Brillig stdlib functions do not have location information" + ); + + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w10 + private parameters: [w0, w1, w2] + public parameters: [] + return values: [] + BLACKBOX::RANGE [(w0, 32)] [] + BLACKBOX::RANGE [(w1, 32)] [] + BLACKBOX::RANGE [(w2, 32)] [] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w1) 0 ]], outputs: [w3] + EXPR [ (1, w1, w3) -1 ] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w4, w5] + BLACKBOX::RANGE [(w4, 32)] [] + BLACKBOX::RANGE [(w5, 32)] [] + EXPR [ (1, w1) (-1, w5) (-1, w6) -1 ] + BLACKBOX::RANGE [(w6, 32)] [] + EXPR [ (-1, w1, w4) (1, w0) (-1, w5) 0 ] + EXPR [ (-1, w2) (1, w4) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w2) 0 ]], outputs: [w7] + EXPR [ (1, w2, w7) -1 ] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w1) 0 ], EXPR [ (1, w2) 0 ]], outputs: [w8, w9] + BLACKBOX::RANGE [(w9, 32)] [] + EXPR [ (1, w2) (-1, w9) (-1, w10) -1 ] + BLACKBOX::RANGE [(w10, 32)] [] + EXPR [ (-1, w2, w8) (1, w1) (-1, w9) 0 ] + EXPR [ (1, w8) -1 ] + "); +} + +// Test that given both hardcoded Brillig directives and calls to normal Brillig functions, +// we generate a single bytecode for the directives and a single bytecode for the normal Brillig calls. +#[test] +fn brillig_stdlib_calls_with_regular_brillig_call() { + let src = " + acir(inline) fn main f0 { + b0(v0: u32, v1: u32, v2: u32): + v4 = div v0, v1 + constrain v4 == v2 + v5 = call f1(v0, v1) -> Field + v6 = call f1(v0, v1) -> Field + v7 = div v1, v2 + constrain v7 == u32 1 + return + } + brillig(inline) fn foo f1 { + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + } + "; + let (program, debug) = ssa_to_acir_program_with_debug_info(src); + + // We expect 3 brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + // - Custom brillig function `foo` + assert_eq!( + program.unconstrained_functions.len(), + 3, + "Should only have generated three Brillig functions" + ); + // We have one normal Brillig functions that was called twice. + // We should have a single locations map for each function's debug metadata. + assert_eq!(debug[0].brillig_locations.len(), 1); + assert!(debug[0].brillig_locations.contains_key(&BrilligFunctionId(0))); + + // Brillig stdlib IDs are expected to always come at the end of the Brillig functions list. + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w12 + private parameters: [w0, w1, w2] + public parameters: [] + return values: [] + BLACKBOX::RANGE [(w0, 32)] [] + BLACKBOX::RANGE [(w1, 32)] [] + BLACKBOX::RANGE [(w2, 32)] [] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w1) 0 ]], outputs: [w3] + EXPR [ (1, w1, w3) -1 ] + BRILLIG CALL func 2: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w4, w5] + BLACKBOX::RANGE [(w4, 32)] [] + BLACKBOX::RANGE [(w5, 32)] [] + EXPR [ (1, w1) (-1, w5) (-1, w6) -1 ] + BLACKBOX::RANGE [(w6, 32)] [] + EXPR [ (-1, w1, w4) (1, w0) (-1, w5) 0 ] + EXPR [ (-1, w2) (1, w4) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w7] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w8] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w2) 0 ]], outputs: [w9] + EXPR [ (1, w2, w9) -1 ] + BRILLIG CALL func 2: inputs: [EXPR [ (1, w1) 0 ], EXPR [ (1, w2) 0 ]], outputs: [w10, w11] + BLACKBOX::RANGE [(w11, 32)] [] + EXPR [ (1, w2) (-1, w11) (-1, w12) -1 ] + BLACKBOX::RANGE [(w12, 32)] [] + EXPR [ (-1, w2, w10) (1, w1) (-1, w11) 0 ] + EXPR [ (1, w10) -1 ] + "); +} + +// Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. +#[test] +fn brillig_stdlib_calls_with_multiple_acir_calls() { + // acir(inline) fn main f0 { + // b0(v0: u32, v1: u32, v2: u32): + // v4 = div v0, v1 + // constrain v4 == v2 + // v5 = call f1(v0, v1) + // v6 = call f2(v0, v1) + // v7 = div v1, v2 + // constrain v7 == u32 1 + // return + // } + // brillig fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // acir(fold) fn foo f2 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::unsigned(32)); + let main_v1 = builder.add_parameter(Type::unsigned(32)); + let main_v2 = builder.add_parameter(Type::unsigned(32)); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let bar_id = Id::test_new(2); + let bar = builder.import_function(bar_id); + + // Call a primitive operation that uses Brillig + let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); + builder.insert_constrain(v0_div_v1, main_v2, None); + + // Insert multiple calls to the same Brillig function + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + + // Call the same primitive operation again + let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); + let one = builder.numeric_constant(1u128, NumericType::unsigned(32)); + builder.insert_constrain(v1_div_v2, one, None); + + builder.terminate_with_return(vec![]); + + // Build a Brillig function + build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); + // Build an ACIR function which has the same logic as the Brillig function above + build_basic_foo_with_return(&mut builder, bar_id, false, InlineType::Fold); + + let ssa = builder.finish(); + // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. + let brillig = ssa.to_brillig(&BrilligOptions::default()); + + let (acir_functions, brillig_functions, _, _) = ssa + .generate_entry_point_index() + .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); + // We expect 3 brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + // - Custom brillig function `foo` + assert_eq!(brillig_functions.len(), 3, "Should only have generated three Brillig functions"); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + check_brillig_calls(&acir_functions[0].brillig_stdlib_func_locations, main_opcodes, 1, 4, 2); + + assert_eq!(main_acir.brillig_locations.len(), 1); + assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); + + let foo_acir = &acir_functions[1]; + let foo_opcodes = foo_acir.opcodes(); + check_brillig_calls(&acir_functions[1].brillig_stdlib_func_locations, foo_opcodes, 1, 1, 0); + + assert_eq!(foo_acir.brillig_locations.len(), 0); +} + +fn check_brillig_calls( + brillig_stdlib_function_locations: &BTreeMap, + opcodes: &[Opcode], + num_normal_brillig_functions: u32, + expected_num_stdlib_calls: u32, + expected_num_normal_calls: u32, +) { + // First we check calls to the Brillig stdlib + let mut num_brillig_stdlib_calls = 0; + for (i, (opcode_location, brillig_stdlib_func)) in + brillig_stdlib_function_locations.iter().enumerate() + { + // We can take just modulo 2 to determine the expected ID as we only code generated two Brillig stdlib function + let stdlib_func_index = (i % 2) as u32; + if stdlib_func_index == 0 { + assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Inverse)); + } else { + assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Quotient)); + } + + match opcode_location { + OpcodeLocation::Acir(acir_index) => { + match opcodes[*acir_index] { + Opcode::BrilligCall { id, .. } => { + // Brillig stdlib function calls are only resolved at the end of ACIR generation so their + // IDs are expected to always reference Brillig bytecode at the end of the Brillig functions list. + // We have one normal Brillig call so we add one here to the std lib function's index within the std lib. + let expected_id = stdlib_func_index + num_normal_brillig_functions; + let expected_id = BrilligFunctionId(expected_id); + assert_eq!(id, expected_id, "Expected {expected_id} but got {id}"); + num_brillig_stdlib_calls += 1; + } + _ => panic!("Expected BrilligCall opcode"), + } + } + _ => panic!("Expected OpcodeLocation::Acir"), + } + } + + assert_eq!( + num_brillig_stdlib_calls, expected_num_stdlib_calls, + "Should have {expected_num_stdlib_calls} BrilligCall opcodes to stdlib functions but got {num_brillig_stdlib_calls}" + ); + + // Check the normal Brillig calls + // This check right now expects to only call one Brillig function. + let mut num_normal_brillig_calls = 0; + for (i, opcode) in opcodes.iter().enumerate() { + if let Opcode::BrilligCall { id, .. } = opcode { + if brillig_stdlib_function_locations.get(&OpcodeLocation::Acir(i)).is_some() { + // We should have already checked Brillig stdlib functions and only want to check normal Brillig calls here + continue; + } + // We only generate one normal Brillig call so we should expect a function ID of `0` + let expected_id = BrilligFunctionId(0); + assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); + num_normal_brillig_calls += 1; + } + } + + assert_eq!( + num_normal_brillig_calls, expected_num_normal_calls, + "Should have {expected_num_normal_calls} BrilligCall opcodes to normal Brillig functions but got {num_normal_brillig_calls}" + ); +} diff --git a/compiler/noirc_evaluator/src/acir/tests/call.rs b/compiler/noirc_evaluator/src/acir/tests/call.rs new file mode 100644 index 00000000000..95ac004db06 --- /dev/null +++ b/compiler/noirc_evaluator/src/acir/tests/call.rs @@ -0,0 +1,191 @@ +use acvm::assert_circuit_snapshot; +use noirc_frontend::monomorphization::ast::InlineType; + +use crate::acir::tests::ssa_to_acir_program; + +/// Check that each `InlineType` which prevents inlining functions generates code in the same manner +#[test] +fn basic_calls_fold() { + basic_call_with_outputs_assert(InlineType::Fold); + call_output_as_next_call_input(InlineType::Fold); + basic_nested_call(InlineType::Fold); +} + +#[test] +#[should_panic = "internal error: entered unreachable code: Expected an associated final index for call to acir function f1 with args [Id(0), Id(1)]"] +fn basic_calls_no_predicates() { + basic_call_with_outputs_assert(InlineType::NoPredicates); +} + +#[test] +#[should_panic = "internal error: entered unreachable code: Expected an associated final index for call to acir function f1 with args [Id(0), Id(1)]"] +fn call_output_as_next_call_input_no_predicates() { + call_output_as_next_call_input(InlineType::NoPredicates); +} + +#[test] +#[should_panic = "internal error: entered unreachable code: Expected an associated final index for call to acir function f1 with args [Id(0), Id(1)]"] +fn nested_call_no_predicates() { + basic_nested_call(InlineType::NoPredicates); +} + +#[test] +#[should_panic = "ICE: Got an ACIR function named foo that should have already been inlined"] +fn call_without_inline_attribute() { + basic_call_with_outputs_assert(InlineType::Inline); +} + +fn basic_call_with_outputs_assert(inline_type: InlineType) { + let src = &format!( + " + acir(inline) fn main f0 {{ + b0(v0: Field, v1: Field): + v3 = call f1(v0, v1) -> Field + v4 = call f1(v0, v1) -> Field + constrain v3 == v4 + return + }} + acir({inline_type}) fn foo f1 {{ + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + }} + " + ); + let program = ssa_to_acir_program(src); + + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w3 + private parameters: [w0, w1] + public parameters: [] + return values: [] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w2] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w3] + EXPR [ (1, w2) (-1, w3) 0 ] + + func 1 + current witness: w5 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] + EXPR [ (1, w3, w4) (1, w5) -1 ] + EXPR [ (1, w3, w5) 0 ] + EXPR [ (1, w5) 0 ] + EXPR [ (-1, w0) (1, w2) 0 ] + "); +} + +fn call_output_as_next_call_input(inline_type: InlineType) { + let src = &format!( + " + acir(inline) fn main f0 {{ + b0(v0: Field, v1: Field): + v3 = call f1(v0, v1) -> Field + v4 = call f1(v3, v1) -> Field + constrain v3 == v4 + return + }} + acir({inline_type}) fn foo f1 {{ + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + }} + " + ); + let program = ssa_to_acir_program(src); + // The expected result should look very similar to the `basic_call_with_outputs_assert test except that + // the input witnesses of the `Call` opcodes will be different. The differences can discerned from the output below. + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w3 + private parameters: [w0, w1] + public parameters: [] + return values: [] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w2] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w2, w1], outputs: [w3] + EXPR [ (1, w2) (-1, w3) 0 ] + + func 1 + current witness: w5 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] + EXPR [ (1, w3, w4) (1, w5) -1 ] + EXPR [ (1, w3, w5) 0 ] + EXPR [ (1, w5) 0 ] + EXPR [ (-1, w0) (1, w2) 0 ] + "); +} + +fn basic_nested_call(inline_type: InlineType) { + let src = &format!( + " + acir(inline) fn main f0 {{ + b0(v0: Field, v1: Field): + v3 = call f1(v0, v1) -> Field + v4 = call f1(v0, v1) -> Field + constrain v3 == v4 + return + }} + acir({inline_type}) fn func_with_nested_foo_call f1 {{ + b0(v0: Field, v1: Field): + v3 = add v0, Field 2 + v5 = call f2(v3, v1) -> Field + return v5 + }} + acir({inline_type}) fn foo f2 {{ + b0(v0: Field, v1: Field): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + }} + " + ); + + let program = ssa_to_acir_program(src); + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w3 + private parameters: [w0, w1] + public parameters: [] + return values: [] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w2] + CALL func 1: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w3] + EXPR [ (1, w2) (-1, w3) 0 ] + + func 1 + current witness: w4 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + EXPR [ (1, w0) (-1, w3) 2 ] + CALL func 2: PREDICATE: EXPR [ 1 ] + inputs: [w3, w1], outputs: [w4] + EXPR [ (1, w2) (-1, w4) 0 ] + + func 2 + current witness: w5 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] + EXPR [ (1, w3, w4) (1, w5) -1 ] + EXPR [ (1, w3, w5) 0 ] + EXPR [ (1, w5) 0 ] + EXPR [ (-1, w0) (1, w2) 0 ] + "); +} diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index 98b89da80f0..0149e2eecdb 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -2,23 +2,19 @@ use acvm::{ AcirField, FieldElement, acir::{ brillig::{BitSize, HeapVector, IntegerBitSize, MemoryAddress, Opcode as BrilligOpcode}, - circuit::{ - ExpressionWidth, Opcode, OpcodeLocation, Program, - brillig::BrilligFunctionId, - opcodes::{AcirFunctionId, BlackBoxFuncCall}, - }, + circuit::{ExpressionWidth, Program}, native_types::{Witness, WitnessMap}, }, assert_circuit_snapshot, blackbox_solver::StubbedBlackBoxSolver, pwg::{ACVM, ACVMStatus}, }; -use noirc_errors::Location; +use noirc_errors::{Location, debug_info::DebugInfo}; use noirc_frontend::{monomorphization::ast::InlineType, shared::Visibility}; use std::collections::BTreeMap; use crate::{ - acir::{BrilligStdlibFunc, acir_context::BrilligStdLib, ssa::codegen_acir}, + acir::{acir_context::BrilligStdLib, ssa::codegen_acir}, brillig::{Brillig, BrilligOptions, brillig_ir::artifact::GeneratedBrillig}, ssa::{ ArtifactsAndWarnings, combine_artifacts, @@ -27,7 +23,6 @@ use crate::{ ir::{ function::FunctionId, instruction::BinaryOp, - map::Id, types::{NumericType, Type}, }, ssa_gen::Ssa, @@ -35,6 +30,8 @@ use crate::{ }; use proptest::prelude::*; +mod brillig_call; +mod call; mod intrinsics; fn build_basic_foo_with_return( @@ -68,682 +65,13 @@ fn build_basic_foo_with_return( builder.terminate_with_return(vec![foo_v0]); } -/// Check that each `InlineType` which prevents inlining functions generates code in the same manner -#[test] -fn basic_calls_fold() { - basic_call_with_outputs_assert(InlineType::Fold); - call_output_as_next_call_input(InlineType::Fold); - basic_nested_call(InlineType::Fold); -} - -#[test] -#[should_panic = "internal error: entered unreachable code: Expected an associated final index for call to acir function f1 with args [Id(0), Id(1)]"] -fn basic_calls_no_predicates() { - basic_call_with_outputs_assert(InlineType::NoPredicates); - call_output_as_next_call_input(InlineType::NoPredicates); - basic_nested_call(InlineType::NoPredicates); -} - -#[test] -#[should_panic = "ICE: Got an ACIR function named foo that should have already been inlined"] -fn call_without_inline_attribute() { - basic_call_with_outputs_assert(InlineType::Inline); -} - -fn basic_call_with_outputs_assert(inline_type: InlineType) { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v2 = call f1(v0, v1) - // v3 = call f1(v0, v1) - // constrain v2 == v3 - // return - // } - // acir(fold) fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let main_call1_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - let main_call2_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, false, inline_type); - - let ssa = builder.finish().generate_entry_point_index(); - - let (acir_functions, _, _, _) = ssa - .into_acir(&Brillig::default(), &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - // Expected result: - // main f0 - // GeneratedAcir { - // ... - // opcodes: [ - // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(2)], - // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(3)], - // EXPR [ (1, _2) (-1, _3) 0 ], - // ], - // return_witnesses: [], - // input_witnesses: [ - // Witness( - // 0, - // ), - // Witness( - // 1, - // ), - // ], - // ... - // } - // foo f1 - // GeneratedAcir { - // ... - // opcodes: [ - // Same as opcodes as the expected result of `basic_call_codegen` - // ], - // return_witnesses: [ - // Witness( - // 0, - // ), - // ], - // input_witnesses: [ - // Witness( - // 0, - // ), - // Witness( - // 1, - // ), - // ], - // ... - // }, - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); - - check_call_opcode( - &main_opcodes[0], - AcirFunctionId(1), - vec![Witness(0), Witness(1)], - vec![Witness(2)], - ); - check_call_opcode( - &main_opcodes[1], - AcirFunctionId(1), - vec![Witness(0), Witness(1)], - vec![Witness(3)], - ); - - if let Opcode::AssertZero(expr) = &main_opcodes[2] { - assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); - assert_eq!(expr.linear_combinations[0].1, Witness(2)); - - assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); - assert_eq!(expr.linear_combinations[1].1, Witness(3)); - assert_eq!(expr.q_c, FieldElement::from(0u128)); - } -} - -fn call_output_as_next_call_input(inline_type: InlineType) { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) - // v4 = call f1(v3, v1) - // constrain v3 == v4 - // return - // } - // acir(fold) fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let main_call1_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - let main_call2_results = builder - .insert_call(foo, vec![main_call1_results[0], main_v1], vec![Type::field()]) - .to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, false, inline_type); - - let ssa = builder.finish(); - - let (acir_functions, _, _, _) = ssa - .generate_entry_point_index() - .into_acir(&Brillig::default(), &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - // The expected result should look very similar to the above test expect that the input witnesses of the `Call` - // opcodes will be different. The changes can discerned from the checks below. - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - - check_call_opcode( - &main_opcodes[0], - AcirFunctionId(1), - vec![Witness(0), Witness(1)], - vec![Witness(2)], - ); - // The output of the first call should be the input of the second call - check_call_opcode( - &main_opcodes[1], - AcirFunctionId(1), - vec![Witness(2), Witness(1)], - vec![Witness(3)], - ); -} - -fn basic_nested_call(inline_type: InlineType) { - // SSA for the following Noir program: - // fn main(x: Field, y: pub Field) { - // let z = func_with_nested_foo_call(x, y); - // let z2 = func_with_nested_foo_call(x, y); - // assert(z == z2); - // } - // #[fold] - // fn func_with_nested_foo_call(x: Field, y: Field) -> Field { - // nested_call(x + 2, y) - // } - // #[fold] - // fn foo(x: Field, y: Field) -> Field { - // assert(x != y); - // x - // } - // - // SSA: - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) - // v4 = call f1(v0, v1) - // constrain v3 == v4 - // return - // } - // acir(fold) fn func_with_nested_foo_call f1 { - // b0(v0: Field, v1: Field): - // v3 = add v0, Field 2 - // v5 = call f2(v3, v1) - // return v5 - // } - // acir(fold) fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == Field 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let func_with_nested_foo_call_id = Id::test_new(1); - let func_with_nested_foo_call = builder.import_function(func_with_nested_foo_call_id); - let main_call1_results = builder - .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) - .to_vec(); - let main_call2_results = builder - .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) - .to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - builder.new_function( - "func_with_nested_foo_call".into(), - func_with_nested_foo_call_id, - inline_type, - ); - let func_with_nested_call_v0 = builder.add_parameter(Type::field()); - let func_with_nested_call_v1 = builder.add_parameter(Type::field()); - - let two = builder.field_constant(2u128); - let v0_plus_two = - builder.insert_binary(func_with_nested_call_v0, BinaryOp::Add { unchecked: false }, two); - - let foo_id = Id::test_new(2); - let foo_call = builder.import_function(foo_id); - let foo_call = builder - .insert_call(foo_call, vec![v0_plus_two, func_with_nested_call_v1], vec![Type::field()]) - .to_vec(); - builder.terminate_with_return(vec![foo_call[0]]); - - build_basic_foo_with_return(&mut builder, foo_id, false, inline_type); - - let ssa = builder.finish().generate_entry_point_index(); - - let (acir_functions, _, _, _) = ssa - .into_acir(&Brillig::default(), &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - - // Both of these should call func_with_nested_foo_call f1 - check_call_opcode( - &main_opcodes[0], - AcirFunctionId(1), - vec![Witness(0), Witness(1)], - vec![Witness(2)], - ); - // The output of the first call should be the input of the second call - check_call_opcode( - &main_opcodes[1], - AcirFunctionId(1), - vec![Witness(0), Witness(1)], - vec![Witness(3)], - ); - - let func_with_nested_call_acir = &acir_functions[1]; - let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); - - assert_eq!( - func_with_nested_call_opcodes.len(), - 3, - "Should have an expression and a call to a nested `foo`" - ); - // Should call foo f2 - check_call_opcode( - &func_with_nested_call_opcodes[1], - AcirFunctionId(2), - vec![Witness(3), Witness(1)], - vec![Witness(4)], - ); -} - -fn check_call_opcode( - opcode: &Opcode, - expected_id: AcirFunctionId, - expected_inputs: Vec, - expected_outputs: Vec, -) { - match opcode { - Opcode::Call { id, inputs, outputs, .. } => { - assert_eq!(*id, expected_id, "Main was expected to call {expected_id} but got {}", *id); - for (expected_input, input) in expected_inputs.iter().zip(inputs) { - assert_eq!( - expected_input, input, - "Expected input witness {expected_input:?} but got {input:?}" - ); - } - for (expected_output, output) in expected_outputs.iter().zip(outputs) { - assert_eq!( - expected_output, output, - "Expected output witness {expected_output:?} but got {output:?}" - ); - } - } - _ => panic!("Expected only Call opcode"), - } -} - -// Test that given multiple calls to the same brillig function we generate only one bytecode -// and the appropriate Brillig call opcodes are generated -#[test] -fn multiple_brillig_calls_one_bytecode() { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v4 = call f1(v0, v1) - // v5 = call f1(v0, v1) - // v6 = call f1(v0, v1) - // v7 = call f2(v0, v1) - // v8 = call f1(v0, v1) - // v9 = call f2(v0, v1) - // return - // } - // brillig fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // brillig fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let bar_id = Id::test_new(2); - let bar = builder.import_function(bar_id); - - // Insert multiple calls to the same Brillig function - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); - build_basic_foo_with_return(&mut builder, bar_id, true, InlineType::default()); - - let ssa = builder.finish(); - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (acir_functions, brillig_functions, _, _) = ssa - .generate_entry_point_index() - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); - assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); - - // We should only have `BrilligCall` opcodes in `main` - for (i, opcode) in main_opcodes.iter().enumerate() { - match opcode { - Opcode::BrilligCall { id, .. } => { - let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; - let expected_id = BrilligFunctionId(expected_id); - assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); - } - _ => panic!("Expected only Brillig call opcode"), - } - } - - // We have two normal Brillig functions that was called multiple times. - // We should have a single locations map for each function's debug metadata. - assert_eq!(main_acir.brillig_locations.len(), 2); - assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); - assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(1))); -} - -// Test that given multiple primitive operations that are represented by Brillig directives (e.g. invert/quotient), -// we will only generate one bytecode and the appropriate Brillig call opcodes are generated. -#[test] -fn multiple_brillig_stdlib_calls() { - let src = " - acir(inline) fn main f0 { - b0(v0: u32, v1: u32, v2: u32): - v3 = div v0, v1 - constrain v3 == v2 - v4 = div v1, v2 - constrain v4 == u32 1 - return - }"; - let ssa = Ssa::from_str(src).unwrap(); - - // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any - // Brillig artifacts to the ACIR gen pass. - let (acir_functions, brillig_functions, _, _) = ssa - .generate_entry_point_index() - .into_acir(&Brillig::default(), &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); - // We expect two brillig functions: - // - Quotient (shared between both divisions) - // - Inversion, caused by division-by-zero check (shared between both divisions) - assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - check_brillig_calls(&acir_functions[0].brillig_stdlib_func_locations, main_opcodes, 0, 4, 0); - - assert_eq!(main_acir.brillig_locations.len(), 0); -} - -// Test that given both hardcoded Brillig directives and calls to normal Brillig functions, -// we generate a single bytecode for the directives and a single bytecode for the normal Brillig calls. -#[test] -fn brillig_stdlib_calls_with_regular_brillig_call() { - // acir(inline) fn main f0 { - // b0(v0: u32, v1: u32, v2: u32): - // v4 = div v0, v1 - // constrain v4 == v2 - // v5 = call f1(v0, v1) - // v6 = call f1(v0, v1) - // v7 = div v1, v2 - // constrain v7 == u32 1 - // return - // } - // brillig fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::unsigned(32)); - let main_v1 = builder.add_parameter(Type::unsigned(32)); - let main_v2 = builder.add_parameter(Type::unsigned(32)); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - - // Call a primitive operation that uses Brillig - let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); - builder.insert_constrain(v0_div_v1, main_v2, None); - - // Insert multiple calls to the same Brillig function - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - - // Call the same primitive operation again - let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); - let one = builder.numeric_constant(1u128, NumericType::unsigned(32)); - builder.insert_constrain(v1_div_v2, one, None); - - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); - - let ssa = builder.finish(); - // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (acir_functions, brillig_functions, _, _) = ssa - .generate_entry_point_index() - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); - // We expect 3 brillig functions: - // - Quotient (shared between both divisions) - // - Inversion, caused by division-by-zero check (shared between both divisions) - // - Custom brillig function `foo` - assert_eq!(brillig_functions.len(), 3, "Should only have generated three Brillig functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - check_brillig_calls(&main_acir.brillig_stdlib_func_locations, main_opcodes, 1, 4, 2); - - // We have one normal Brillig functions that was called twice. - // We should have a single locations map for each function's debug metadata. - assert_eq!(main_acir.brillig_locations.len(), 1); - assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); -} - -// Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. -#[test] -fn brillig_stdlib_calls_with_multiple_acir_calls() { - // acir(inline) fn main f0 { - // b0(v0: u32, v1: u32, v2: u32): - // v4 = div v0, v1 - // constrain v4 == v2 - // v5 = call f1(v0, v1) - // v6 = call f2(v0, v1) - // v7 = div v1, v2 - // constrain v7 == u32 1 - // return - // } - // brillig fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // acir(fold) fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::unsigned(32)); - let main_v1 = builder.add_parameter(Type::unsigned(32)); - let main_v2 = builder.add_parameter(Type::unsigned(32)); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let bar_id = Id::test_new(2); - let bar = builder.import_function(bar_id); - - // Call a primitive operation that uses Brillig - let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); - builder.insert_constrain(v0_div_v1, main_v2, None); - - // Insert multiple calls to the same Brillig function - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - - // Call the same primitive operation again - let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); - let one = builder.numeric_constant(1u128, NumericType::unsigned(32)); - builder.insert_constrain(v1_div_v2, one, None); - - builder.terminate_with_return(vec![]); - - // Build a Brillig function - build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); - // Build an ACIR function which has the same logic as the Brillig function above - build_basic_foo_with_return(&mut builder, bar_id, false, InlineType::Fold); - - let ssa = builder.finish(); - // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (acir_functions, brillig_functions, _, _) = ssa - .generate_entry_point_index() - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); - // We expect 3 brillig functions: - // - Quotient (shared between both divisions) - // - Inversion, caused by division-by-zero check (shared between both divisions) - // - Custom brillig function `foo` - assert_eq!(brillig_functions.len(), 3, "Should only have generated three Brillig functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - check_brillig_calls(&acir_functions[0].brillig_stdlib_func_locations, main_opcodes, 1, 4, 2); - - assert_eq!(main_acir.brillig_locations.len(), 1); - assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); - - let foo_acir = &acir_functions[1]; - let foo_opcodes = foo_acir.opcodes(); - check_brillig_calls(&acir_functions[1].brillig_stdlib_func_locations, foo_opcodes, 1, 1, 0); - - assert_eq!(foo_acir.brillig_locations.len(), 0); -} - -fn check_brillig_calls( - brillig_stdlib_function_locations: &BTreeMap, - opcodes: &[Opcode], - num_normal_brillig_functions: u32, - expected_num_stdlib_calls: u32, - expected_num_normal_calls: u32, -) { - // First we check calls to the Brillig stdlib - let mut num_brillig_stdlib_calls = 0; - for (i, (opcode_location, brillig_stdlib_func)) in - brillig_stdlib_function_locations.iter().enumerate() - { - // We can take just modulo 2 to determine the expected ID as we only code generated two Brillig stdlib function - let stdlib_func_index = (i % 2) as u32; - if stdlib_func_index == 0 { - assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Inverse)); - } else { - assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Quotient)); - } - - match opcode_location { - OpcodeLocation::Acir(acir_index) => { - match opcodes[*acir_index] { - Opcode::BrilligCall { id, .. } => { - // Brillig stdlib function calls are only resolved at the end of ACIR generation so their - // IDs are expected to always reference Brillig bytecode at the end of the Brillig functions list. - // We have one normal Brillig call so we add one here to the std lib function's index within the std lib. - let expected_id = stdlib_func_index + num_normal_brillig_functions; - let expected_id = BrilligFunctionId(expected_id); - assert_eq!(id, expected_id, "Expected {expected_id} but got {id}"); - num_brillig_stdlib_calls += 1; - } - _ => panic!("Expected BrilligCall opcode"), - } - } - _ => panic!("Expected OpcodeLocation::Acir"), - } - } - - assert_eq!( - num_brillig_stdlib_calls, expected_num_stdlib_calls, - "Should have {expected_num_stdlib_calls} BrilligCall opcodes to stdlib functions but got {num_brillig_stdlib_calls}" - ); - - // Check the normal Brillig calls - // This check right now expects to only call one Brillig function. - let mut num_normal_brillig_calls = 0; - for (i, opcode) in opcodes.iter().enumerate() { - if let Opcode::BrilligCall { id, .. } = opcode { - if brillig_stdlib_function_locations.get(&OpcodeLocation::Acir(i)).is_some() { - // We should have already checked Brillig stdlib functions and only want to check normal Brillig calls here - continue; - } - // We only generate one normal Brillig call so we should expect a function ID of `0` - let expected_id = BrilligFunctionId(0); - assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); - num_normal_brillig_calls += 1; - } - } - - assert_eq!( - num_normal_brillig_calls, expected_num_normal_calls, - "Should have {expected_num_normal_calls} BrilligCall opcodes to normal Brillig functions but got {num_normal_brillig_calls}" - ); -} - /// Test utility for converting [ACIR gen artifacts][crate::acir::ssa::Artifacts] /// into the final [ACIR Program][Program] in order to use its parser and human-readable text format. fn ssa_to_acir_program(src: &str) -> Program { + ssa_to_acir_program_with_debug_info(src).0 +} + +fn ssa_to_acir_program_with_debug_info(src: &str) -> (Program, Vec) { let ssa = Ssa::from_str(src).unwrap(); let arg_size_and_visibilities = ssa @@ -772,37 +100,39 @@ fn ssa_to_acir_program(src: &str) -> Program { let artifacts = ArtifactsAndWarnings((acir_functions, vec![], vec![], BTreeMap::default()), vec![]); - let program = combine_artifacts( + let program_artifact = combine_artifacts( artifacts, &arg_size_and_visibilities, BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), - ) - .program; - program + ); + let program = program_artifact.program; + let debug = program_artifact.debug; + (program, debug) } #[test] fn unchecked_mul_should_not_have_range_check() { let src = " - acir(inline) fn main f0 { - b0(v0: u32, v1: u32): - v3 = unchecked_mul v0, v1 - return v3 - } - "; + acir(inline) fn main f0 { + b0(v0: u32, v1: u32): + v3 = unchecked_mul v0, v1 + return v3 + } + "; let program = ssa_to_acir_program(src); + // Check that range checks only exist on the function parameters assert_circuit_snapshot!(program, @r" func 0 - current witness index : _2 - private parameters indices : [_0, _1] - public parameters indices : [] - return value indices : [_2] - BLACKBOX::RANGE [(_0, 32)] [] - BLACKBOX::RANGE [(_1, 32)] [] - EXPR [ (-1, _0, _1) (1, _2) 0 ] + current witness: w2 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + BLACKBOX::RANGE [(w0, 32)] [] + BLACKBOX::RANGE [(w1, 32)] [] + EXPR [ (-1, w0, w1) (1, w2) 0 ] "); } @@ -828,12 +158,12 @@ fn does_not_generate_memory_blocks_without_dynamic_accesses() { // Check that no memory opcodes were emitted. assert_circuit_snapshot!(program, @r" func 0 - current witness index : _1 - private parameters indices : [_0, _1] - public parameters indices : [] - return value indices : [] - BRILLIG CALL func 0: inputs: [EXPR [ 2 ], [EXPR [ (1, _0) 0 ], EXPR [ (1, _1) 0 ]]], outputs: [] - EXPR [ (1, _0) 0 ] + current witness: w1 + private parameters: [w0, w1] + public parameters: [] + return values: [] + BRILLIG CALL func 0: inputs: [EXPR [ 2 ], [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]]], outputs: [] + EXPR [ (1, w0) 0 ] "); } @@ -952,7 +282,6 @@ fn do_not_overflow_with_constant_constrain_neq() { return } "#; - let ssa = Ssa::from_str(src).unwrap(); let brillig = ssa.to_brillig(&BrilligOptions::default()); @@ -968,21 +297,47 @@ fn do_not_overflow_with_constant_constrain_neq() { fn derive_pedersen_generators_requires_constant_input() { // derive_pedersen_generators is expected to fail because one of its argument is not a constant. let src = r#" - acir(inline) fn main f0 { - b0(v0: u32, v1: u32): - separator = make_array b"DEFAULT_DOMAIN_SEPARATOR" - v2 = call derive_pedersen_generators(separator, v1) -> [(Field, Field, u1); 1] - return v2 - } - "#; + acir(inline) fn main f0 { + b0(v0: u32, v1: u32): + separator = make_array b"DEFAULT_DOMAIN_SEPARATOR" + v2 = call derive_pedersen_generators(separator, v1) -> [(Field, Field, u1); 1] + return v2 + } + "#; let ssa = Ssa::from_str(src).unwrap(); let brillig = ssa.to_brillig(&BrilligOptions::default()); - let ssa = ssa.fold_constants(); ssa.into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) .expect_err("Should fail with assert constant"); } +#[test] +// Regression for https://github.com/noir-lang/noir/issues/9847 +fn signed_div_overflow() { + // Test that check -128 / -1 overflow for i8 + let src = r#" + acir(inline) predicate_pure fn main f0 { + b0(v1: i8, v2: i8): + v3 = div v1, v2 + return + } + "#; + + let ssa = Ssa::from_str(src).unwrap(); + let inputs = vec![FieldElement::from(128_u128), FieldElement::from(255_u128)]; + let inputs = inputs + .into_iter() + .enumerate() + .map(|(i, f)| (Witness(i as u32), f)) + .collect::>(); + let initial_witness = WitnessMap::from(inputs); + let output = None; + + // acir execution should fail to divide -128 / -1 + let acir_execution_result = execute_ssa(ssa, initial_witness.clone(), output.as_ref()); + assert!(matches!(acir_execution_result, (ACVMStatus::Failure(_), _))); +} + /// Convert the SSA input into ACIR and use ACVM to execute it /// Returns the ACVM execution status and the value of the 'output' witness value, /// unless the provided output is None or the ACVM fails during execution. @@ -1119,33 +474,6 @@ fn test_operators( } } -#[test] -// Regression for https://github.com/noir-lang/noir/issues/9847 -fn signed_div_overflow() { - // Test that check -128 / -1 overflow for i8 - let src = r#" - acir(inline) predicate_pure fn main f0 { - b0(v1: i8, v2: i8): - v3 = div v1, v2 - return - } - "#; - - let ssa = Ssa::from_str(src).unwrap(); - let inputs = vec![FieldElement::from(128_u128), FieldElement::from(255_u128)]; - let inputs = inputs - .into_iter() - .enumerate() - .map(|(i, f)| (Witness(i as u32), f)) - .collect::>(); - let initial_witness = WitnessMap::from(inputs); - let output = None; - - // acir execution should fail to divide -128 / -1 - let acir_execution_result = execute_ssa(ssa, initial_witness.clone(), output.as_ref()); - assert!(matches!(acir_execution_result, (ACVMStatus::Failure(_), _))); -} - proptest! { #[test] fn test_binary_on_field(lhs in 0u128.., rhs in 0u128..) { diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index bfe81b6feef..6e8f9a6f08a 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -4,7 +4,7 @@ use std::{ }; use acvm::acir::circuit::ErrorSelector; -use noirc_errors::call_stack::CallStackId; +use noirc_errors::{Location, call_stack::CallStackId}; use crate::ssa::{ function_builder::FunctionBuilder, @@ -156,10 +156,22 @@ impl Translator { RuntimeType::Brillig(inline_type) => { self.builder.new_brillig_function(external_name, function_id, inline_type); self.builder.set_globals(self.globals_graph.clone()); + + // In our ACIR generation tests we want to make sure that `brillig_locations` in the `GeneratedAcir` was accurately set. + // Thus, we set a dummy location here so that translated instructions have a location associated with them. + let stack = vec![Location::dummy()]; + let call_stack = self + .builder + .current_function + .dfg + .call_stack_data + .get_or_insert_locations(&stack); + self.builder.set_call_stack(call_stack); } } self.builder.set_purities(self.purities.clone()); + self.translate_function_body(function) } @@ -573,7 +585,7 @@ impl Translator { } fn finish(self) -> Ssa { - let mut ssa = self.builder.finish(); + let mut ssa = self.builder.finish().generate_entry_point_index(); // Normalize the IDs so we have a better chance of matching the SSA we parsed // after the step-by-step reconstruction done during translation. This assumes From 6aef08aead43b4f8badff4b8ac32d14314f4ee91 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 18:49:12 +0000 Subject: [PATCH 3/8] cleanup tests --- .../src/acir/tests/brillig_call.rs | 286 ++++++++---------- .../noirc_evaluator/src/acir/tests/mod.rs | 10 +- 2 files changed, 130 insertions(+), 166 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs index ec58316d9ef..5e58538036a 100644 --- a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs +++ b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs @@ -1,17 +1,7 @@ -use std::collections::BTreeMap; - -use acvm::{ - FieldElement, - acir::circuit::{ExpressionWidth, Opcode, OpcodeLocation, brillig::BrilligFunctionId}, - assert_circuit_snapshot, -}; -use noirc_frontend::monomorphization::ast::InlineType; +use acvm::{acir::circuit::brillig::BrilligFunctionId, assert_circuit_snapshot}; use crate::{ - acir::{ - acir_context::BrilligStdlibFunc, - tests::{build_basic_foo_with_return, ssa_to_acir_program_with_debug_info}, - }, + acir::tests::ssa_to_acir_program_with_debug_info, brillig::BrilligOptions, ssa::{ function_builder::FunctionBuilder, @@ -59,6 +49,7 @@ fn multiple_brillig_calls_one_bytecode() { assert_eq!(main_debug.brillig_locations.len(), 2); assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(0))); assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(1))); + println!("{program}"); assert_circuit_snapshot!(program, @r" func 0 @@ -72,6 +63,11 @@ fn multiple_brillig_calls_one_bytecode() { BRILLIG CALL func 1: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w5] BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w6] BRILLIG CALL func 1: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w7] + + unconstrained func 0 + [Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 2 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(3), offset_address: Relative(4) }, Mov { destination: Relative(1), source: Direct(32836) }, Mov { destination: Relative(2), source: Direct(32837) }, Call { location: 14 }, Call { location: 15 }, Mov { destination: Direct(32838), source: Relative(1) }, Const { destination: Relative(2), bit_size: Integer(U32), value: 32838 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 1 }, Stop { return_data: HeapVector { pointer: Relative(2), size: Relative(3) } }, Return, Call { location: 23 }, BinaryFieldOp { destination: Relative(3), op: Equals, lhs: Relative(1), rhs: Relative(2) }, Const { destination: Relative(2), bit_size: Integer(U1), value: 0 }, BinaryIntOp { destination: Relative(4), op: Equals, bit_size: U1, lhs: Relative(3), rhs: Relative(2) }, JumpIf { condition: Relative(4), location: 22 }, Const { destination: Relative(5), bit_size: Integer(U32), value: 0 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Relative(5) } }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 28 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 15764276373176857197 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return] + unconstrained func 1 + [Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 2 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(3), offset_address: Relative(4) }, Mov { destination: Relative(1), source: Direct(32836) }, Mov { destination: Relative(2), source: Direct(32837) }, Call { location: 14 }, Call { location: 15 }, Mov { destination: Direct(32838), source: Relative(1) }, Const { destination: Relative(2), bit_size: Integer(U32), value: 32838 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 1 }, Stop { return_data: HeapVector { pointer: Relative(2), size: Relative(3) } }, Return, Call { location: 23 }, BinaryFieldOp { destination: Relative(3), op: Equals, lhs: Relative(1), rhs: Relative(2) }, Const { destination: Relative(2), bit_size: Integer(U1), value: 0 }, BinaryIntOp { destination: Relative(4), op: Equals, bit_size: U1, lhs: Relative(3), rhs: Relative(2) }, JumpIf { condition: Relative(4), location: 22 }, Const { destination: Relative(5), bit_size: Integer(U32), value: 0 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Relative(5) } }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 28 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 15764276373176857197 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return] "); } @@ -81,12 +77,12 @@ fn multiple_brillig_calls_one_bytecode() { fn multiple_brillig_stdlib_calls() { let src = " acir(inline) fn main f0 { - b0(v0: u32, v1: u32, v2: u32): - v3 = div v0, v1 - constrain v3 == v2 - v4 = div v1, v2 - constrain v4 == u32 1 - return + b0(v0: u32, v1: u32, v2: u32): + v3 = div v0, v1 + constrain v3 == v2 + v4 = div v1, v2 + constrain v4 == u32 1 + return }"; let (program, debug) = ssa_to_acir_program_with_debug_info(src); // We expect two brillig functions: @@ -102,6 +98,7 @@ fn multiple_brillig_stdlib_calls() { 0, "Brillig stdlib functions do not have location information" ); + println!("{program}"); assert_circuit_snapshot!(program, @r" func 0 @@ -129,6 +126,11 @@ fn multiple_brillig_stdlib_calls() { BLACKBOX::RANGE [(w10, 32)] [] EXPR [ (-1, w2, w8) (1, w1) (-1, w9) 0 ] EXPR [ (1, w8) -1 ] + + unconstrained func 0 + [Const { destination: Direct(21), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(20), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(21), offset_address: Direct(20) }, Const { destination: Direct(2), bit_size: Field, value: 0 }, BinaryFieldOp { destination: Direct(3), op: Equals, lhs: Direct(0), rhs: Direct(2) }, JumpIf { condition: Direct(3), location: 8 }, Const { destination: Direct(1), bit_size: Field, value: 1 }, BinaryFieldOp { destination: Direct(0), op: Div, lhs: Direct(1), rhs: Direct(0) }, Stop { return_data: HeapVector { pointer: Direct(20), size: Direct(21) } }] + unconstrained func 1 + [Const { destination: Direct(10), bit_size: Integer(U32), value: 2 }, Const { destination: Direct(11), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(10), offset_address: Direct(11) }, BinaryFieldOp { destination: Direct(2), op: IntegerDiv, lhs: Direct(0), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Mul, lhs: Direct(2), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Sub, lhs: Direct(0), rhs: Direct(1) }, Mov { destination: Direct(0), source: Direct(2) }, Stop { return_data: HeapVector { pointer: Direct(11), size: Direct(10) } }] "); } @@ -141,14 +143,14 @@ fn brillig_stdlib_calls_with_regular_brillig_call() { b0(v0: u32, v1: u32, v2: u32): v4 = div v0, v1 constrain v4 == v2 - v5 = call f1(v0, v1) -> Field - v6 = call f1(v0, v1) -> Field + v5 = call f1(v0, v1) -> u32 + v6 = call f1(v0, v1) -> u32 v7 = div v1, v2 constrain v7 == u32 1 return } brillig(inline) fn foo f1 { - b0(v0: Field, v1: Field): + b0(v0: u32, v1: u32): v2 = eq v0, v1 constrain v2 == u1 0 return v0 @@ -190,7 +192,9 @@ fn brillig_stdlib_calls_with_regular_brillig_call() { EXPR [ (-1, w1, w4) (1, w0) (-1, w5) 0 ] EXPR [ (-1, w2) (1, w4) 0 ] BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w7] + BLACKBOX::RANGE [(w7, 32)] [] BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w8] + BLACKBOX::RANGE [(w8, 32)] [] BRILLIG CALL func 1: inputs: [EXPR [ (1, w2) 0 ]], outputs: [w9] EXPR [ (1, w2, w9) -1 ] BRILLIG CALL func 2: inputs: [EXPR [ (1, w1) 0 ], EXPR [ (1, w2) 0 ]], outputs: [w10, w11] @@ -199,159 +203,117 @@ fn brillig_stdlib_calls_with_regular_brillig_call() { BLACKBOX::RANGE [(w12, 32)] [] EXPR [ (-1, w2, w10) (1, w1) (-1, w11) 0 ] EXPR [ (1, w10) -1 ] + + unconstrained func 0 + [Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 2 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(3), offset_address: Relative(4) }, Cast { destination: Direct(32836), source: Direct(32836), bit_size: Integer(U32) }, Cast { destination: Direct(32837), source: Direct(32837), bit_size: Integer(U32) }, Mov { destination: Relative(1), source: Direct(32836) }, Mov { destination: Relative(2), source: Direct(32837) }, Call { location: 16 }, Call { location: 17 }, Mov { destination: Direct(32838), source: Relative(1) }, Const { destination: Relative(2), bit_size: Integer(U32), value: 32838 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 1 }, Stop { return_data: HeapVector { pointer: Relative(2), size: Relative(3) } }, Return, Call { location: 25 }, BinaryIntOp { destination: Relative(3), op: Equals, bit_size: U32, lhs: Relative(1), rhs: Relative(2) }, Const { destination: Relative(2), bit_size: Integer(U1), value: 0 }, BinaryIntOp { destination: Relative(4), op: Equals, bit_size: U1, lhs: Relative(3), rhs: Relative(2) }, JumpIf { condition: Relative(4), location: 24 }, Const { destination: Relative(5), bit_size: Integer(U32), value: 0 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Relative(5) } }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 30 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 15764276373176857197 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return] + unconstrained func 1 + [Const { destination: Direct(21), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(20), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(21), offset_address: Direct(20) }, Const { destination: Direct(2), bit_size: Field, value: 0 }, BinaryFieldOp { destination: Direct(3), op: Equals, lhs: Direct(0), rhs: Direct(2) }, JumpIf { condition: Direct(3), location: 8 }, Const { destination: Direct(1), bit_size: Field, value: 1 }, BinaryFieldOp { destination: Direct(0), op: Div, lhs: Direct(1), rhs: Direct(0) }, Stop { return_data: HeapVector { pointer: Direct(20), size: Direct(21) } }] + unconstrained func 2 + [Const { destination: Direct(10), bit_size: Integer(U32), value: 2 }, Const { destination: Direct(11), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(10), offset_address: Direct(11) }, BinaryFieldOp { destination: Direct(2), op: IntegerDiv, lhs: Direct(0), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Mul, lhs: Direct(2), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Sub, lhs: Direct(0), rhs: Direct(1) }, Mov { destination: Direct(0), source: Direct(2) }, Stop { return_data: HeapVector { pointer: Direct(11), size: Direct(10) } }] "); } // Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. #[test] fn brillig_stdlib_calls_with_multiple_acir_calls() { - // acir(inline) fn main f0 { - // b0(v0: u32, v1: u32, v2: u32): - // v4 = div v0, v1 - // constrain v4 == v2 - // v5 = call f1(v0, v1) - // v6 = call f2(v0, v1) - // v7 = div v1, v2 - // constrain v7 == u32 1 - // return - // } - // brillig fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // acir(fold) fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::unsigned(32)); - let main_v1 = builder.add_parameter(Type::unsigned(32)); - let main_v2 = builder.add_parameter(Type::unsigned(32)); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let bar_id = Id::test_new(2); - let bar = builder.import_function(bar_id); - - // Call a primitive operation that uses Brillig - let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); - builder.insert_constrain(v0_div_v1, main_v2, None); - - // Insert multiple calls to the same Brillig function - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - - // Call the same primitive operation again - let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); - let one = builder.numeric_constant(1u128, NumericType::unsigned(32)); - builder.insert_constrain(v1_div_v2, one, None); - - builder.terminate_with_return(vec![]); - - // Build a Brillig function - build_basic_foo_with_return(&mut builder, foo_id, true, InlineType::default()); - // Build an ACIR function which has the same logic as the Brillig function above - build_basic_foo_with_return(&mut builder, bar_id, false, InlineType::Fold); - - let ssa = builder.finish(); - // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. - let brillig = ssa.to_brillig(&BrilligOptions::default()); - - let (acir_functions, brillig_functions, _, _) = ssa - .generate_entry_point_index() - .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); + let src = " + acir(inline) fn main f0 { + b0(v0: u32, v1: u32, v2: u32): + v5 = div v0, v1 + constrain v5 == v2 + v6 = call f1(v0, v1) -> u32 + v7 = call f1(v0, v1) -> u32 + v8 = call f2(v0, v1) -> u32 + v9 = div v1, v2 + constrain v9 == u32 1 + return + } + brillig(inline) fn foo f1 { + b0(v0: u32, v1: u32): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + } + acir(fold) fn foo f2 { + b0(v0: u32, v1: u32): + v2 = eq v0, v1 + constrain v2 == u1 0 + return v0 + } + "; + let (program, debug) = ssa_to_acir_program_with_debug_info(src); + println!("{program}"); // We expect 3 brillig functions: // - Quotient (shared between both divisions) // - Inversion, caused by division-by-zero check (shared between both divisions) // - Custom brillig function `foo` - assert_eq!(brillig_functions.len(), 3, "Should only have generated three Brillig functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - check_brillig_calls(&acir_functions[0].brillig_stdlib_func_locations, main_opcodes, 1, 4, 2); - - assert_eq!(main_acir.brillig_locations.len(), 1); - assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); - - let foo_acir = &acir_functions[1]; - let foo_opcodes = foo_acir.opcodes(); - check_brillig_calls(&acir_functions[1].brillig_stdlib_func_locations, foo_opcodes, 1, 1, 0); - - assert_eq!(foo_acir.brillig_locations.len(), 0); -} - -fn check_brillig_calls( - brillig_stdlib_function_locations: &BTreeMap, - opcodes: &[Opcode], - num_normal_brillig_functions: u32, - expected_num_stdlib_calls: u32, - expected_num_normal_calls: u32, -) { - // First we check calls to the Brillig stdlib - let mut num_brillig_stdlib_calls = 0; - for (i, (opcode_location, brillig_stdlib_func)) in - brillig_stdlib_function_locations.iter().enumerate() - { - // We can take just modulo 2 to determine the expected ID as we only code generated two Brillig stdlib function - let stdlib_func_index = (i % 2) as u32; - if stdlib_func_index == 0 { - assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Inverse)); - } else { - assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Quotient)); - } - - match opcode_location { - OpcodeLocation::Acir(acir_index) => { - match opcodes[*acir_index] { - Opcode::BrilligCall { id, .. } => { - // Brillig stdlib function calls are only resolved at the end of ACIR generation so their - // IDs are expected to always reference Brillig bytecode at the end of the Brillig functions list. - // We have one normal Brillig call so we add one here to the std lib function's index within the std lib. - let expected_id = stdlib_func_index + num_normal_brillig_functions; - let expected_id = BrilligFunctionId(expected_id); - assert_eq!(id, expected_id, "Expected {expected_id} but got {id}"); - num_brillig_stdlib_calls += 1; - } - _ => panic!("Expected BrilligCall opcode"), - } - } - _ => panic!("Expected OpcodeLocation::Acir"), - } - } - assert_eq!( - num_brillig_stdlib_calls, expected_num_stdlib_calls, - "Should have {expected_num_stdlib_calls} BrilligCall opcodes to stdlib functions but got {num_brillig_stdlib_calls}" + program.unconstrained_functions.len(), + 3, + "Should only have generated three Brillig functions" ); - // Check the normal Brillig calls - // This check right now expects to only call one Brillig function. - let mut num_normal_brillig_calls = 0; - for (i, opcode) in opcodes.iter().enumerate() { - if let Opcode::BrilligCall { id, .. } = opcode { - if brillig_stdlib_function_locations.get(&OpcodeLocation::Acir(i)).is_some() { - // We should have already checked Brillig stdlib functions and only want to check normal Brillig calls here - continue; - } - // We only generate one normal Brillig call so we should expect a function ID of `0` - let expected_id = BrilligFunctionId(0); - assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); - num_normal_brillig_calls += 1; - } - } + let main_debug = &debug[0]; + assert_eq!(main_debug.brillig_locations.len(), 1); + assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(0))); - assert_eq!( - num_normal_brillig_calls, expected_num_normal_calls, - "Should have {expected_num_normal_calls} BrilligCall opcodes to normal Brillig functions but got {num_normal_brillig_calls}" - ); + let foo_debug = &debug[1]; + assert_eq!(foo_debug.brillig_locations.len(), 0); + + // TODO(https://github.com/noir-lang/noir/issues/9877): Update this snapshot once the linked issue is fixed. + // `CALL func 2` in `func 0` is incorrect. + assert_circuit_snapshot!(program, @r" + func 0 + current witness: w13 + private parameters: [w0, w1, w2] + public parameters: [] + return values: [] + BLACKBOX::RANGE [(w0, 32)] [] + BLACKBOX::RANGE [(w1, 32)] [] + BLACKBOX::RANGE [(w2, 32)] [] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w1) 0 ]], outputs: [w3] + EXPR [ (1, w1, w3) -1 ] + BRILLIG CALL func 2: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w4, w5] + BLACKBOX::RANGE [(w4, 32)] [] + BLACKBOX::RANGE [(w5, 32)] [] + EXPR [ (1, w1) (-1, w5) (-1, w6) -1 ] + BLACKBOX::RANGE [(w6, 32)] [] + EXPR [ (-1, w1, w4) (1, w0) (-1, w5) 0 ] + EXPR [ (-1, w2) (1, w4) 0 ] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w7] + BLACKBOX::RANGE [(w7, 32)] [] + BRILLIG CALL func 0: inputs: [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]], outputs: [w8] + BLACKBOX::RANGE [(w8, 32)] [] + CALL func 2: PREDICATE: EXPR [ 1 ] + inputs: [w0, w1], outputs: [w9] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w2) 0 ]], outputs: [w10] + EXPR [ (1, w2, w10) -1 ] + BRILLIG CALL func 2: inputs: [EXPR [ (1, w1) 0 ], EXPR [ (1, w2) 0 ]], outputs: [w11, w12] + BLACKBOX::RANGE [(w12, 32)] [] + EXPR [ (1, w2) (-1, w12) (-1, w13) -1 ] + BLACKBOX::RANGE [(w13, 32)] [] + EXPR [ (-1, w2, w11) (1, w1) (-1, w12) 0 ] + EXPR [ (1, w11) -1 ] + + func 1 + current witness: w5 + private parameters: [w0, w1] + public parameters: [] + return values: [w2] + BLACKBOX::RANGE [(w0, 32)] [] + BLACKBOX::RANGE [(w1, 32)] [] + EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] + BRILLIG CALL func 1: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] + EXPR [ (1, w3, w4) (1, w5) -1 ] + EXPR [ (1, w3, w5) 0 ] + EXPR [ (1, w5) 0 ] + EXPR [ (-1, w0) (1, w2) 0 ] + + unconstrained func 0 + [Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 2 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(3), offset_address: Relative(4) }, Cast { destination: Direct(32836), source: Direct(32836), bit_size: Integer(U32) }, Cast { destination: Direct(32837), source: Direct(32837), bit_size: Integer(U32) }, Mov { destination: Relative(1), source: Direct(32836) }, Mov { destination: Relative(2), source: Direct(32837) }, Call { location: 16 }, Call { location: 17 }, Mov { destination: Direct(32838), source: Relative(1) }, Const { destination: Relative(2), bit_size: Integer(U32), value: 32838 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 1 }, Stop { return_data: HeapVector { pointer: Relative(2), size: Relative(3) } }, Return, Call { location: 25 }, BinaryIntOp { destination: Relative(3), op: Equals, bit_size: U32, lhs: Relative(1), rhs: Relative(2) }, Const { destination: Relative(2), bit_size: Integer(U1), value: 0 }, BinaryIntOp { destination: Relative(4), op: Equals, bit_size: U1, lhs: Relative(3), rhs: Relative(2) }, JumpIf { condition: Relative(4), location: 24 }, Const { destination: Relative(5), bit_size: Integer(U32), value: 0 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Relative(5) } }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 30 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 15764276373176857197 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return] + unconstrained func 1 + [Const { destination: Direct(21), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(20), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(21), offset_address: Direct(20) }, Const { destination: Direct(2), bit_size: Field, value: 0 }, BinaryFieldOp { destination: Direct(3), op: Equals, lhs: Direct(0), rhs: Direct(2) }, JumpIf { condition: Direct(3), location: 8 }, Const { destination: Direct(1), bit_size: Field, value: 1 }, BinaryFieldOp { destination: Direct(0), op: Div, lhs: Direct(1), rhs: Direct(0) }, Stop { return_data: HeapVector { pointer: Direct(20), size: Direct(21) } }] + unconstrained func 2 + [Const { destination: Direct(10), bit_size: Integer(U32), value: 2 }, Const { destination: Direct(11), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(0), size_address: Direct(10), offset_address: Direct(11) }, BinaryFieldOp { destination: Direct(2), op: IntegerDiv, lhs: Direct(0), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Mul, lhs: Direct(2), rhs: Direct(1) }, BinaryFieldOp { destination: Direct(1), op: Sub, lhs: Direct(0), rhs: Direct(1) }, Mov { destination: Direct(0), source: Direct(2) }, Stop { return_data: HeapVector { pointer: Direct(11), size: Direct(10) } }] + "); } diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index 0149e2eecdb..ad45b0008f2 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -73,7 +73,7 @@ fn ssa_to_acir_program(src: &str) -> Program { fn ssa_to_acir_program_with_debug_info(src: &str) -> (Program, Vec) { let ssa = Ssa::from_str(src).unwrap(); - + println!("{}", ssa); let arg_size_and_visibilities = ssa .functions .iter() @@ -94,12 +94,14 @@ fn ssa_to_acir_program_with_debug_info(src: &str) -> (Program, Vec let brillig = ssa.to_brillig(&BrilligOptions::default()); - let (acir_functions, _brillig_functions, _, _) = ssa + let (acir_functions, brillig_functions, brillig_names, _) = ssa .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) .expect("Should compile manually written SSA into ACIR"); - let artifacts = - ArtifactsAndWarnings((acir_functions, vec![], vec![], BTreeMap::default()), vec![]); + let artifacts = ArtifactsAndWarnings( + (acir_functions, brillig_functions, brillig_names, BTreeMap::default()), + vec![], + ); let program_artifact = combine_artifacts( artifacts, &arg_size_and_visibilities, From c734c9a6f70b3bda6ece7df0606eb025e1be09b8 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 18:54:51 +0000 Subject: [PATCH 4/8] cleanup --- .../src/acir/tests/brillig_call.rs | 5 ++- .../noirc_evaluator/src/acir/tests/mod.rs | 32 ------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs index 5e58538036a..f73ccafbc68 100644 --- a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs +++ b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs @@ -49,7 +49,6 @@ fn multiple_brillig_calls_one_bytecode() { assert_eq!(main_debug.brillig_locations.len(), 2); assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(0))); assert!(main_debug.brillig_locations.contains_key(&BrilligFunctionId(1))); - println!("{program}"); assert_circuit_snapshot!(program, @r" func 0 @@ -98,7 +97,6 @@ fn multiple_brillig_stdlib_calls() { 0, "Brillig stdlib functions do not have location information" ); - println!("{program}"); assert_circuit_snapshot!(program, @r" func 0 @@ -242,7 +240,7 @@ fn brillig_stdlib_calls_with_multiple_acir_calls() { } "; let (program, debug) = ssa_to_acir_program_with_debug_info(src); - println!("{program}"); + // We expect 3 brillig functions: // - Quotient (shared between both divisions) // - Inversion, caused by division-by-zero check (shared between both divisions) @@ -260,6 +258,7 @@ fn brillig_stdlib_calls_with_multiple_acir_calls() { let foo_debug = &debug[1]; assert_eq!(foo_debug.brillig_locations.len(), 0); + // TODO(https://github.com/noir-lang/noir/issues/9877): Update this snapshot once the linked issue is fixed. // `CALL func 2` in `func 0` is incorrect. assert_circuit_snapshot!(program, @r" diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index ad45b0008f2..e96a4b352e7 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -34,37 +34,6 @@ mod brillig_call; mod call; mod intrinsics; -fn build_basic_foo_with_return( - builder: &mut FunctionBuilder, - foo_id: FunctionId, - brillig: bool, - inline_type: InlineType, -) { - // fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - if brillig { - builder.new_brillig_function("foo".into(), foo_id, inline_type); - } else { - builder.new_function("foo".into(), foo_id, inline_type); - } - // Set a call stack for testing whether `brillig_locations` in the `GeneratedAcir` was accurately set. - let stack = vec![Location::dummy(), Location::dummy()]; - let call_stack = builder.current_function.dfg.call_stack_data.get_or_insert_locations(&stack); - builder.set_call_stack(call_stack); - - let foo_v0 = builder.add_parameter(Type::field()); - let foo_v1 = builder.add_parameter(Type::field()); - - let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); - let zero = builder.numeric_constant(0u128, NumericType::unsigned(1)); - builder.insert_constrain(foo_equality_check, zero, None); - builder.terminate_with_return(vec![foo_v0]); -} - /// Test utility for converting [ACIR gen artifacts][crate::acir::ssa::Artifacts] /// into the final [ACIR Program][Program] in order to use its parser and human-readable text format. fn ssa_to_acir_program(src: &str) -> Program { @@ -73,7 +42,6 @@ fn ssa_to_acir_program(src: &str) -> Program { fn ssa_to_acir_program_with_debug_info(src: &str) -> (Program, Vec) { let ssa = Ssa::from_str(src).unwrap(); - println!("{}", ssa); let arg_size_and_visibilities = ssa .functions .iter() From c2515d19fe5ed5e639c513f62b98c7d173ace2ed Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 18:56:16 +0000 Subject: [PATCH 5/8] fmt --- compiler/noirc_evaluator/src/acir/tests/brillig_call.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs index f73ccafbc68..4a8047c4332 100644 --- a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs +++ b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs @@ -258,7 +258,6 @@ fn brillig_stdlib_calls_with_multiple_acir_calls() { let foo_debug = &debug[1]; assert_eq!(foo_debug.brillig_locations.len(), 0); - // TODO(https://github.com/noir-lang/noir/issues/9877): Update this snapshot once the linked issue is fixed. // `CALL func 2` in `func 0` is incorrect. assert_circuit_snapshot!(program, @r" From 2b748ba1e094a22348d32abd7401034971433678 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 15:17:35 -0400 Subject: [PATCH 6/8] Update compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs Co-authored-by: Ary Borenszweig --- compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index 6e8f9a6f08a..159264ad1e3 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -160,12 +160,8 @@ impl Translator { // In our ACIR generation tests we want to make sure that `brillig_locations` in the `GeneratedAcir` was accurately set. // Thus, we set a dummy location here so that translated instructions have a location associated with them. let stack = vec![Location::dummy()]; - let call_stack = self - .builder - .current_function - .dfg - .call_stack_data - .get_or_insert_locations(&stack); + let call_stack_data = &mut self.builder.current_function.dfg.call_stack_data; + let call_stack = call_stack_data.get_or_insert_locations(&stack); self.builder.set_call_stack(call_stack); } } From cdae8d328a2ed746861f70180816b83c0ff3e87d Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 19:21:29 +0000 Subject: [PATCH 7/8] unusd imports --- .../noirc_evaluator/src/acir/tests/brillig_call.rs | 13 +------------ compiler/noirc_evaluator/src/acir/tests/mod.rs | 13 +++---------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs index 4a8047c4332..a9d89570caf 100644 --- a/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs +++ b/compiler/noirc_evaluator/src/acir/tests/brillig_call.rs @@ -1,17 +1,6 @@ use acvm::{acir::circuit::brillig::BrilligFunctionId, assert_circuit_snapshot}; -use crate::{ - acir::tests::ssa_to_acir_program_with_debug_info, - brillig::BrilligOptions, - ssa::{ - function_builder::FunctionBuilder, - ir::{ - instruction::BinaryOp, - map::Id, - types::{NumericType, Type}, - }, - }, -}; +use crate::acir::tests::ssa_to_acir_program_with_debug_info; // Test that given multiple calls to the same brillig function we generate only one bytecode // and the appropriate Brillig call opcodes are generated diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index e96a4b352e7..8b7029a9bea 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -9,22 +9,15 @@ use acvm::{ blackbox_solver::StubbedBlackBoxSolver, pwg::{ACVM, ACVMStatus}, }; -use noirc_errors::{Location, debug_info::DebugInfo}; -use noirc_frontend::{monomorphization::ast::InlineType, shared::Visibility}; +use noirc_errors::debug_info::DebugInfo; +use noirc_frontend::shared::Visibility; use std::collections::BTreeMap; use crate::{ acir::{acir_context::BrilligStdLib, ssa::codegen_acir}, brillig::{Brillig, BrilligOptions, brillig_ir::artifact::GeneratedBrillig}, ssa::{ - ArtifactsAndWarnings, combine_artifacts, - function_builder::FunctionBuilder, - interpreter::value::Value, - ir::{ - function::FunctionId, - instruction::BinaryOp, - types::{NumericType, Type}, - }, + ArtifactsAndWarnings, combine_artifacts, interpreter::value::Value, ir::types::NumericType, ssa_gen::Ssa, }, }; From 4ccc4d1821aea9e07d04fdd2c72d186b504ddba9 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 Sep 2025 20:21:04 +0000 Subject: [PATCH 8/8] fixup tests and do not use brillig in acir calls tests --- .../noirc_evaluator/src/acir/tests/call.rs | 36 ++++++------------- .../noirc_evaluator/src/acir/tests/mod.rs | 4 +++ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/tests/call.rs b/compiler/noirc_evaluator/src/acir/tests/call.rs index 95ac004db06..e8f54842835 100644 --- a/compiler/noirc_evaluator/src/acir/tests/call.rs +++ b/compiler/noirc_evaluator/src/acir/tests/call.rs @@ -47,14 +47,13 @@ fn basic_call_with_outputs_assert(inline_type: InlineType) { }} acir({inline_type}) fn foo f1 {{ b0(v0: Field, v1: Field): - v2 = eq v0, v1 - constrain v2 == u1 0 + constrain v0 == v1 return v0 }} " ); - let program = ssa_to_acir_program(src); + let program = ssa_to_acir_program(src); assert_circuit_snapshot!(program, @r" func 0 current witness: w3 @@ -68,15 +67,11 @@ fn basic_call_with_outputs_assert(inline_type: InlineType) { EXPR [ (1, w2) (-1, w3) 0 ] func 1 - current witness: w5 + current witness: w2 private parameters: [w0, w1] public parameters: [] return values: [w2] - EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] - BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] - EXPR [ (1, w3, w4) (1, w5) -1 ] - EXPR [ (1, w3, w5) 0 ] - EXPR [ (1, w5) 0 ] + EXPR [ (1, w0) (-1, w1) 0 ] EXPR [ (-1, w0) (1, w2) 0 ] "); } @@ -93,12 +88,12 @@ fn call_output_as_next_call_input(inline_type: InlineType) { }} acir({inline_type}) fn foo f1 {{ b0(v0: Field, v1: Field): - v2 = eq v0, v1 - constrain v2 == u1 0 + constrain v0 == v1 return v0 }} " ); + let program = ssa_to_acir_program(src); // The expected result should look very similar to the `basic_call_with_outputs_assert test except that // the input witnesses of the `Call` opcodes will be different. The differences can discerned from the output below. @@ -115,15 +110,11 @@ fn call_output_as_next_call_input(inline_type: InlineType) { EXPR [ (1, w2) (-1, w3) 0 ] func 1 - current witness: w5 + current witness: w2 private parameters: [w0, w1] public parameters: [] return values: [w2] - EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] - BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] - EXPR [ (1, w3, w4) (1, w5) -1 ] - EXPR [ (1, w3, w5) 0 ] - EXPR [ (1, w5) 0 ] + EXPR [ (1, w0) (-1, w1) 0 ] EXPR [ (-1, w0) (1, w2) 0 ] "); } @@ -146,8 +137,7 @@ fn basic_nested_call(inline_type: InlineType) { }} acir({inline_type}) fn foo f2 {{ b0(v0: Field, v1: Field): - v2 = eq v0, v1 - constrain v2 == u1 0 + constrain v0 == v1 return v0 }} " @@ -177,15 +167,11 @@ fn basic_nested_call(inline_type: InlineType) { EXPR [ (1, w2) (-1, w4) 0 ] func 2 - current witness: w5 + current witness: w2 private parameters: [w0, w1] public parameters: [] return values: [w2] - EXPR [ (1, w0) (-1, w1) (-1, w3) 0 ] - BRILLIG CALL func 0: inputs: [EXPR [ (1, w3) 0 ]], outputs: [w4] - EXPR [ (1, w3, w4) (1, w5) -1 ] - EXPR [ (1, w3, w5) 0 ] - EXPR [ (1, w5) 0 ] + EXPR [ (1, w0) (-1, w1) 0 ] EXPR [ (-1, w0) (1, w2) 0 ] "); } diff --git a/compiler/noirc_evaluator/src/acir/tests/mod.rs b/compiler/noirc_evaluator/src/acir/tests/mod.rs index 8b7029a9bea..0b2451c7c0f 100644 --- a/compiler/noirc_evaluator/src/acir/tests/mod.rs +++ b/compiler/noirc_evaluator/src/acir/tests/mod.rs @@ -117,6 +117,7 @@ fn does_not_generate_memory_blocks_without_dynamic_accesses() { } "; let program = ssa_to_acir_program(src); + println!("{program}"); // Check that no memory opcodes were emitted. assert_circuit_snapshot!(program, @r" @@ -127,6 +128,9 @@ fn does_not_generate_memory_blocks_without_dynamic_accesses() { return values: [] BRILLIG CALL func 0: inputs: [EXPR [ 2 ], [EXPR [ (1, w0) 0 ], EXPR [ (1, w1) 0 ]]], outputs: [] EXPR [ (1, w0) 0 ] + + unconstrained func 0 + [Const { destination: Direct(2), bit_size: Integer(U32), value: 1 }, Const { destination: Direct(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Direct(0), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(3), bit_size: Integer(U32), value: 3 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 0 }, CalldataCopy { destination_address: Direct(32836), size_address: Relative(3), offset_address: Relative(4) }, Cast { destination: Direct(32836), source: Direct(32836), bit_size: Integer(U32) }, Mov { destination: Relative(1), source: Direct(32836) }, Const { destination: Relative(2), bit_size: Integer(U32), value: 32837 }, Const { destination: Relative(4), bit_size: Integer(U32), value: 2 }, Const { destination: Relative(6), bit_size: Integer(U32), value: 3 }, BinaryIntOp { destination: Relative(5), op: Add, bit_size: U32, lhs: Relative(4), rhs: Relative(6) }, Mov { destination: Relative(3), source: Direct(1) }, BinaryIntOp { destination: Direct(1), op: Add, bit_size: U32, lhs: Direct(1), rhs: Relative(5) }, IndirectConst { destination_pointer: Relative(3), bit_size: Integer(U32), value: 1 }, BinaryIntOp { destination: Relative(5), op: Add, bit_size: U32, lhs: Relative(3), rhs: Direct(2) }, Store { destination_pointer: Relative(5), source: Relative(4) }, BinaryIntOp { destination: Relative(5), op: Add, bit_size: U32, lhs: Relative(5), rhs: Direct(2) }, Store { destination_pointer: Relative(5), source: Relative(4) }, Const { destination: Relative(6), bit_size: Integer(U32), value: 3 }, BinaryIntOp { destination: Relative(5), op: Add, bit_size: U32, lhs: Relative(3), rhs: Relative(6) }, Mov { destination: Direct(32771), source: Relative(2) }, Mov { destination: Direct(32772), source: Relative(5) }, Mov { destination: Direct(32773), source: Relative(4) }, Call { location: 31 }, Mov { destination: Relative(2), source: Relative(3) }, Call { location: 42 }, Call { location: 43 }, Const { destination: Relative(1), bit_size: Integer(U32), value: 32839 }, Const { destination: Relative(2), bit_size: Integer(U32), value: 0 }, Stop { return_data: HeapVector { pointer: Relative(1), size: Relative(2) } }, BinaryIntOp { destination: Direct(32775), op: Add, bit_size: U32, lhs: Direct(32771), rhs: Direct(32773) }, Mov { destination: Direct(32776), source: Direct(32771) }, Mov { destination: Direct(32777), source: Direct(32772) }, BinaryIntOp { destination: Direct(32778), op: Equals, bit_size: U32, lhs: Direct(32776), rhs: Direct(32775) }, JumpIf { condition: Direct(32778), location: 41 }, Load { destination: Direct(32774), source_pointer: Direct(32776) }, Store { destination_pointer: Direct(32777), source: Direct(32774) }, BinaryIntOp { destination: Direct(32776), op: Add, bit_size: U32, lhs: Direct(32776), rhs: Direct(2) }, BinaryIntOp { destination: Direct(32777), op: Add, bit_size: U32, lhs: Direct(32777), rhs: Direct(2) }, Jump { location: 34 }, Return, Return, Call { location: 45 }, Return, Const { destination: Direct(32772), bit_size: Integer(U32), value: 30720 }, BinaryIntOp { destination: Direct(32771), op: LessThan, bit_size: U32, lhs: Direct(0), rhs: Direct(32772) }, JumpIf { condition: Direct(32771), location: 50 }, IndirectConst { destination_pointer: Direct(1), bit_size: Integer(U64), value: 15764276373176857197 }, Trap { revert_data: HeapVector { pointer: Direct(1), size: Direct(2) } }, Return] "); }