From 9d150d9f4deb0a977244ac2d26327dc2f916b01a Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 29 Mar 2024 16:12:18 +0000 Subject: [PATCH] feat(acvm): Execute multiple circuits (https://github.com/AztecProtocol/aztec-packages/pull/5380) Resolves https://github.com/noir-lang/noir/issues/4428 This is a followup to https://github.com/AztecProtocol/aztec-packages/pull/5341 which does the initial ACIR generation work for multiple ACIR functions. Execution is now done by moving `execute_circuit` to be part of a stateful `ProgramExecutor` that builds a witness stack for every completed `execute_circuit` call. An initial `execute_program` function instantiates the `ProgramExecutor` and starts execution on our `main` entry point circuit. When a `Call` opcode is reached we pause execution and recursively call `execute_circuit`, which then returns the solved witness for that call. We then resolve the outputs of that execution by reading the return witnesses from the inner solved witness. We then push the nested call solved witness onto the witness stack and continue execution of our main ACVM instance. This is quite similar to the process used by foreign calls with Brillig, except it is now done with the main ACVM rather than the contained Brillig VM. This witness stack and program (list of `Circuit` functions) then gets passed to the backend. For now, I have only done an additive `prove_and_verify_ultra_honk_program` to show the process working for the basic non-inlined ACIR programs supplied here. I wanted to leave any WASM exports or ACVM JS changes for a follow-up PR as they are quite a bit of changes on their own. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: Tom French Co-authored-by: jfecher --- .aztec-sync-commit | 2 +- .github/workflows/release.yml | 14 +- acvm-repo/acir/src/circuit/opcodes.rs | 4 +- .../acir/src/native_types/witness_stack.rs | 14 + .../acvm/src/compiler/transformers/mod.rs | 7 +- acvm-repo/acvm/src/pwg/mod.rs | 96 +++- acvm-repo/acvm_js/src/execute.rs | 3 + compiler/noirc_driver/src/lib.rs | 19 +- .../brillig/brillig_gen/brillig_slice_ops.rs | 5 +- .../brillig/brillig_gen/variable_liveness.rs | 6 +- compiler/noirc_evaluator/src/lib.rs | 2 +- compiler/noirc_evaluator/src/ssa.rs | 75 ++- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 24 + .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 520 +++++++++++++++--- .../src/ssa/function_builder/mod.rs | 33 +- compiler/noirc_evaluator/src/ssa/ir/dom.rs | 6 +- .../noirc_evaluator/src/ssa/ir/function.rs | 43 +- .../noirc_evaluator/src/ssa/ir/post_order.rs | 4 +- .../src/ssa/opt/bubble_up_constrains.rs | 4 +- .../src/ssa/opt/constant_folding.rs | 14 +- .../src/ssa/opt/defunctionalize.rs | 4 +- compiler/noirc_evaluator/src/ssa/opt/die.rs | 4 +- .../src/ssa/opt/flatten_cfg.rs | 24 +- .../ssa/opt/flatten_cfg/branch_analysis.rs | 11 +- .../src/ssa/opt/flatten_cfg/value_merger.rs | 4 +- .../noirc_evaluator/src/ssa/opt/inlining.rs | 44 +- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 12 +- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 15 +- .../src/ssa/opt/remove_bit_shifts.rs | 4 +- .../src/ssa/opt/simplify_cfg.rs | 6 +- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 13 +- .../src/ssa/ssa_gen/context.rs | 8 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 10 +- .../src/ssa/ssa_gen/program.rs | 5 +- compiler/noirc_frontend/src/ast/function.rs | 1 + .../src/hir/resolution/resolver.rs | 5 +- .../src/hir/type_check/errors.rs | 2 +- .../noirc_frontend/src/hir/type_check/mod.rs | 41 +- .../noirc_frontend/src/hir_def/function.rs | 4 + compiler/noirc_frontend/src/lexer/lexer.rs | 10 + compiler/noirc_frontend/src/lexer/token.rs | 12 + .../src/monomorphization/ast.rs | 5 + .../src/monomorphization/mod.rs | 61 +- .../github-dependency-resolver.ts | 6 +- compiler/wasm/src/noir/noir-wasm-compiler.ts | 3 +- .../github-dependency-resolver.test.ts | 8 +- .../execution_success/fold_basic/Nargo.toml | 7 + .../execution_success/fold_basic/Prover.toml | 2 + .../execution_success/fold_basic/src/main.nr | 11 + .../fold_basic_nested_call/Nargo.toml | 7 + .../fold_basic_nested_call/Prover.toml | 2 + .../fold_basic_nested_call/src/main.nr | 16 + .../regression_sha256_slice/Nargo.toml | 7 - .../regression_sha256_slice/Prover.toml | 1 - .../regression_sha256_slice/src/main.nr | 12 - tooling/acvm_cli/src/cli/execute_cmd.rs | 21 +- tooling/backend_interface/src/proof_system.rs | 4 +- tooling/bb_abstraction_leaks/build.rs | 2 +- tooling/debugger/src/context.rs | 3 + tooling/nargo/src/errors.rs | 4 +- tooling/nargo/src/ops/execute.rs | 196 +++++-- tooling/nargo/src/ops/mod.rs | 2 +- tooling/nargo/src/ops/test.rs | 14 +- tooling/nargo_cli/src/cli/debug_cmd.rs | 8 +- tooling/nargo_cli/src/cli/execute_cmd.rs | 28 +- tooling/nargo_cli/src/cli/fs/witness.rs | 6 +- tooling/nargo_cli/src/cli/prove_cmd.rs | 11 +- .../noir_js_backend_barretenberg/package.json | 2 +- yarn.lock | 13 +- 69 files changed, 1238 insertions(+), 358 deletions(-) create mode 100644 test_programs/execution_success/fold_basic/Nargo.toml create mode 100644 test_programs/execution_success/fold_basic/Prover.toml create mode 100644 test_programs/execution_success/fold_basic/src/main.nr create mode 100644 test_programs/execution_success/fold_basic_nested_call/Nargo.toml create mode 100644 test_programs/execution_success/fold_basic_nested_call/Prover.toml create mode 100644 test_programs/execution_success/fold_basic_nested_call/src/main.nr delete mode 100644 test_programs/execution_success/regression_sha256_slice/Nargo.toml delete mode 100644 test_programs/execution_success/regression_sha256_slice/Prover.toml delete mode 100644 test_programs/execution_success/regression_sha256_slice/src/main.nr diff --git a/.aztec-sync-commit b/.aztec-sync-commit index ad4ef635e1..540b447693 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -a18288d9b8f3057b9e79362d922da656dacf22a9 +bb719200034e3bc6db09fb56538dadca4203abf4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index badcd3af2d..249d83afec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,15 +79,6 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup - - name: Install wasm-bindgen-cli - uses: taiki-e/install-action@v2 - with: - tool: wasm-bindgen-cli@0.2.86 - - - name: Install wasm-opt - run: | - npm i wasm-opt -g - - name: Query new noir version id: noir-version run: | @@ -116,20 +107,17 @@ jobs: if: ${{ always() }} needs: - - release-please - update-acvm-workspace-package-versions - update-docs env: # We treat any skipped or failing jobs as a failure for the workflow as a whole. - FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} + FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') } steps: - name: Add warning to sticky comment uses: marocchino/sticky-pull-request-comment@v2 with: - # We need to specify the PR on which to make the comment as workflow is triggered by push. - number: ${{ fromJSON(needs.release-please.outputs.release-pr).number }} # delete the comment in case failures have been fixed delete: ${{ !env.FAIL }} message: "The release workflow has not completed successfully. Releasing now will result in a broken release" diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index 064a9d1244..68d28b287e 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -99,8 +99,8 @@ impl std::fmt::Display for Opcode { } Opcode::Call { id, inputs, outputs } => { write!(f, "CALL func {}: ", id)?; - writeln!(f, "inputs: {:?}", inputs)?; - writeln!(f, "outputs: {:?}", outputs) + write!(f, "inputs: {:?}, ", inputs)?; + write!(f, "outputs: {:?}", outputs) } } } diff --git a/acvm-repo/acir/src/native_types/witness_stack.rs b/acvm-repo/acir/src/native_types/witness_stack.rs index 9592d90b01..a9e8f219b3 100644 --- a/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/acvm-repo/acir/src/native_types/witness_stack.rs @@ -32,6 +32,20 @@ pub struct StackItem { pub witness: WitnessMap, } +impl WitnessStack { + pub fn push(&mut self, index: u32, witness: WitnessMap) { + self.stack.push(StackItem { index, witness }); + } + + pub fn peek(&self) -> Option<&StackItem> { + self.stack.last() + } + + pub fn length(&self) -> usize { + self.stack.len() + } +} + impl From for WitnessStack { fn from(witness: WitnessMap) -> Self { let stack = vec![StackItem { index: 0, witness }]; diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index 18f49c154f..1ba261b09a 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -142,7 +142,12 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } - Opcode::Call { .. } => todo!("Handle Call opcodes in the ACVM"), + Opcode::Call { .. } => { + // `Call` does not write values to the `WitnessMap` + // A separate ACIR function should have its own respective `WitnessMap` + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode); + } } } diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index 0fd733a633..3cedcfc039 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -49,6 +49,12 @@ pub enum ACVMStatus { /// /// Once this is done, the ACVM can be restarted to solve the remaining opcodes. RequiresForeignCall(ForeignCallWaitInfo), + + /// The ACVM has encountered a request for an ACIR [call][acir::circuit::Opcode] + /// to execute a separate ACVM instance. The result of the ACIR call must be passd back to the ACVM. + /// + /// Once this is done, the ACVM can be restarted to solve the remaining opcodes. + RequiresAcirCall(AcirCallWaitInfo), } impl std::fmt::Display for ACVMStatus { @@ -58,6 +64,7 @@ impl std::fmt::Display for ACVMStatus { ACVMStatus::InProgress => write!(f, "In progress"), ACVMStatus::Failure(_) => write!(f, "Execution failure"), ACVMStatus::RequiresForeignCall(_) => write!(f, "Waiting on foreign call"), + ACVMStatus::RequiresAcirCall(_) => write!(f, "Waiting on acir call"), } } } @@ -117,6 +124,10 @@ pub enum OpcodeResolutionError { BlackBoxFunctionFailed(BlackBoxFunc, String), #[error("Failed to solve brillig function, reason: {message}")] BrilligFunctionFailed { message: String, call_stack: Vec }, + #[error("Attempted to call `main` with a `Call` opcode")] + AcirMainCallAttempted { opcode_location: ErrorLocation }, + #[error("{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen")] + AcirCallOutputsMismatch { opcode_location: ErrorLocation, results_size: u32, outputs_size: u32 }, } impl From for OpcodeResolutionError { @@ -147,6 +158,13 @@ pub struct ACVM<'a, B: BlackBoxFunctionSolver> { witness_map: WitnessMap, brillig_solver: Option>, + + /// A counter maintained throughout an ACVM process that determines + /// whether the caller has resolved the results of an ACIR [call][Opcode::Call]. + acir_call_counter: usize, + /// Represents the outputs of all ACIR calls during an ACVM process + /// List is appended onto by the caller upon reaching a [ACVMStatus::RequiresAcirCall] + acir_call_results: Vec>, } impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { @@ -161,6 +179,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { instruction_pointer: 0, witness_map: initial_witness, brillig_solver: None, + acir_call_counter: 0, + acir_call_results: Vec::default(), } } @@ -244,6 +264,29 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { self.status(ACVMStatus::InProgress); } + /// Sets the status of the VM to `RequiresAcirCall` + /// Indicating that the VM is now waiting for an ACIR call to be resolved + fn wait_for_acir_call(&mut self, acir_call: AcirCallWaitInfo) -> ACVMStatus { + self.status(ACVMStatus::RequiresAcirCall(acir_call)) + } + + /// Resolves an ACIR call's result (simply a list of fields) using a result calculated by a separate ACVM instance. + /// + /// The current ACVM instance can then be restarted to solve the remaining ACIR opcodes. + pub fn resolve_pending_acir_call(&mut self, call_result: Vec) { + if !matches!(self.status, ACVMStatus::RequiresAcirCall(_)) { + panic!("ACVM is not expecting an ACIR call response as no call was made"); + } + + if self.acir_call_counter < self.acir_call_results.len() { + panic!("No unresolved ACIR calls"); + } + self.acir_call_results.push(call_result); + + // Now that the ACIR call has been resolved then we can resume execution. + self.status(ACVMStatus::InProgress); + } + /// Executes the ACVM's circuit until execution halts. /// /// Execution can halt due to three reasons: @@ -281,7 +324,10 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), res => res.map(|_| ()), }, - Opcode::Call { .. } => todo!("Handle Call opcodes in the ACVM"), + Opcode::Call { .. } => match self.solve_call_opcode() { + Ok(Some(input_values)) => return self.wait_for_acir_call(input_values), + res => res.map(|_| ()), + }, }; self.handle_opcode_resolution(resolution) } @@ -400,6 +446,46 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { self.brillig_solver = Some(solver); self.solve_opcode() } + + pub fn solve_call_opcode(&mut self) -> Result, OpcodeResolutionError> { + let Opcode::Call { id, inputs, outputs } = &self.opcodes[self.instruction_pointer] else { + unreachable!("Not executing a Call opcode"); + }; + if *id == 0 { + return Err(OpcodeResolutionError::AcirMainCallAttempted { + opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir( + self.instruction_pointer(), + )), + }); + } + + if self.acir_call_counter >= self.acir_call_results.len() { + let mut initial_witness = WitnessMap::default(); + for (i, input_witness) in inputs.iter().enumerate() { + let input_value = *witness_to_value(&self.witness_map, *input_witness)?; + initial_witness.insert(Witness(i as u32), input_value); + } + return Ok(Some(AcirCallWaitInfo { id: *id, initial_witness })); + } + + let result_values = &self.acir_call_results[self.acir_call_counter]; + if outputs.len() != result_values.len() { + return Err(OpcodeResolutionError::AcirCallOutputsMismatch { + opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir( + self.instruction_pointer(), + )), + results_size: result_values.len() as u32, + outputs_size: outputs.len() as u32, + }); + } + + for (output_witness, result_value) in outputs.iter().zip(result_values) { + insert_value(output_witness, *result_value, &mut self.witness_map)?; + } + + self.acir_call_counter += 1; + Ok(None) + } } // Returns the concrete value for a particular witness @@ -469,3 +555,11 @@ fn any_witness_from_expression(expr: &Expression) -> Option { Some(expr.linear_combinations[0].1) } } + +#[derive(Debug, Clone, PartialEq)] +pub struct AcirCallWaitInfo { + /// Index in the list of ACIR function's that should be called + pub id: u32, + /// Initial witness for the given circuit to be called + pub initial_witness: WitnessMap, +} diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index ac71a573e6..60d27a489e 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -113,6 +113,9 @@ pub async fn execute_circuit_with_black_box_solver( acvm.resolve_pending_foreign_call(result); } + ACVMStatus::RequiresAcirCall(_) => { + todo!("Handle acir calls in acvm JS"); + } } } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index bc3062e580..e52f702ad7 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -3,13 +3,13 @@ #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] -use acvm::acir::circuit::{ExpressionWidth, Program}; +use acvm::acir::circuit::ExpressionWidth; use clap::Args; use fm::{FileId, FileManager}; use iter_extended::vecmap; use noirc_abi::{AbiParameter, AbiType, ContractEvent}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; -use noirc_evaluator::create_circuit; +use noirc_evaluator::create_program; use noirc_evaluator::errors::RuntimeError; use noirc_frontend::debug::build_debug_crate_file; use noirc_frontend::graph::{CrateId, CrateName}; @@ -478,18 +478,23 @@ pub fn compile_no_check( return Ok(cached_program.expect("cache must exist for hashes to match")); } let visibility = program.return_visibility; - let (circuit, debug, input_witnesses, return_witnesses, warnings) = - create_circuit(program, options.show_ssa, options.show_brillig, options.force_brillig)?; + + let (program, debug, warnings, input_witnesses, return_witnesses) = + create_program(program, options.show_ssa, options.show_brillig, options.force_brillig)?; let abi = abi_gen::gen_abi(context, &main_function, input_witnesses, return_witnesses, visibility); - let file_map = filter_relevant_files(&[debug.clone()], &context.file_manager); + let file_map = filter_relevant_files(&debug, &context.file_manager); Ok(CompiledProgram { hash, // TODO(https://github.com/noir-lang/noir/issues/4428) - program: Program { functions: vec![circuit] }, - debug, + program, + // TODO(https://github.com/noir-lang/noir/issues/4428) + // Debug info is only relevant for errors at execution time which is not yet supported + // The CompileProgram `debug` field is used in multiple places and is better + // left to be updated once execution of multiple ACIR functions is enabled + debug: debug[0].clone(), abi, file_map, noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs index e42b2787f7..b93693d9c7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_slice_ops.rs @@ -373,8 +373,9 @@ mod tests { use crate::ssa::ssa_gen::Ssa; fn create_test_environment() -> (Ssa, FunctionContext, BrilligContext) { - let builder = - FunctionBuilder::new("main".to_string(), Id::test_new(0), RuntimeType::Brillig); + let mut builder = FunctionBuilder::new("main".to_string(), Id::test_new(0)); + builder.set_runtime(RuntimeType::Brillig); + let ssa = builder.finish(); let mut brillig_context = create_context(); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs index 05978c2c6a..c9f1cd1e24 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs @@ -323,7 +323,8 @@ mod test { // } let main_id = Id::test_new(1); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Brillig); + let mut builder = FunctionBuilder::new("main".into(), main_id); + builder.set_runtime(RuntimeType::Brillig); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -425,7 +426,8 @@ mod test { // } let main_id = Id::test_new(1); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Brillig); + let mut builder = FunctionBuilder::new("main".into(), main_id); + builder.set_runtime(RuntimeType::Brillig); let b1 = builder.insert_block(); let b2 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/lib.rs b/compiler/noirc_evaluator/src/lib.rs index 70751d3e54..5437696333 100644 --- a/compiler/noirc_evaluator/src/lib.rs +++ b/compiler/noirc_evaluator/src/lib.rs @@ -11,4 +11,4 @@ pub mod ssa; pub mod brillig; -pub use ssa::create_circuit; +pub use ssa::create_program; diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 808cf7533c..f05a0932f2 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -14,11 +14,11 @@ use crate::{ errors::{RuntimeError, SsaReport}, }; use acvm::acir::{ - circuit::{Circuit, ExpressionWidth, PublicInputs}, + circuit::{Circuit, ExpressionWidth, Program as AcirProgram, PublicInputs}, native_types::Witness, }; -use noirc_errors::debug_info::DebugInfo; +use noirc_errors::debug_info::{DebugFunctions, DebugInfo, DebugTypes, DebugVariables}; use noirc_frontend::{ hir_def::function::FunctionSignature, monomorphization::ast::Program, Visibility, @@ -41,7 +41,7 @@ pub(crate) fn optimize_into_acir( print_ssa_passes: bool, print_brillig_trace: bool, force_brillig_output: bool, -) -> Result { +) -> Result, RuntimeError> { let abi_distinctness = program.return_distinctness; let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); @@ -70,31 +70,82 @@ pub(crate) fn optimize_into_acir( let last_array_uses = ssa.find_last_array_uses(); - ssa.into_acir(brillig, abi_distinctness, &last_array_uses) + ssa.into_acir(&brillig, abi_distinctness, &last_array_uses) } -/// Compiles the [`Program`] into [`ACIR`][acvm::acir::circuit::Circuit]. +/// Compiles the [`Program`] into [`ACIR``][acvm::acir::circuit::Program]. /// /// The output ACIR is is backend-agnostic and so must go through a transformation pass before usage in proof generation. #[allow(clippy::type_complexity)] #[tracing::instrument(level = "trace", skip_all)] -pub fn create_circuit( +pub fn create_program( program: Program, enable_ssa_logging: bool, enable_brillig_logging: bool, force_brillig_output: bool, -) -> Result<(Circuit, DebugInfo, Vec, Vec, Vec), RuntimeError> { +) -> Result<(AcirProgram, Vec, Vec, Vec, Vec), RuntimeError> +{ let debug_variables = program.debug_variables.clone(); let debug_types = program.debug_types.clone(); let debug_functions = program.debug_functions.clone(); - let func_sig = program.main_function_signature.clone(); + + let func_sigs = program.function_signatures.clone(); + let recursive = program.recursive; - let mut generated_acir = optimize_into_acir( + let generated_acirs = optimize_into_acir( program, enable_ssa_logging, enable_brillig_logging, force_brillig_output, )?; + assert_eq!( + generated_acirs.len(), + func_sigs.len(), + "The generated ACIRs should match the supplied function signatures" + ); + + let mut functions = vec![]; + let mut debug_infos = vec![]; + let mut warning_infos = vec![]; + let mut main_input_witnesses = Vec::new(); + let mut main_return_witnesses = Vec::new(); + // For setting up the ABI we need separately specify main's input and return witnesses + let mut is_main = true; + for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { + let (circuit, debug_info, warnings, input_witnesses, return_witnesses) = + convert_generated_acir_into_circuit( + acir, + func_sig, + recursive, + // TODO: get rid of these clones + debug_variables.clone(), + debug_functions.clone(), + debug_types.clone(), + ); + functions.push(circuit); + debug_infos.push(debug_info); + warning_infos.extend(warnings); + if is_main { + // main_input_witness = circuit.re + main_input_witnesses = input_witnesses; + main_return_witnesses = return_witnesses; + } + is_main = false; + } + + let program = AcirProgram { functions }; + + Ok((program, debug_infos, warning_infos, main_input_witnesses, main_return_witnesses)) +} + +fn convert_generated_acir_into_circuit( + mut generated_acir: GeneratedAcir, + func_sig: FunctionSignature, + recursive: bool, + debug_variables: DebugVariables, + debug_functions: DebugFunctions, + debug_types: DebugTypes, +) -> (Circuit, DebugInfo, Vec, Vec, Vec) { let opcodes = generated_acir.take_opcodes(); let current_witness_index = generated_acir.current_witness_index().0; let GeneratedAcir { @@ -106,6 +157,8 @@ pub fn create_circuit( .. } = generated_acir; + let locations = locations.clone(); + let (public_parameter_witnesses, private_parameters) = split_public_and_private_inputs(&func_sig, &input_witnesses); @@ -119,7 +172,7 @@ pub fn create_circuit( private_parameters, public_parameters, return_values, - assert_messages: assert_messages.into_iter().collect(), + assert_messages: assert_messages.clone().into_iter().collect(), recursive, }; @@ -135,7 +188,7 @@ pub fn create_circuit( let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); debug_info.update_acir(transformation_map); - Ok((optimized_circuit, debug_info, input_witnesses, return_witnesses, warnings)) + (optimized_circuit, debug_info, warnings, input_witnesses, return_witnesses) } // Takes each function argument and partitions the circuit's inputs witnesses according to its visibility. diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 7e951cf4e0..bcd62e3b06 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1757,6 +1757,30 @@ impl AcirContext { } Ok(()) } + + pub(crate) fn call_acir_function( + &mut self, + id: u32, + inputs: Vec, + output_count: usize, + ) -> Result, RuntimeError> { + let inputs = self.prepare_inputs_for_black_box_func_call(inputs)?; + let inputs = inputs + .iter() + .flat_map(|input| vecmap(input, |input| input.witness)) + .collect::>(); + let outputs = vecmap(0..output_count, |_| self.acir_ir.next_witness_index()); + + // Convert `Witness` values which are now constrained to be the output of the + // ACIR function call into `AcirVar`s. + // Similar to black box functions, we do not apply range information on the output of the function. + // See issue https://github.com/noir-lang/noir/issues/1439 + let results = + vecmap(&outputs, |witness_index| self.add_data(AcirVarData::Witness(*witness_index))); + + self.acir_ir.push_opcode(Opcode::Call { id, inputs, outputs }); + Ok(results) + } } /// Enum representing the possible values that a diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 8390f480e3..96f959612a 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -25,6 +25,7 @@ use crate::brillig::brillig_ir::artifact::GeneratedBrillig; use crate::brillig::brillig_ir::BrilligContext; use crate::brillig::{brillig_gen::brillig_fn::FunctionContext as BrilligFunctionContext, Brillig}; use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; +use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::native_types::Witness; @@ -178,31 +179,45 @@ impl Ssa { #[tracing::instrument(level = "trace", skip_all)] pub(crate) fn into_acir( self, - brillig: Brillig, + brillig: &Brillig, abi_distinctness: Distinctness, last_array_uses: &HashMap, - ) -> Result { - let context = Context::new(); - let mut generated_acir = context.convert_ssa(self, brillig, last_array_uses)?; + ) -> Result, RuntimeError> { + let mut acirs = Vec::new(); + // TODO: can we parallelise this? + for function in self.functions.values() { + let context = Context::new(); + if let Some(generated_acir) = + context.convert_ssa_function(&self, function, brillig, last_array_uses)? + { + acirs.push(generated_acir); + } + } + // TODO: check whether doing this for a single circuit's return witnesses is correct. + // We probably need it for all foldable circuits, as any circuit being folded is essentially an entry point. However, I do not know how that + // plays a part when we potentially want not inlined functions normally as part of the compiler. + // Also at the moment we specify Distinctness as part of the ABI exclusively rather than the function itself + // so this will need to be updated. + let main_func_acir = &mut acirs[0]; match abi_distinctness { Distinctness::Distinct => { // Create a witness for each return witness we have // to guarantee that the return witnesses are distinct - let distinct_return_witness: Vec<_> = generated_acir + let distinct_return_witness: Vec<_> = main_func_acir .return_witnesses .clone() .into_iter() .map(|return_witness| { - generated_acir + main_func_acir .create_witness_for_expression(&Expression::from(return_witness)) }) .collect(); - generated_acir.return_witnesses = distinct_return_witness; - Ok(generated_acir) + main_func_acir.return_witnesses = distinct_return_witness; + Ok(acirs) } - Distinctness::DuplicationAllowed => Ok(generated_acir), + Distinctness::DuplicationAllowed => Ok(acirs), } } } @@ -225,17 +240,33 @@ impl Context { } } - /// Converts SSA into ACIR - fn convert_ssa( + fn convert_ssa_function( self, - ssa: Ssa, - brillig: Brillig, + ssa: &Ssa, + function: &Function, + brillig: &Brillig, last_array_uses: &HashMap, - ) -> Result { - let main_func = ssa.main(); - match main_func.runtime() { - RuntimeType::Acir => self.convert_acir_main(main_func, &ssa, brillig, last_array_uses), - RuntimeType::Brillig => self.convert_brillig_main(main_func, brillig), + ) -> Result, RuntimeError> { + match function.runtime() { + RuntimeType::Acir(inline_type) => { + match inline_type { + InlineType::Fold => {} + InlineType::Inline => { + if function.id() != ssa.main_id { + panic!("ACIR function should have been inlined earlier if not marked otherwise"); + } + } + } + // We only want to convert entry point functions. This being `main` and those marked with `#[fold]` + Ok(Some(self.convert_acir_main(function, ssa, brillig, last_array_uses)?)) + } + RuntimeType::Brillig => { + if function.id() == ssa.main_id { + Ok(Some(self.convert_brillig_main(function, brillig)?)) + } else { + Ok(None) + } + } } } @@ -243,7 +274,7 @@ impl Context { mut self, main_func: &Function, ssa: &Ssa, - brillig: Brillig, + brillig: &Brillig, last_array_uses: &HashMap, ) -> Result { let dfg = &main_func.dfg; @@ -257,7 +288,7 @@ impl Context { *instruction_id, dfg, ssa, - &brillig, + brillig, last_array_uses, )?); } @@ -269,7 +300,7 @@ impl Context { fn convert_brillig_main( mut self, main_func: &Function, - brillig: Brillig, + brillig: &Brillig, ) -> Result { let dfg = &main_func.dfg; @@ -282,7 +313,7 @@ impl Context { let outputs: Vec = vecmap(main_func.returns(), |result_id| dfg.type_of_value(*result_id).into()); - let code = self.gen_brillig_for(main_func, &brillig)?; + let code = self.gen_brillig_for(main_func, brillig)?; // We specifically do not attempt execution of the brillig code being generated as this can result in it being // replaced with constraints on witnesses to the program outputs. @@ -537,15 +568,40 @@ impl Context { Value::Function(id) => { let func = &ssa.functions[id]; match func.runtime() { - RuntimeType::Acir => unimplemented!( - "expected an intrinsic/brillig call, but found {func:?}. All ACIR methods should be inlined" - ), + RuntimeType::Acir(inline_type) => { + assert!(!matches!(inline_type, InlineType::Inline), "ICE: Got an ACIR function named {} that should have already been inlined", func.name()); + + let inputs = vecmap(arguments, |arg| self.convert_value(*arg, dfg)); + // TODO(https://github.com/noir-lang/noir/issues/4608): handle complex return types from ACIR functions + let output_count = + result_ids.iter().fold(0usize, |sum, result_id| { + sum + dfg.try_get_array_length(*result_id).unwrap_or(1) + }); + + let acir_program_id = ssa + .id_to_index + .get(id) + .expect("ICE: should have an associated final index"); + let output_vars = self.acir_context.call_acir_function( + *acir_program_id, + inputs, + output_count, + )?; + let output_values = + self.convert_vars_to_values(output_vars, dfg, result_ids); + + self.handle_ssa_call_outputs(result_ids, output_values, dfg)?; + } RuntimeType::Brillig => { - // Check that we are not attempting to return a slice from + // Check that we are not attempting to return a slice from // an unconstrained runtime to a constrained runtime for result_id in result_ids { if dfg.type_of_value(*result_id).contains_slice_element() { - return Err(RuntimeError::UnconstrainedSliceReturnToConstrained { call_stack: self.acir_context.get_call_stack() }) + return Err( + RuntimeError::UnconstrainedSliceReturnToConstrained { + call_stack: self.acir_context.get_call_stack(), + }, + ); } } @@ -553,22 +609,23 @@ impl Context { let code = self.gen_brillig_for(func, brillig)?; - let outputs: Vec = vecmap(result_ids, |result_id| dfg.type_of_value(*result_id).into()); + let outputs: Vec = vecmap(result_ids, |result_id| { + dfg.type_of_value(*result_id).into() + }); - let output_values = self.acir_context.brillig(self.current_side_effects_enabled_var, code, inputs, outputs, true, false)?; + let output_values = self.acir_context.brillig( + self.current_side_effects_enabled_var, + code, + inputs, + outputs, + true, + false, + )?; // Compiler sanity check assert_eq!(result_ids.len(), output_values.len(), "ICE: The number of Brillig output values should match the result ids in SSA"); - for result in result_ids.iter().zip(output_values) { - if let AcirValue::Array(_) = &result.1 { - let array_id = dfg.resolve(*result.0); - let block_id = self.block_id(&array_id); - let array_typ = dfg.type_of_value(array_id); - self.initialize_array(block_id, array_typ.flattened_size(), Some(result.1.clone()))?; - } - self.ssa_values.insert(*result.0, result.1); - } + self.handle_ssa_call_outputs(result_ids, output_values, dfg)?; } } } @@ -587,30 +644,7 @@ impl Context { // Issue #1438 causes this check to fail with intrinsics that return 0 // results but the ssa form instead creates 1 unit result value. // assert_eq!(result_ids.len(), outputs.len()); - - for (result, output) in result_ids.iter().zip(outputs) { - match &output { - // We need to make sure we initialize arrays returned from intrinsic calls - // or else they will fail if accessed with a dynamic index - AcirValue::Array(_) => { - let block_id = self.block_id(result); - let array_typ = dfg.type_of_value(*result); - let len = if matches!(array_typ, Type::Array(_, _)) { - array_typ.flattened_size() - } else { - Self::flattened_value_size(&output) - }; - self.initialize_array(block_id, len, Some(output.clone()))?; - } - AcirValue::DynamicArray(_) => { - // Do nothing as a dynamic array returned from a slice intrinsic should already be initialized - } - AcirValue::Var(_, _) => { - // Do nothing - } - } - self.ssa_values.insert(*result, output); - } + self.handle_ssa_call_outputs(result_ids, outputs, dfg)?; } Value::ForeignFunction(_) => { return Err(RuntimeError::UnconstrainedOracleReturnToConstrained { @@ -625,6 +659,32 @@ impl Context { Ok(warnings) } + fn handle_ssa_call_outputs( + &mut self, + result_ids: &[ValueId], + output_values: Vec, + dfg: &DataFlowGraph, + ) -> Result<(), RuntimeError> { + for (result_id, output) in result_ids.iter().zip(output_values) { + if let AcirValue::Array(_) = &output { + let array_id = dfg.resolve(*result_id); + let block_id = self.block_id(&array_id); + let array_typ = dfg.type_of_value(array_id); + let len = if matches!(array_typ, Type::Array(_, _)) { + array_typ.flattened_size() + } else { + Self::flattened_value_size(&output) + }; + self.initialize_array(block_id, len, Some(output.clone()))?; + } + // Do nothing for AcirValue::DynamicArray and AcirValue::Var + // A dynamic array returned from a function call should already be initialized + // and a single variable does not require any extra initialization. + self.ssa_values.insert(*result_id, output); + } + Ok(()) + } + fn gen_brillig_for( &self, func: &Function, @@ -1373,6 +1433,7 @@ impl Context { TerminatorInstruction::Return { return_values, call_stack } => { (return_values, call_stack) } + // TODO(https://github.com/noir-lang/noir/issues/4616): Enable recursion on foldable/non-inlined ACIR functions _ => unreachable!("ICE: Program must have a singular return"), }; @@ -2327,3 +2388,338 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { !types.iter().any(|typ| typ.contains_an_array()) } + +#[cfg(test)] +mod test { + use acvm::{ + acir::{circuit::Opcode, native_types::Witness}, + FieldElement, + }; + + use crate::{ + brillig::Brillig, + ssa::{ + function_builder::FunctionBuilder, + ir::{ + function::{FunctionId, InlineType, RuntimeType}, + instruction::BinaryOp, + map::Id, + types::Type, + }, + }, + }; + use fxhash::FxHashMap as HashMap; + + fn build_basic_foo_with_return(builder: &mut FunctionBuilder, foo_id: FunctionId) { + // acir(fold) fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + builder.new_function("foo".into(), foo_id, InlineType::Fold); + 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.field_constant(0u128); + builder.insert_constrain(foo_equality_check, zero, None); + builder.terminate_with_return(vec![foo_v0]); + } + + #[test] + fn basic_call_with_outputs_assert() { + // 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); + + let ssa = builder.finish(); + + let acir_functions = ssa + .into_acir( + &Brillig::default(), + noirc_frontend::Distinctness::Distinct, + &HashMap::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], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + + match &main_opcodes[2] { + Opcode::AssertZero(expr) => { + 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)); + } + _ => {} + } + } + + #[test] + fn call_output_as_next_call_input() { + // 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); + + let ssa = builder.finish(); + + let acir_functions = ssa + .into_acir( + &Brillig::default(), + noirc_frontend::Distinctness::Distinct, + &HashMap::default(), + ) + .expect("Should compile manually written SSA into ACIR"); + // The expected result should look very similar to the abvoe 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], 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], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); + } + + #[test] + fn basic_nested_call() { + // 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, + InlineType::Fold, + ); + 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, 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); + + let ssa = builder.finish(); + + let acir_functions = ssa + .into_acir( + &Brillig::default(), + noirc_frontend::Distinctness::Distinct, + &HashMap::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], 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], 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(), + 2, + "Should have an expression and a call to a nested `foo`" + ); + // Should call foo f2 + check_call_opcode( + &func_with_nested_call_opcodes[1], + 2, + vec![Witness(2), Witness(1)], + vec![Witness(3)], + ); + } + + fn check_call_opcode( + opcode: &Opcode, + expected_id: u32, + 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 witness {expected_input:?} but got {input:?}" + ); + } + for (expected_output, output) in expected_outputs.iter().zip(outputs) { + assert_eq!( + expected_output, output, + "Expected witness {expected_output:?} but got {output:?}" + ); + } + } + _ => panic!("Expected only Call opcode"), + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 0726b55761..e0e60b737a 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -17,7 +17,7 @@ use super::{ ir::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{ConstrainError, InstructionId, Intrinsic}, }, ssa_gen::Ssa, @@ -42,13 +42,8 @@ impl FunctionBuilder { /// /// This creates the new function internally so there is no need to call .new_function() /// right after constructing a new FunctionBuilder. - pub(crate) fn new( - function_name: String, - function_id: FunctionId, - runtime: RuntimeType, - ) -> Self { - let mut new_function = Function::new(function_name, function_id); - new_function.set_runtime(runtime); + pub(crate) fn new(function_name: String, function_id: FunctionId) -> Self { + let new_function = Function::new(function_name, function_id); Self { current_block: new_function.entry_block(), @@ -58,6 +53,15 @@ impl FunctionBuilder { } } + /// Set the runtime of the initial function that is created internally after constructing + /// the FunctionBuilder. A function's default runtime type is `RuntimeType::Acir(InlineType::Inline)`. + /// This should only be used immediately following construction of a FunctionBuilder + /// and will panic if there are any already finished functions. + pub(crate) fn set_runtime(&mut self, runtime: RuntimeType) { + assert_eq!(self.finished_functions.len(), 0, "Attempted to set runtime on a FunctionBuilder with finished functions. A FunctionBuilder's runtime should only be set on its initial function"); + self.current_function.set_runtime(runtime); + } + /// Finish the current function and create a new function. /// /// A FunctionBuilder can always only work on one function at a time, so care @@ -78,8 +82,13 @@ impl FunctionBuilder { } /// Finish the current function and create a new ACIR function. - pub(crate) fn new_function(&mut self, name: String, function_id: FunctionId) { - self.new_function_with_type(name, function_id, RuntimeType::Acir); + pub(crate) fn new_function( + &mut self, + name: String, + function_id: FunctionId, + inline_type: InlineType, + ) { + self.new_function_with_type(name, function_id, RuntimeType::Acir(inline_type)); } /// Finish the current function and create a new unconstrained function. @@ -491,7 +500,7 @@ mod tests { use acvm::FieldElement; use crate::ssa::ir::{ - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{Endian, Intrinsic}, map::Id, types::Type, @@ -506,7 +515,7 @@ mod tests { // let x = 7; // let bits = x.to_le_bits(8); let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let one = builder.numeric_constant(FieldElement::one(), Type::bool()); let zero = builder.numeric_constant(FieldElement::zero(), Type::bool()); diff --git a/compiler/noirc_evaluator/src/ssa/ir/dom.rs b/compiler/noirc_evaluator/src/ssa/ir/dom.rs index c1df0428c6..bd1481a747 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dom.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dom.rs @@ -252,7 +252,7 @@ mod tests { basic_block::BasicBlockId, dfg::CallStack, dom::DominatorTree, - function::{Function, RuntimeType}, + function::{Function, InlineType, RuntimeType}, instruction::TerminatorInstruction, map::Id, types::Type, @@ -286,7 +286,7 @@ mod tests { // return () // } let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let cond = builder.add_parameter(Type::unsigned(1)); let block1_id = builder.insert_block(); @@ -395,7 +395,7 @@ mod tests { // jump block1() // } let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let block1_id = builder.insert_block(); let block2_id = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/ir/function.rs b/compiler/noirc_evaluator/src/ssa/ir/function.rs index 360f571011..011bee3666 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/function.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/function.rs @@ -12,11 +12,39 @@ use super::value::ValueId; #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub(crate) enum RuntimeType { // A noir function, to be compiled in ACIR and executed by ACVM - Acir, + Acir(InlineType), // Unconstrained function, to be compiled to brillig and executed by the Brillig VM Brillig, } +/// Represents how a RuntimeType::Acir function should be inlined. +/// This type is only relevant for ACIR functions as we do not inline any Brillig functions +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub(crate) enum InlineType { + /// The most basic entry point can expect all its functions to be inlined. + /// All function calls are expected to be inlined into a single ACIR. + #[default] + Inline, + /// Functions marked as foldable will not be inlined and compiled separately into ACIR + Fold, +} + +impl RuntimeType { + /// Returns whether the runtime type represents an entry point. + /// We return `false` for InlineType::Inline on default, which is true + /// in all cases except for main. `main` should be supported with special + /// handling in any places where this function determines logic. + pub(crate) fn is_entry_point(&self) -> bool { + match self { + RuntimeType::Acir(inline_type) => match inline_type { + InlineType::Inline => false, + InlineType::Fold => true, + }, + RuntimeType::Brillig => true, + } + } +} + /// A function holds a list of instructions. /// These instructions are further grouped into Basic blocks /// @@ -47,7 +75,7 @@ impl Function { pub(crate) fn new(name: String, id: FunctionId) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.make_block(); - Self { name, id, entry_block, dfg, runtime: RuntimeType::Acir } + Self { name, id, entry_block, dfg, runtime: RuntimeType::Acir(InlineType::default()) } } /// The name of the function. @@ -129,12 +157,21 @@ impl Function { impl std::fmt::Display for RuntimeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - RuntimeType::Acir => write!(f, "acir"), + RuntimeType::Acir(inline_type) => write!(f, "acir({inline_type})"), RuntimeType::Brillig => write!(f, "brillig"), } } } +impl std::fmt::Display for InlineType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InlineType::Inline => write!(f, "inline"), + InlineType::Fold => write!(f, "fold"), + } + } +} + /// FunctionId is a reference for a function /// /// This Id is how each function refers to other functions diff --git a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs index 3cae86829e..5d743e953a 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/post_order.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/post_order.rs @@ -74,7 +74,7 @@ mod tests { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::{Function, RuntimeType}, + function::{Function, InlineType, RuntimeType}, map::Id, post_order::PostOrder, types::Type, @@ -112,7 +112,7 @@ mod tests { // D, F, E, B, A, (C dropped as unreachable) let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let block_b_id = builder.insert_block(); let block_c_id = builder.insert_block(); let block_d_id = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs index b737c51d14..6d556e86cb 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs @@ -77,7 +77,7 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{Binary, BinaryOp, Instruction}, map::Id, types::Type, @@ -100,7 +100,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::field()); let one = builder.field_constant(1u128); diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 06ae4bf520..8894833196 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -283,7 +283,7 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, map::Id, types::Type, @@ -305,7 +305,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::field()); let one = builder.field_constant(1u128); @@ -361,7 +361,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::unsigned(16)); let v1 = builder.add_parameter(Type::unsigned(16)); @@ -415,7 +415,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::unsigned(16)); let v1 = builder.add_parameter(Type::unsigned(16)); @@ -471,7 +471,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::field()); let one = builder.field_constant(1u128); let v1 = builder.insert_binary(v0, BinaryOp::Add, one); @@ -518,7 +518,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::unsigned(16)); let v1 = builder.insert_cast(v0, Type::unsigned(32)); @@ -562,7 +562,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::bool()); let v1 = builder.add_parameter(Type::bool()); let v2 = builder.add_parameter(Type::bool()); diff --git a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index 1f09a13213..ca6527eb0e 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -13,7 +13,7 @@ use crate::ssa::{ function_builder::FunctionBuilder, ir::{ basic_block::BasicBlockId, - function::{Function, FunctionId, RuntimeType, Signature}, + function::{Function, FunctionId, Signature}, instruction::{BinaryOp, ConstrainError, Instruction}, types::{NumericType, Type}, value::{Value, ValueId}, @@ -304,7 +304,7 @@ fn create_apply_function( ) -> FunctionId { assert!(!function_ids.is_empty()); ssa.add_fn(|id| { - let mut function_builder = FunctionBuilder::new("apply".to_string(), id, RuntimeType::Acir); + let mut function_builder = FunctionBuilder::new("apply".to_string(), id); let target_id = function_builder.add_parameter(Type::field()); let params_ids = vecmap(signature.params, |typ| function_builder.add_parameter(typ)); diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 4c7beff0fb..df89dcb716 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -169,7 +169,7 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{BinaryOp, Intrinsic}, map::Id, types::Type, @@ -199,7 +199,7 @@ mod test { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::field()); let b1 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 46f1e7a276..e731a7952a 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -167,7 +167,9 @@ impl Ssa { /// For more information, see the module-level comment at the top of this file. #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn flatten_cfg(mut self) -> Ssa { - flatten_function_cfg(self.main_mut()); + for function in self.functions.values_mut() { + flatten_function_cfg(function); + } self } } @@ -839,7 +841,7 @@ mod test { function_builder::FunctionBuilder, ir::{ dfg::DataFlowGraph, - function::{Function, RuntimeType}, + function::{Function, InlineType, RuntimeType}, instruction::{BinaryOp, Instruction, Intrinsic, TerminatorInstruction}, map::Id, types::Type, @@ -860,7 +862,7 @@ mod test { // return v1 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -914,7 +916,7 @@ mod test { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -963,7 +965,7 @@ mod test { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -1024,7 +1026,7 @@ mod test { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -1114,7 +1116,7 @@ mod test { // ↘ ↙ // b9 let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -1271,7 +1273,7 @@ mod test { // before the first store to allocate, which loaded an uninitialized value. // In this test we assert the ordering is strictly Allocate then Store then Load. let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -1370,7 +1372,7 @@ mod test { // return // } let main_id = Id::test_new(1); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); builder.insert_block(); // entry @@ -1423,7 +1425,7 @@ mod test { // jmp b3() // } let main_id = Id::test_new(1); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); builder.insert_block(); // b0 let b1 = builder.insert_block(); @@ -1533,7 +1535,7 @@ mod test { // jmp b5() // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs index 59bee00936..adb6d2871e 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs @@ -114,7 +114,12 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{cfg::ControlFlowGraph, function::RuntimeType, map::Id, types::Type}, + ir::{ + cfg::ControlFlowGraph, + function::{InlineType, RuntimeType}, + map::Id, + types::Type, + }, opt::flatten_cfg::branch_analysis::find_branch_ends, }; @@ -134,7 +139,7 @@ mod test { // ↘ ↙ // b9 let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -195,7 +200,7 @@ mod test { // ↘ ↙ // b15 let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs index 0a351148fa..6b923a2e42 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs @@ -234,9 +234,9 @@ impl<'a> ValueMerger<'a> { /// such as with dynamic indexing of non-homogenous slices. fn make_slice_dummy_data(&mut self, typ: &Type) -> ValueId { match typ { - Type::Numeric(numeric_type) => { + Type::Numeric(_) => { let zero = FieldElement::zero(); - self.dfg.make_constant(zero, Type::Numeric(*numeric_type)) + self.dfg.make_constant(zero, Type::field()) } Type::Array(element_types, len) => { let mut array = im::Vector::new(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index aff06af992..7c7698d236 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -11,7 +11,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::{CallStack, InsertInstructionResult}, - function::{Function, FunctionId, RuntimeType}, + function::{Function, FunctionId, InlineType, RuntimeType}, instruction::{Instruction, InstructionId, TerminatorInstruction}, value::{Value, ValueId}, }, @@ -94,12 +94,13 @@ struct PerFunctionContext<'function> { } /// The entry point functions are each function we should inline into - and each function that -/// should be left in the final program. This is usually just `main` but also includes any -/// brillig functions used. +/// should be left in the final program. +/// This is the `main` function, any Acir functions with a [fold inline type][InlineType::Fold], +/// and any brillig functions used. fn get_entry_point_functions(ssa: &Ssa) -> BTreeSet { let functions = ssa.functions.iter(); let mut entry_points = functions - .filter(|(_, function)| function.runtime() == RuntimeType::Brillig) + .filter(|(_, function)| function.runtime().is_entry_point()) .map(|(id, _)| *id) .collect::>(); @@ -115,7 +116,8 @@ impl InlineContext { /// that could not be inlined calling it. fn new(ssa: &Ssa, entry_point: FunctionId) -> InlineContext { let source = &ssa.functions[&entry_point]; - let builder = FunctionBuilder::new(source.name().to_owned(), entry_point, source.runtime()); + let mut builder = FunctionBuilder::new(source.name().to_owned(), entry_point); + builder.set_runtime(source.runtime()); Self { builder, recursion_level: 0, entry_point, call_stack: CallStack::new() } } @@ -350,8 +352,12 @@ impl<'function> PerFunctionContext<'function> { match &self.source_function.dfg[*id] { Instruction::Call { func, arguments } => match self.get_function(*func) { Some(function) => match ssa.functions[&function].runtime() { - RuntimeType::Acir => self.inline_function(ssa, *id, function, arguments), - RuntimeType::Brillig => self.push_instruction(*id), + RuntimeType::Acir(InlineType::Inline) => { + self.inline_function(ssa, *id, function, arguments); + } + RuntimeType::Acir(InlineType::Fold) | RuntimeType::Brillig => { + self.push_instruction(*id); + } }, None => self.push_instruction(*id), }, @@ -517,7 +523,7 @@ mod test { function_builder::FunctionBuilder, ir::{ basic_block::BasicBlockId, - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{BinaryOp, Intrinsic, TerminatorInstruction}, map::Id, types::Type, @@ -536,14 +542,14 @@ mod test { // return 72 // } let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("foo".into(), foo_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("foo".into(), foo_id); let bar_id = Id::test_new(1); let bar = builder.import_function(bar_id); let results = builder.insert_call(bar, Vec::new(), vec![Type::field()]).to_vec(); builder.terminate_with_return(results); - builder.new_function("bar".into(), bar_id); + builder.new_function("bar".into(), bar_id, InlineType::default()); let expected_return = 72u128; let seventy_two = builder.field_constant(expected_return); builder.terminate_with_return(vec![seventy_two]); @@ -585,7 +591,7 @@ mod test { let id2_id = Id::test_new(3); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let main_v0 = builder.add_parameter(Type::field()); let main_f1 = builder.import_function(square_id); @@ -598,18 +604,18 @@ mod test { builder.terminate_with_return(vec![main_v16]); // Compiling square f1 - builder.new_function("square".into(), square_id); + builder.new_function("square".into(), square_id, InlineType::default()); let square_v0 = builder.add_parameter(Type::field()); let square_v2 = builder.insert_binary(square_v0, BinaryOp::Mul, square_v0); builder.terminate_with_return(vec![square_v2]); // Compiling id1 f2 - builder.new_function("id1".into(), id1_id); + builder.new_function("id1".into(), id1_id, InlineType::default()); let id1_v0 = builder.add_parameter(Type::Function); builder.terminate_with_return(vec![id1_v0]); // Compiling id2 f3 - builder.new_function("id2".into(), id2_id); + builder.new_function("id2".into(), id2_id, InlineType::default()); let id2_v0 = builder.add_parameter(Type::Function); builder.terminate_with_return(vec![id2_v0]); @@ -641,7 +647,7 @@ mod test { // return v4 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let factorial_id = Id::test_new(1); let factorial = builder.import_function(factorial_id); @@ -650,7 +656,7 @@ mod test { let results = builder.insert_call(factorial, vec![five], vec![Type::field()]).to_vec(); builder.terminate_with_return(results); - builder.new_function("factorial".into(), factorial_id); + builder.new_function("factorial".into(), factorial_id, InlineType::default()); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -741,7 +747,7 @@ mod test { // jmp b3(Field 2) // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let main_cond = builder.add_parameter(Type::bool()); let inner1_id = Id::test_new(1); @@ -751,14 +757,14 @@ mod test { builder.insert_call(assert_constant, vec![main_v2], vec![]); builder.terminate_with_return(vec![]); - builder.new_function("inner1".into(), inner1_id); + builder.new_function("inner1".into(), inner1_id, InlineType::default()); let inner1_cond = builder.add_parameter(Type::bool()); let inner2_id = Id::test_new(2); let inner2 = builder.import_function(inner2_id); let inner1_v2 = builder.insert_call(inner2, vec![inner1_cond], vec![Type::field()])[0]; builder.terminate_with_return(vec![inner1_v2]); - builder.new_function("inner2".into(), inner2_id); + builder.new_function("inner2".into(), inner2_id, InlineType::default()); let inner2_cond = builder.add_parameter(Type::bool()); let then_block = builder.insert_block(); let else_block = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 0a49ca4ecc..f1a38585bd 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -414,7 +414,7 @@ mod tests { ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{BinaryOp, Instruction, Intrinsic, TerminatorInstruction}, map::Id, types::Type, @@ -433,7 +433,7 @@ mod tests { // } let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let v0 = builder.insert_allocate(Type::Array(Rc::new(vec![Type::field()]), 2)); let one = builder.field_constant(FieldElement::one()); let two = builder.field_constant(FieldElement::one()); @@ -474,7 +474,7 @@ mod tests { // } let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let v0 = builder.insert_allocate(Type::field()); let one = builder.field_constant(FieldElement::one()); builder.insert_store(v0, one); @@ -508,7 +508,7 @@ mod tests { // } let func_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("func".into(), func_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("func".into(), func_id); let v0 = builder.insert_allocate(Type::field()); let const_one = builder.field_constant(FieldElement::one()); builder.insert_store(v0, const_one); @@ -567,7 +567,7 @@ mod tests { // return v2, v3, v4 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.insert_allocate(Type::field()); @@ -647,7 +647,7 @@ mod tests { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.insert_allocate(Type::field()); diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 4766bc3e8d..7b5196f200 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -166,8 +166,12 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - basic_block::BasicBlockId, dfg::DataFlowGraph, function::RuntimeType, - instruction::Instruction, map::Id, types::Type, + basic_block::BasicBlockId, + dfg::DataFlowGraph, + function::{InlineType, RuntimeType}, + instruction::Instruction, + map::Id, + types::Type, }, }; @@ -206,7 +210,8 @@ mod test { // return [v0] // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("foo".into(), main_id, RuntimeType::Brillig); + let mut builder = FunctionBuilder::new("foo".into(), main_id); + builder.set_runtime(RuntimeType::Brillig); let inner_array_type = Type::Array(Rc::new(vec![Type::field()]), 2); let v0 = builder.add_parameter(inner_array_type.clone()); @@ -245,7 +250,7 @@ mod test { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("mutator".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("mutator".into(), main_id); let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); let v0 = builder.add_parameter(array_type.clone()); @@ -294,7 +299,7 @@ mod test { // return // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("mutator2".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("mutator2".into(), main_id); let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); let reference_type = Type::Reference(Rc::new(array_type.clone())); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index a71a42d575..1eb1e20d41 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -20,7 +20,9 @@ impl Ssa { /// See [`constant_folding`][self] module for more information. #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_bit_shifts(mut self) -> Ssa { - remove_bit_shifts(self.main_mut()); + for function in self.functions.values_mut() { + remove_bit_shifts(function); + } self } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index a31def8fd9..b9675d99c9 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -154,7 +154,7 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::RuntimeType, + function::{InlineType, RuntimeType}, instruction::{BinaryOp, TerminatorInstruction}, map::Id, types::Type, @@ -172,7 +172,7 @@ mod test { // return v1 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -228,7 +228,7 @@ mod test { // return Field 2 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.add_parameter(Type::bool()); let b1 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 645adb6b35..92ec1e8f1b 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -48,7 +48,7 @@ impl Ssa { // This check is always true with the addition of the above guard, but I'm // keeping it in case the guard on brillig functions is ever removed. - let abort_on_error = function.runtime() == RuntimeType::Acir; + let abort_on_error = matches!(function.runtime(), RuntimeType::Acir(_)); find_all_loops(function).unroll_each_loop(function, abort_on_error)?; } Ok(self) @@ -464,7 +464,12 @@ impl<'f> LoopIteration<'f> { mod tests { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{function::RuntimeType, instruction::BinaryOp, map::Id, types::Type}, + ir::{ + function::{InlineType, RuntimeType}, + instruction::BinaryOp, + map::Id, + types::Type, + }, }; #[test] @@ -503,7 +508,7 @@ mod tests { let main_id = Id::test_new(0); // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); @@ -605,7 +610,7 @@ mod tests { // return Field 0 // } let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); + let mut builder = FunctionBuilder::new("main".into(), main_id); let b1 = builder.insert_block(); let b2 = builder.insert_block(); diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index a9a707ca8e..569e543ce4 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -12,8 +12,8 @@ use crate::errors::RuntimeError; use crate::ssa::function_builder::FunctionBuilder; use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::dfg::DataFlowGraph; -use crate::ssa::ir::function::FunctionId as IrFunctionId; use crate::ssa::ir::function::{Function, RuntimeType}; +use crate::ssa::ir::function::{FunctionId as IrFunctionId, InlineType}; use crate::ssa::ir::instruction::BinaryOp; use crate::ssa::ir::instruction::Instruction; use crate::ssa::ir::map::AtomicCounter; @@ -108,7 +108,8 @@ impl<'a> FunctionContext<'a> { .expect("No function in queue for the FunctionContext to compile") .1; - let builder = FunctionBuilder::new(function_name, function_id, runtime); + let mut builder = FunctionBuilder::new(function_name, function_id); + builder.set_runtime(runtime); let definitions = HashMap::default(); let mut this = Self { definitions, builder, shared_context, loops: Vec::new() }; this.add_parameters_to_scope(parameters); @@ -125,7 +126,8 @@ impl<'a> FunctionContext<'a> { if func.unconstrained { self.builder.new_brillig_function(func.name.clone(), id); } else { - self.builder.new_function(func.name.clone(), id); + let inline_type = if func.should_fold { InlineType::Fold } else { InlineType::Inline }; + self.builder.new_function(func.name.clone(), id, inline_type); } self.add_parameters_to_scope(&func.parameters); } diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index a39df14d60..3fe52f7f0e 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -14,7 +14,10 @@ use noirc_frontend::{ use crate::{ errors::{InternalError, RuntimeError}, - ssa::{function_builder::data_bus::DataBusBuilder, ir::instruction::Intrinsic}, + ssa::{ + function_builder::data_bus::DataBusBuilder, + ir::{function::InlineType, instruction::Intrinsic}, + }, }; use self::{ @@ -52,14 +55,15 @@ pub(crate) fn generate_ssa( // Queue the main function for compilation context.get_or_queue_function(main_id); - let mut function_context = FunctionContext::new( main.name.clone(), &main.parameters, if force_brillig_runtime || main.unconstrained { RuntimeType::Brillig } else { - RuntimeType::Acir + let main_inline_type = + if main.should_fold { InlineType::Fold } else { InlineType::Inline }; + RuntimeType::Acir(main_inline_type) }, &context, ); diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 509f778f3b..9995c03114 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -12,6 +12,7 @@ pub(crate) struct Ssa { pub(crate) functions: BTreeMap, pub(crate) main_id: FunctionId, pub(crate) next_id: AtomicCounter, + pub(crate) id_to_index: BTreeMap, } impl Ssa { @@ -26,7 +27,9 @@ impl Ssa { (f.id(), f) }); - Self { functions, main_id, next_id: AtomicCounter::starting_after(max_id) } + let id_to_index = btree_map(functions.iter().enumerate(), |(i, (id, _))| (*id, i as u32)); + + Self { functions, main_id, next_id: AtomicCounter::starting_after(max_id), id_to_index } } /// Returns the entry-point function of the program diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index 3e8b78c131..10d962a23f 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -108,6 +108,7 @@ impl From for NoirFunction { Some(FunctionAttribute::Test { .. }) => FunctionKind::Normal, Some(FunctionAttribute::Oracle(_)) => FunctionKind::Oracle, Some(FunctionAttribute::Recursive) => FunctionKind::Recursive, + Some(FunctionAttribute::Fold) => FunctionKind::Normal, None => FunctionKind::Normal, }; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index f8e3c4cab6..624961ddae 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -942,7 +942,7 @@ impl<'a> Resolver<'a> { }); } let is_low_level_function = - func.attributes().function.as_ref().map_or(false, |func| func.is_low_level()); + attributes.function.as_ref().map_or(false, |func| func.is_low_level()); if !self.path_resolver.module_id().krate.is_stdlib() && is_low_level_function { let error = ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; @@ -991,6 +991,8 @@ impl<'a> Resolver<'a> { .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) .collect(); + let should_fold = attributes.is_foldable(); + FuncMeta { name: name_ident, kind: func.kind, @@ -1005,6 +1007,7 @@ impl<'a> Resolver<'a> { has_body: !func.def.body.is_empty(), trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), is_entry_point: self.is_entry_point_function(func), + should_fold, } } diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index 6beb6929ce..c881efcfc4 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -315,7 +315,7 @@ impl From for Diagnostic { } TypeCheckError::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error( "Only sized types may be used in the entry point to a program".to_string(), - "Slices, references, or any type containing them may not be used in main or a contract function".to_string(), span), + "Slices, references, or any type containing them may not be used in main, contract functions, or foldable functions".to_string(), span), TypeCheckError::MismatchTraitImplNumParameters { expected_num_parameters, actual_num_parameters, diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 137608f803..c6e9b7f804 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -160,7 +160,7 @@ fn check_if_type_is_valid_for_program_input( errors: &mut Vec, ) { let meta = type_checker.interner.function_meta(&func_id); - if meta.is_entry_point && !param.1.is_valid_for_program_input() { + if (meta.is_entry_point || meta.should_fold) && !param.1.is_valid_for_program_input() { let span = param.0.span(); errors.push(TypeCheckError::InvalidTypeForEntryPoint { span }); } @@ -503,6 +503,7 @@ mod test { trait_constraints: Vec::new(), direct_generics: Vec::new(), is_entry_point: true, + should_fold: false, }; interner.push_fn_meta(func_meta, func_id); @@ -561,12 +562,7 @@ mod test { "#; - let expected_num_errors = 0; - type_check_src_code_errors_expected( - src, - expected_num_errors, - vec![String::from("main"), String::from("foo")], - ); + type_check_src_code(src, vec![String::from("main"), String::from("foo")]); } #[test] fn basic_closure() { @@ -591,6 +587,19 @@ mod test { type_check_src_code(src, vec![String::from("main")]); } + + #[test] + fn fold_entry_point() { + let src = r#" + #[fold] + fn fold(x: &mut Field) -> Field { + *x + } + "#; + + type_check_src_code_errors_expected(src, vec![String::from("fold")], 1); + } + // This is the same Stub that is in the resolver, maybe we can pull this out into a test module and re-use? struct TestPathResolver(HashMap); @@ -624,15 +633,15 @@ mod test { } fn type_check_src_code(src: &str, func_namespace: Vec) { - type_check_src_code_errors_expected(src, 0, func_namespace); + type_check_src_code_errors_expected(src, func_namespace, 0); } // This function assumes that there is only one function and this is the // func id that is returned fn type_check_src_code_errors_expected( src: &str, - expected_number_errors: usize, func_namespace: Vec, + expected_num_type_check_errs: usize, ) { let (program, errors) = parse_program(src); let mut interner = NodeInterner::default(); @@ -640,9 +649,8 @@ mod test { assert_eq!( errors.len(), - expected_number_errors, - "expected {} errors, but got {}, errors: {:?}", - expected_number_errors, + 0, + "expected 0 parser errors, but got {}, errors: {:?}", errors.len(), errors ); @@ -688,6 +696,13 @@ mod test { // Type check section let errors = super::type_check_func(&mut interner, func_ids.first().cloned().unwrap()); - assert_eq!(errors, vec![]); + assert_eq!( + errors.len(), + expected_num_type_check_errs, + "expected {} type check errors, but got {}, errors: {:?}", + expected_num_type_check_errs, + errors.len(), + errors + ); } } diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index 56543e8185..a3bbc9445a 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -124,6 +124,10 @@ pub struct FuncMeta { /// True if this function is an entry point to the program. /// For non-contracts, this means the function is `main`. pub is_entry_point: bool, + + /// True if this function is marked with an attribute + /// that indicates it should not be inlined, such as for folding. + pub should_fold: bool, } impl FuncMeta { diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index cf66ece0c3..363ca767a8 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -726,6 +726,16 @@ mod tests { ); } + #[test] + fn fold_attribute() { + let input = r#"#[fold]"#; + + let mut lexer = Lexer::new(input); + let token = lexer.next_token().unwrap(); + + assert_eq!(token.token(), &Token::Attribute(Attribute::Function(FunctionAttribute::Fold))); + } + #[test] fn contract_library_method_attribute() { let input = r#"#[contract_library_method]"#; diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 4432a3f9e0..f8378cdd84 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -427,6 +427,10 @@ impl Attributes { } None } + + pub fn is_foldable(&self) -> bool { + self.function.as_ref().map_or(false, |func_attribute| func_attribute.is_foldable()) + } } /// An Attribute can be either a Primary Attribute or a Secondary Attribute @@ -486,6 +490,7 @@ impl Attribute { } ["test"] => Attribute::Function(FunctionAttribute::Test(TestScope::None)), ["recursive"] => Attribute::Function(FunctionAttribute::Recursive), + ["fold"] => Attribute::Function(FunctionAttribute::Fold), ["test", name] => { validate(name)?; let malformed_scope = @@ -537,6 +542,7 @@ pub enum FunctionAttribute { Oracle(String), Test(TestScope), Recursive, + Fold, } impl FunctionAttribute { @@ -565,6 +571,10 @@ impl FunctionAttribute { pub fn is_low_level(&self) -> bool { matches!(self, FunctionAttribute::Foreign(_) | FunctionAttribute::Builtin(_)) } + + pub fn is_foldable(&self) -> bool { + matches!(self, FunctionAttribute::Fold) + } } impl fmt::Display for FunctionAttribute { @@ -575,6 +585,7 @@ impl fmt::Display for FunctionAttribute { FunctionAttribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), FunctionAttribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), FunctionAttribute::Recursive => write!(f, "#[recursive]"), + FunctionAttribute::Fold => write!(f, "#[fold]"), } } } @@ -619,6 +630,7 @@ impl AsRef for FunctionAttribute { FunctionAttribute::Oracle(string) => string, FunctionAttribute::Test { .. } => "", FunctionAttribute::Recursive => "", + FunctionAttribute::Fold => "", } } } diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 4f633cbb35..7d20c2bcfe 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -211,6 +211,8 @@ pub struct Function { pub return_type: Type, pub unconstrained: bool, + pub should_fold: bool, + pub func_sig: FunctionSignature, } /// Compared to hir_def::types::Type, this monomorphized Type has: @@ -245,6 +247,7 @@ impl Type { #[derive(Debug, Clone, Hash)] pub struct Program { pub functions: Vec, + pub function_signatures: Vec, pub main_function_signature: FunctionSignature, /// Indicates whether witness indices are allowed to reoccur in the ABI of the resulting ACIR. /// @@ -264,6 +267,7 @@ impl Program { #[allow(clippy::too_many_arguments)] pub fn new( functions: Vec, + function_signatures: Vec, main_function_signature: FunctionSignature, return_distinctness: Distinctness, return_location: Option, @@ -275,6 +279,7 @@ impl Program { ) -> Program { Program { functions, + function_signatures, main_function_signature, return_distinctness, return_location, diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 618eba8f19..c6ab681db6 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -161,6 +161,18 @@ pub fn monomorphize_debug( undo_instantiation_bindings(bindings); } + let func_sigs = monomorphizer + .finished_functions + .iter() + .flat_map(|(_, f)| { + if f.should_fold || f.id == Program::main_id() { + Some(f.func_sig.clone()) + } else { + None + } + }) + .collect(); + let functions = vecmap(monomorphizer.finished_functions, |(_, f)| f); let FuncMeta { return_distinctness, return_visibility, kind, .. } = monomorphizer.interner.function_meta(&main); @@ -169,6 +181,7 @@ pub fn monomorphize_debug( monomorphizer.debug_type_tracker.extract_vars_and_types(); let program = Program::new( functions, + func_sigs, function_sig, *return_distinctness, monomorphizer.return_location, @@ -303,6 +316,8 @@ impl<'interner> Monomorphizer<'interner> { } let meta = self.interner.function_meta(&f).clone(); + let func_sig = meta.function_signature(); + let modifiers = self.interner.function_modifiers(&f); let name = self.interner.function_name(&f).to_owned(); @@ -314,9 +329,20 @@ impl<'interner> Monomorphizer<'interner> { }); let unconstrained = modifiers.is_unconstrained; + let should_fold = meta.should_fold; + let parameters = self.parameters(&meta.parameters); let body = self.expr(body_expr_id)?; - let function = ast::Function { id, name, parameters, body, return_type, unconstrained }; + let function = ast::Function { + id, + name, + parameters, + body, + return_type, + unconstrained, + should_fold, + func_sig, + }; self.push_function(id, function); Ok(()) @@ -1351,7 +1377,16 @@ impl<'interner> Monomorphizer<'interner> { let name = lambda_name.to_owned(); let unconstrained = false; - let function = ast::Function { id, name, parameters, body, return_type, unconstrained }; + let function = ast::Function { + id, + name, + parameters, + body, + return_type, + unconstrained, + should_fold: false, + func_sig: FunctionSignature::default(), + }; self.push_function(id, function); let typ = @@ -1465,7 +1500,16 @@ impl<'interner> Monomorphizer<'interner> { parameters.append(&mut converted_parameters); let unconstrained = false; - let function = ast::Function { id, name, parameters, body, return_type, unconstrained }; + let function = ast::Function { + id, + name, + parameters, + body, + return_type, + unconstrained, + should_fold: false, + func_sig: FunctionSignature::default(), + }; self.push_function(id, function); let lambda_value = @@ -1583,7 +1627,16 @@ impl<'interner> Monomorphizer<'interner> { let name = lambda_name.to_owned(); let unconstrained = false; - let function = ast::Function { id, name, parameters, body, return_type, unconstrained }; + let function = ast::Function { + id, + name, + parameters, + body, + return_type, + unconstrained, + should_fold: false, + func_sig: FunctionSignature::default(), + }; self.push_function(id, function); ast::Expression::Ident(ast::Ident { diff --git a/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts b/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts index 8b08b6f0dd..39ad0d802f 100644 --- a/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts +++ b/compiler/wasm/src/noir/dependencies/github-dependency-resolver.ts @@ -12,10 +12,12 @@ import { LogData } from '../../utils'; */ export class GithubDependencyResolver implements DependencyResolver { #fm: FileManager; + #fetch: typeof fetch; #log; - constructor(fm: FileManager) { + constructor(fm: FileManager, fetcher: typeof fetch) { this.#fm = fm; + this.#fetch = fetcher; this.#log = (msg: string, _data?: LogData) => { console.log(msg); }; @@ -56,7 +58,7 @@ export class GithubDependencyResolver implements DependencyResolver { return localArchivePath; } - const response = await fetch(url, { + const response = await this.#fetch(url, { method: 'GET', }); diff --git a/compiler/wasm/src/noir/noir-wasm-compiler.ts b/compiler/wasm/src/noir/noir-wasm-compiler.ts index 1ec3af1fd6..1fe3c79eae 100644 --- a/compiler/wasm/src/noir/noir-wasm-compiler.ts +++ b/compiler/wasm/src/noir/noir-wasm-compiler.ts @@ -72,7 +72,8 @@ export class NoirWasmCompiler { const dependencyManager = new DependencyManager( [ new LocalDependencyResolver(fileManager), - new GithubCodeArchiveDependencyResolver(fileManager), + // use node's global fetch + new GithubCodeArchiveDependencyResolver(fileManager, fetch), // TODO support actual Git repositories ], noirPackage, diff --git a/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts b/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts index e7fae8afe8..505b2269cd 100644 --- a/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts +++ b/compiler/wasm/test/dependencies/github-dependency-resolver.test.ts @@ -31,7 +31,7 @@ describe('GithubDependencyResolver', () => { let fetchStub: SinonStub | undefined; beforeEach(() => { - fetchStub = Sinon.stub(globalThis, 'fetch'); + fetchStub = Sinon.stub(); fm = createMemFSFileManager(createFsFromVolume(new Volume()), '/'); libDependency = { @@ -50,16 +50,12 @@ describe('GithubDependencyResolver', () => { }, }); - resolver = new GithubDependencyResolver(fm); + resolver = new GithubDependencyResolver(fm, fetchStub); // cut off outside access fetchStub.onCall(0).throws(new Error()); }); - afterEach(() => { - fetchStub?.restore(); - }); - it("returns null if it can't resolve a dependency", async () => { const dep = await resolver.resolveDependency(pkg, { path: '/lib-c', diff --git a/test_programs/execution_success/fold_basic/Nargo.toml b/test_programs/execution_success/fold_basic/Nargo.toml new file mode 100644 index 0000000000..575ba1f3ad --- /dev/null +++ b/test_programs/execution_success/fold_basic/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_basic" +type = "bin" +authors = [""] +compiler_version = ">=0.25.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/fold_basic/Prover.toml b/test_programs/execution_success/fold_basic/Prover.toml new file mode 100644 index 0000000000..f28f2f8cc4 --- /dev/null +++ b/test_programs/execution_success/fold_basic/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "10" diff --git a/test_programs/execution_success/fold_basic/src/main.nr b/test_programs/execution_success/fold_basic/src/main.nr new file mode 100644 index 0000000000..6c17120660 --- /dev/null +++ b/test_programs/execution_success/fold_basic/src/main.nr @@ -0,0 +1,11 @@ +fn main(x: Field, y: pub Field) { + let z = foo(x, y); + let z2 = foo(x, y); + assert(z == z2); +} + +#[fold] +fn foo(x: Field, y: Field) -> Field { + assert(x != y); + x +} diff --git a/test_programs/execution_success/fold_basic_nested_call/Nargo.toml b/test_programs/execution_success/fold_basic_nested_call/Nargo.toml new file mode 100644 index 0000000000..1b3c32999a --- /dev/null +++ b/test_programs/execution_success/fold_basic_nested_call/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_basic_nested_call" +type = "bin" +authors = [""] +compiler_version = ">=0.25.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/fold_basic_nested_call/Prover.toml b/test_programs/execution_success/fold_basic_nested_call/Prover.toml new file mode 100644 index 0000000000..f28f2f8cc4 --- /dev/null +++ b/test_programs/execution_success/fold_basic_nested_call/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "10" diff --git a/test_programs/execution_success/fold_basic_nested_call/src/main.nr b/test_programs/execution_success/fold_basic_nested_call/src/main.nr new file mode 100644 index 0000000000..3eb16b409f --- /dev/null +++ b/test_programs/execution_success/fold_basic_nested_call/src/main.nr @@ -0,0 +1,16 @@ +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 { + foo(x + 2, y) +} + +#[fold] +fn foo(x: Field, y: Field) -> Field { + assert(x != y); + x +} \ No newline at end of file diff --git a/test_programs/execution_success/regression_sha256_slice/Nargo.toml b/test_programs/execution_success/regression_sha256_slice/Nargo.toml deleted file mode 100644 index 759c3b20ba..0000000000 --- a/test_programs/execution_success/regression_sha256_slice/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "regression_sha256_slice" -type = "bin" -authors = [""] -compiler_version = ">=0.26.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/regression_sha256_slice/Prover.toml b/test_programs/execution_success/regression_sha256_slice/Prover.toml deleted file mode 100644 index 8a027e9eca..0000000000 --- a/test_programs/execution_success/regression_sha256_slice/Prover.toml +++ /dev/null @@ -1 +0,0 @@ -x = ["5", "10"] diff --git a/test_programs/execution_success/regression_sha256_slice/src/main.nr b/test_programs/execution_success/regression_sha256_slice/src/main.nr deleted file mode 100644 index 60b0911cf0..0000000000 --- a/test_programs/execution_success/regression_sha256_slice/src/main.nr +++ /dev/null @@ -1,12 +0,0 @@ -use dep::std; - -fn main(x: [u8; 2]) { - let mut y = x.as_slice(); - let digest1 = std::hash::sha256_slice(y); - let mut v = y; - if x[0] != 0 { - v = y.push_back(x[0]); - } - let digest2 = std::hash::sha256_slice(v); - assert(digest1 != digest2); -} diff --git a/tooling/acvm_cli/src/cli/execute_cmd.rs b/tooling/acvm_cli/src/cli/execute_cmd.rs index b76d0eccc2..86e7277451 100644 --- a/tooling/acvm_cli/src/cli/execute_cmd.rs +++ b/tooling/acvm_cli/src/cli/execute_cmd.rs @@ -1,14 +1,14 @@ use std::io::{self, Write}; use acir::circuit::Program; -use acir::native_types::WitnessMap; +use acir::native_types::{WitnessMap, WitnessStack}; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use crate::cli::fs::inputs::{read_bytecode_from_file, read_inputs_from_file}; use crate::cli::fs::witness::save_witness_to_dir; use crate::errors::CliError; -use nargo::ops::{execute_circuit, DefaultForeignCallExecutor}; +use nargo::ops::{execute_program, DefaultForeignCallExecutor}; use super::fs::witness::create_output_witness_string; @@ -39,8 +39,11 @@ pub(crate) struct ExecuteCommand { fn run_command(args: ExecuteCommand) -> Result { let bytecode = read_bytecode_from_file(&args.working_directory, &args.bytecode)?; let circuit_inputs = read_inputs_from_file(&args.working_directory, &args.input_witness)?; - let output_witness = execute_program_from_witness(&circuit_inputs, &bytecode, None)?; - let output_witness_string = create_output_witness_string(&output_witness)?; + let output_witness = execute_program_from_witness(circuit_inputs, &bytecode, None)?; + assert_eq!(output_witness.length(), 1, "ACVM CLI only supports a witness stack of size 1"); + let output_witness_string = create_output_witness_string( + &output_witness.peek().expect("Should have a witness stack item").witness, + )?; if args.output_witness.is_some() { save_witness_to_dir( &output_witness_string, @@ -61,16 +64,16 @@ pub(crate) fn run(args: ExecuteCommand) -> Result { } pub(crate) fn execute_program_from_witness( - inputs_map: &WitnessMap, + inputs_map: WitnessMap, bytecode: &[u8], foreign_call_resolver_url: Option<&str>, -) -> Result { +) -> Result { let blackbox_solver = Bn254BlackBoxSolver::new(); let program: Program = Program::deserialize_program(bytecode) .map_err(|_| CliError::CircuitDeserializationError())?; - execute_circuit( - &program.functions[0], - inputs_map.clone(), + execute_program( + &program, + inputs_map, &blackbox_solver, &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), ) diff --git a/tooling/backend_interface/src/proof_system.rs b/tooling/backend_interface/src/proof_system.rs index 105ae33779..3b47a7ced3 100644 --- a/tooling/backend_interface/src/proof_system.rs +++ b/tooling/backend_interface/src/proof_system.rs @@ -56,7 +56,7 @@ impl Backend { pub fn prove( &self, program: &Program, - witness_values: WitnessStack, + witness_stack: WitnessStack, ) -> Result, BackendError> { let binary_path = self.assert_binary_exists()?; self.assert_correct_version()?; @@ -66,7 +66,7 @@ impl Backend { // Create a temporary file for the witness let serialized_witnesses: Vec = - witness_values.try_into().expect("could not serialize witness map"); + witness_stack.try_into().expect("could not serialize witness map"); let witness_path = temp_directory.join("witness").with_extension("tr"); write_to_file(&serialized_witnesses, &witness_path); diff --git a/tooling/bb_abstraction_leaks/build.rs b/tooling/bb_abstraction_leaks/build.rs index 52f7783851..362d213295 100644 --- a/tooling/bb_abstraction_leaks/build.rs +++ b/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.32.0"; +const VERSION: &str = "0.30.1"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index f0de8d5d1c..ba12a20460 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -381,6 +381,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { ACVMStatus::RequiresForeignCall(_) => { unreachable!("Unexpected pending foreign call resolution"); } + ACVMStatus::RequiresAcirCall(_) => { + todo!("Multiple ACIR calls are not supported"); + } } } diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index c743768bee..ff238d79a4 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -64,7 +64,9 @@ impl NargoError { ExecutionError::SolvingError(error) => match error { OpcodeResolutionError::IndexOutOfBounds { .. } | OpcodeResolutionError::OpcodeNotSolvable(_) - | OpcodeResolutionError::UnsatisfiedConstrain { .. } => None, + | OpcodeResolutionError::UnsatisfiedConstrain { .. } + | OpcodeResolutionError::AcirMainCallAttempted { .. } + | OpcodeResolutionError::AcirCallOutputsMismatch { .. } => None, OpcodeResolutionError::BrilligFunctionFailed { message, .. } => Some(message), OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => Some(reason), }, diff --git a/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 370393fea0..6d328d6511 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -1,5 +1,7 @@ +use acvm::acir::circuit::Program; +use acvm::acir::native_types::WitnessStack; use acvm::brillig_vm::brillig::ForeignCallResult; -use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}; +use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -8,73 +10,145 @@ use crate::NargoError; use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; -#[tracing::instrument(level = "trace", skip_all)] -pub fn execute_circuit( - circuit: &Circuit, - initial_witness: WitnessMap, - blackbox_solver: &B, - foreign_call_executor: &mut F, -) -> Result { - let mut acvm = ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness); - - // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. - let mut assert_message: Option = None; - loop { - let solver_status = acvm.solve(); - - match solver_status { - ACVMStatus::Solved => break, - ACVMStatus::InProgress => { - unreachable!("Execution should not stop while in `InProgress` state.") - } - ACVMStatus::Failure(error) => { - let call_stack = match &error { - OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: ErrorLocation::Resolved(opcode_location), - } => Some(vec![*opcode_location]), - OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { - Some(call_stack.clone()) - } - _ => None, - }; - - return Err(NargoError::ExecutionError(match call_stack { - Some(call_stack) => { - // First check whether we have a runtime assertion message that should be resolved on an ACVM failure - // If we do not have a runtime assertion message, we should check whether the circuit has any hardcoded - // messages associated with a specific `OpcodeLocation`. - // Otherwise return the provided opcode resolution error. - if let Some(assert_message) = assert_message { - ExecutionError::AssertionFailed(assert_message.to_owned(), call_stack) - } else if let Some(assert_message) = circuit.get_assert_message( - *call_stack.last().expect("Call stacks should not be empty"), - ) { - ExecutionError::AssertionFailed(assert_message.to_owned(), call_stack) - } else { - ExecutionError::SolvingError(error) +struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> { + functions: &'a [Circuit], + // This gets built as we run through the program looking at each function call + witness_stack: WitnessStack, + + blackbox_solver: &'a B, + + foreign_call_executor: &'a mut F, +} + +impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, B, F> { + fn new( + functions: &'a [Circuit], + blackbox_solver: &'a B, + foreign_call_executor: &'a mut F, + ) -> Self { + ProgramExecutor { + functions, + witness_stack: WitnessStack::default(), + blackbox_solver, + foreign_call_executor, + } + } + + fn finalize(self) -> WitnessStack { + self.witness_stack + } + + #[tracing::instrument(level = "trace", skip_all)] + fn execute_circuit( + &mut self, + circuit: &Circuit, + initial_witness: WitnessMap, + ) -> Result { + let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); + + // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. + let mut assert_message: Option = None; + loop { + let solver_status = acvm.solve(); + + match solver_status { + ACVMStatus::Solved => break, + ACVMStatus::InProgress => { + unreachable!("Execution should not stop while in `InProgress` state.") + } + ACVMStatus::Failure(error) => { + let call_stack = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(opcode_location), + } => Some(vec![*opcode_location]), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + Some(call_stack.clone()) } - } - None => ExecutionError::SolvingError(error), - })); - } - ACVMStatus::RequiresForeignCall(foreign_call) => { - let foreign_call_result = foreign_call_executor.execute(&foreign_call)?; - match foreign_call_result { - NargoForeignCallResult::BrilligOutput(foreign_call_result) => { - acvm.resolve_pending_foreign_call(foreign_call_result); - } - NargoForeignCallResult::ResolvedAssertMessage(message) => { - if assert_message.is_some() { - unreachable!("Resolving an assert message should happen only once as the VM should have failed"); + _ => None, + }; + + return Err(NargoError::ExecutionError(match call_stack { + Some(call_stack) => { + // First check whether we have a runtime assertion message that should be resolved on an ACVM failure + // If we do not have a runtime assertion message, we should check whether the circuit has any hardcoded + // messages associated with a specific `OpcodeLocation`. + // Otherwise return the provided opcode resolution error. + if let Some(assert_message) = assert_message { + ExecutionError::AssertionFailed( + assert_message.to_owned(), + call_stack, + ) + } else if let Some(assert_message) = circuit.get_assert_message( + *call_stack.last().expect("Call stacks should not be empty"), + ) { + ExecutionError::AssertionFailed( + assert_message.to_owned(), + call_stack, + ) + } else { + ExecutionError::SolvingError(error) + } + } + None => ExecutionError::SolvingError(error), + })); + } + ACVMStatus::RequiresForeignCall(foreign_call) => { + let foreign_call_result = self.foreign_call_executor.execute(&foreign_call)?; + match foreign_call_result { + NargoForeignCallResult::BrilligOutput(foreign_call_result) => { + acvm.resolve_pending_foreign_call(foreign_call_result); } - assert_message = Some(message); + NargoForeignCallResult::ResolvedAssertMessage(message) => { + if assert_message.is_some() { + unreachable!("Resolving an assert message should happen only once as the VM should have failed"); + } + assert_message = Some(message); - acvm.resolve_pending_foreign_call(ForeignCallResult::default()); + acvm.resolve_pending_foreign_call(ForeignCallResult::default()); + } } } + ACVMStatus::RequiresAcirCall(call_info) => { + let acir_to_call = &self.functions[call_info.id as usize]; + let initial_witness = call_info.initial_witness; + let call_solved_witness = + self.execute_circuit(acir_to_call, initial_witness)?; + let mut call_resolved_outputs = Vec::new(); + for return_witness_index in acir_to_call.return_values.indices() { + if let Some(return_value) = + call_solved_witness.get_index(return_witness_index) + { + call_resolved_outputs.push(*return_value); + } else { + return Err(ExecutionError::SolvingError( + OpcodeNotSolvable::MissingAssignment(return_witness_index).into(), + ) + .into()); + } + } + acvm.resolve_pending_acir_call(call_resolved_outputs); + self.witness_stack.push(call_info.id, call_solved_witness); + } } } + + Ok(acvm.finalize()) } +} + +#[tracing::instrument(level = "trace", skip_all)] +pub fn execute_program( + program: &Program, + initial_witness: WitnessMap, + blackbox_solver: &B, + foreign_call_executor: &mut F, +) -> Result { + let main = &program.functions[0]; + + let mut executor = + ProgramExecutor::new(&program.functions, blackbox_solver, foreign_call_executor); + let main_witness = executor.execute_circuit(main, initial_witness)?; + executor.witness_stack.push(0, main_witness); - Ok(acvm.finalize()) + Ok(executor.finalize()) } diff --git a/tooling/nargo/src/ops/mod.rs b/tooling/nargo/src/ops/mod.rs index 55e9e92780..2f5e6ebb7d 100644 --- a/tooling/nargo/src/ops/mod.rs +++ b/tooling/nargo/src/ops/mod.rs @@ -2,7 +2,7 @@ pub use self::compile::{ collect_errors, compile_contract, compile_program, compile_program_with_debug_instrumenter, compile_workspace, report_errors, }; -pub use self::execute::execute_circuit; +pub use self::execute::execute_program; pub use self::foreign_calls::{ DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor, NargoForeignCallResult, }; diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index 8cf2934da4..45b1a88f99 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -1,4 +1,7 @@ -use acvm::{acir::native_types::WitnessMap, BlackBoxFunctionSolver}; +use acvm::{ + acir::native_types::{WitnessMap, WitnessStack}, + BlackBoxFunctionSolver, +}; use noirc_driver::{compile_no_check, CompileError, CompileOptions}; use noirc_errors::{debug_info::DebugInfo, FileDiagnostic}; use noirc_evaluator::errors::RuntimeError; @@ -6,7 +9,7 @@ use noirc_frontend::hir::{def_map::TestFunction, Context}; use crate::{errors::try_to_diagnose_runtime_error, NargoError}; -use super::{execute_circuit, DefaultForeignCallExecutor}; +use super::{execute_program, DefaultForeignCallExecutor}; pub enum TestStatus { Pass, @@ -33,9 +36,8 @@ pub fn run_test( Ok(compiled_program) => { // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. - let circuit_execution = execute_circuit( - // TODO(https://github.com/noir-lang/noir/issues/4428) - &compiled_program.program.functions[0], + let circuit_execution = execute_program( + &compiled_program.program, WitnessMap::new(), blackbox_solver, &mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url), @@ -83,7 +85,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct fn test_status_program_compile_pass( test_function: &TestFunction, debug: DebugInfo, - circuit_execution: Result, + circuit_execution: Result, ) -> TestStatus { let circuit_execution_err = match circuit_execution { // Circuit execution was successful; ie no errors or unsatisfied constraints diff --git a/tooling/nargo_cli/src/cli/debug_cmd.rs b/tooling/nargo_cli/src/cli/debug_cmd.rs index 1f448105ee..4f3e2886b2 100644 --- a/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use acvm::acir::native_types::WitnessMap; +use acvm::acir::native_types::{WitnessMap, WitnessStack}; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; @@ -187,7 +187,11 @@ fn run_async( } if let Some(witness_name) = witness_name { - let witness_path = save_witness_to_dir(solved_witness, witness_name, target_dir)?; + let witness_path = save_witness_to_dir( + WitnessStack::from(solved_witness), + witness_name, + target_dir, + )?; println!("[{}] Witness saved to {}", package.name, witness_path.display()); } diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index 022bf7b761..697f6d7c1e 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -1,4 +1,4 @@ -use acvm::acir::native_types::WitnessMap; +use acvm::acir::native_types::WitnessStack; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; @@ -91,7 +91,7 @@ pub(crate) fn run( let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); - let (return_value, solved_witness) = execute_program_and_decode( + let (return_value, witness_stack) = execute_program_and_decode( compiled_program, package, &args.prover_name, @@ -103,7 +103,7 @@ pub(crate) fn run( println!("[{}] Circuit output: {return_value:?}", package.name); } if let Some(witness_name) = &args.witness_name { - let witness_path = save_witness_to_dir(solved_witness, witness_name, target_dir)?; + let witness_path = save_witness_to_dir(witness_stack, witness_name, target_dir)?; println!("[{}] Witness saved to {}", package.name, witness_path.display()); } @@ -116,35 +116,37 @@ fn execute_program_and_decode( package: &Package, prover_name: &str, foreign_call_resolver_url: Option<&str>, -) -> Result<(Option, WitnessMap), CliError> { +) -> Result<(Option, WitnessStack), CliError> { // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; - let solved_witness = execute_program(&program, &inputs_map, foreign_call_resolver_url)?; + let witness_stack = execute_program(&program, &inputs_map, foreign_call_resolver_url)?; let public_abi = program.abi.public_abi(); - let (_, return_value) = public_abi.decode(&solved_witness)?; + // Get the entry point witness for the ABI + let main_witness = + &witness_stack.peek().expect("Should have at least one witness on the stack").witness; + let (_, return_value) = public_abi.decode(main_witness)?; - Ok((return_value, solved_witness)) + Ok((return_value, witness_stack)) } pub(crate) fn execute_program( compiled_program: &CompiledProgram, inputs_map: &InputMap, foreign_call_resolver_url: Option<&str>, -) -> Result { +) -> Result { let blackbox_solver = Bn254BlackBoxSolver::new(); let initial_witness = compiled_program.abi.encode(inputs_map, None)?; - // TODO(https://github.com/noir-lang/noir/issues/4428) - let solved_witness_err = nargo::ops::execute_circuit( - &compiled_program.program.functions[0], + let solved_witness_stack_err = nargo::ops::execute_program( + &compiled_program.program, initial_witness, &blackbox_solver, &mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url), ); - match solved_witness_err { - Ok(solved_witness) => Ok(solved_witness), + match solved_witness_stack_err { + Ok(solved_witness_stack) => Ok(solved_witness_stack), Err(err) => { let debug_artifact = DebugArtifact { debug_symbols: vec![compiled_program.debug.clone()], diff --git a/tooling/nargo_cli/src/cli/fs/witness.rs b/tooling/nargo_cli/src/cli/fs/witness.rs index 52b5c385e5..613cdec28d 100644 --- a/tooling/nargo_cli/src/cli/fs/witness.rs +++ b/tooling/nargo_cli/src/cli/fs/witness.rs @@ -1,21 +1,19 @@ use std::path::{Path, PathBuf}; -use acvm::acir::native_types::{WitnessMap, WitnessStack}; +use acvm::acir::native_types::WitnessStack; use nargo::constants::WITNESS_EXT; use super::{create_named_dir, write_to_file}; use crate::errors::FilesystemError; pub(crate) fn save_witness_to_dir>( - witnesses: WitnessMap, + witness_stack: WitnessStack, witness_name: &str, witness_dir: P, ) -> Result { create_named_dir(witness_dir.as_ref(), "witness"); let witness_path = witness_dir.as_ref().join(witness_name).with_extension(WITNESS_EXT); - // TODO(https://github.com/noir-lang/noir/issues/4428) - let witness_stack: WitnessStack = witnesses.into(); let buf: Vec = witness_stack.try_into()?; write_to_file(buf.as_slice(), &witness_path); diff --git a/tooling/nargo_cli/src/cli/prove_cmd.rs b/tooling/nargo_cli/src/cli/prove_cmd.rs index 272f2fa8e5..b9e4bca9e6 100644 --- a/tooling/nargo_cli/src/cli/prove_cmd.rs +++ b/tooling/nargo_cli/src/cli/prove_cmd.rs @@ -1,4 +1,3 @@ -use acvm::acir::native_types::WitnessStack; use clap::Args; use nargo::constants::{PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}; use nargo::ops::{compile_program, report_errors}; @@ -123,12 +122,14 @@ pub(crate) fn prove_package( let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &compiled_program.abi)?; - let solved_witness = - execute_program(&compiled_program, &inputs_map, foreign_call_resolver_url)?; + let witness_stack = execute_program(&compiled_program, &inputs_map, foreign_call_resolver_url)?; // Write public inputs into Verifier.toml let public_abi = compiled_program.abi.public_abi(); - let (public_inputs, return_value) = public_abi.decode(&solved_witness)?; + // Get the entry point witness for the ABI + let main_witness = + &witness_stack.peek().expect("Should have at least one witness on the stack").witness; + let (public_inputs, return_value) = public_abi.decode(main_witness)?; write_inputs_to_file( &public_inputs, @@ -139,7 +140,7 @@ pub(crate) fn prove_package( Format::Toml, )?; - let proof = backend.prove(&compiled_program.program, WitnessStack::from(solved_witness))?; + let proof = backend.prove(&compiled_program.program, witness_stack)?; if check_proof { let public_inputs = public_abi.encode(&public_inputs, return_value)?; diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index 251dd80c2f..1ea384cdd4 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.32.0", + "@aztec/bb.js": "portal:../../../../barretenberg/ts", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/yarn.lock b/yarn.lock index a39ae9921d..b45678f5d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,19 +221,18 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.32.0": - version: 0.32.0 - resolution: "@aztec/bb.js@npm:0.32.0" +"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": + version: 0.0.0-use.local + resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: dest/node/main.js - checksum: 0919957e141ae0a65cfab961dce122fa06de628a10b7cb661d31d8ed4793ce80980fcf315620ceffffa45581db941bad43c392f4b2aa9becaaf7d2faaba01ffc + bb.js: ./dest/node/main.js languageName: node - linkType: hard + linkType: soft "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4396,7 +4395,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.32.0 + "@aztec/bb.js": "portal:../../../../barretenberg/ts" "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3