Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow providing custom foreign call executors to execute_circuit #3506

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use nargo::artifacts::debug::DebugArtifact;
use nargo::errors::{ExecutionError, Location};
use nargo::ops::ForeignCallExecutor;
use nargo::ops::{DefaultForeignCallExecutor, ForeignCallExecutor};
use nargo::NargoError;

use std::collections::{hash_set::Iter, HashSet};
Expand All @@ -23,9 +23,8 @@
pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> {
acvm: ACVM<'a, B>,
brillig_solver: Option<BrilligSolver<'a, B>>,
foreign_call_executor: ForeignCallExecutor,
foreign_call_executor: DefaultForeignCallExecutor,
debug_artifact: &'a DebugArtifact,
show_output: bool,
breakpoints: HashSet<OpcodeLocation>,
}

Expand All @@ -39,9 +38,8 @@
Self {
acvm: ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness),
brillig_solver: None,
foreign_call_executor: ForeignCallExecutor::default(),
foreign_call_executor: DefaultForeignCallExecutor::new(true),
debug_artifact,
show_output: true,
breakpoints: HashSet::new(),
}
}
Expand All @@ -64,7 +62,7 @@
}
}

// Returns the callstack in source code locations for the currently

Check warning on line 65 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (callstack)
// executing opcode. This can be None if the execution finished (and
// get_current_opcode_location() returns None) or if the opcode is not
// mapped to a specific source location in the debug artifact (which can
Expand Down Expand Up @@ -106,15 +104,14 @@
}

fn handle_foreign_call(&mut self, foreign_call: ForeignCallWaitInfo) -> DebugCommandResult {
let foreign_call_result =
self.foreign_call_executor.execute(&foreign_call, self.show_output);
let foreign_call_result = self.foreign_call_executor.execute(&foreign_call);
match foreign_call_result {
Ok(foreign_call_result) => {
self.acvm.resolve_pending_foreign_call(foreign_call_result);
// TODO: should we retry executing the opcode somehow in this case?
DebugCommandResult::Ok
}
Err(error) => DebugCommandResult::Error(error),
Err(error) => DebugCommandResult::Error(error.into()),
}
}

Expand Down
11 changes: 4 additions & 7 deletions tooling/nargo/src/ops/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ use crate::NargoError;

use super::foreign_calls::ForeignCallExecutor;

pub fn execute_circuit<B: BlackBoxFunctionSolver>(
blackbox_solver: &B,
pub fn execute_circuit<B: BlackBoxFunctionSolver, F: ForeignCallExecutor>(
circuit: &Circuit,
initial_witness: WitnessMap,
show_output: bool,
blackbox_solver: &B,
foreign_call_executor: &mut F,
) -> Result<WitnessMap, NargoError> {
let mut acvm = ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness);

let mut foreign_call_executor = ForeignCallExecutor::default();

loop {
let solver_status = acvm.solve();

Expand Down Expand Up @@ -50,8 +48,7 @@ pub fn execute_circuit<B: BlackBoxFunctionSolver>(
}));
}
ACVMStatus::RequiresForeignCall(foreign_call) => {
let foreign_call_result =
foreign_call_executor.execute(&foreign_call, show_output)?;
let foreign_call_result = foreign_call_executor.execute(&foreign_call)?;
acvm.resolve_pending_foreign_call(foreign_call_result);
}
}
Expand Down
74 changes: 44 additions & 30 deletions tooling/nargo/src/ops/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ use acvm::{
use iter_extended::vecmap;
use noirc_printable_type::{decode_string_value, ForeignCallError, PrintableValueDisplay};

use crate::NargoError;
pub trait ForeignCallExecutor {
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo,
) -> Result<ForeignCallResult, ForeignCallError>;
}

/// This enumeration represents the Brillig foreign calls that are natively supported by nargo.
/// After resolution of a foreign call, nargo will restart execution of the ACVM
Expand Down Expand Up @@ -89,23 +94,55 @@ impl MockedCall {
}

#[derive(Debug, Default)]
pub struct ForeignCallExecutor {
pub struct DefaultForeignCallExecutor {
/// Mocks have unique ids used to identify them in Noir, allowing to update or remove them.
last_mock_id: usize,
/// The registered mocks
mocked_responses: Vec<MockedCall>,
/// Whether to print [`ForeignCall::Println`] output.
show_output: bool,
}

impl ForeignCallExecutor {
pub fn execute(
impl DefaultForeignCallExecutor {
pub fn new(show_output: bool) -> Self {
DefaultForeignCallExecutor { show_output, ..DefaultForeignCallExecutor::default() }
}
}

impl DefaultForeignCallExecutor {
fn extract_mock_id(
foreign_call_inputs: &[ForeignCallParam],
) -> Result<(usize, &[ForeignCallParam]), ForeignCallError> {
let (id, params) =
foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?;
Ok((id.unwrap_value().to_usize(), params))
}

fn find_mock_by_id(&mut self, id: usize) -> Option<&mut MockedCall> {
self.mocked_responses.iter_mut().find(|response| response.id == id)
}

fn parse_string(param: &ForeignCallParam) -> String {
let fields: Vec<_> = param.values().into_iter().map(|value| value.to_field()).collect();
decode_string_value(&fields)
}

fn execute_println(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> {
let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?;
println!("{display_values}");
Ok(())
}
}

impl ForeignCallExecutor for DefaultForeignCallExecutor {
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo,
show_output: bool,
) -> Result<ForeignCallResult, NargoError> {
) -> Result<ForeignCallResult, ForeignCallError> {
let foreign_call_name = foreign_call.function.as_str();
match ForeignCall::lookup(foreign_call_name) {
Some(ForeignCall::Println) => {
if show_output {
if self.show_output {
Self::execute_println(&foreign_call.inputs)?;
}
Ok(ForeignCallResult { values: vec![] })
Expand Down Expand Up @@ -202,27 +239,4 @@ impl ForeignCallExecutor {
}
}
}

fn extract_mock_id(
foreign_call_inputs: &[ForeignCallParam],
) -> Result<(usize, &[ForeignCallParam]), ForeignCallError> {
let (id, params) =
foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?;
Ok((id.unwrap_value().to_usize(), params))
}

fn find_mock_by_id(&mut self, id: usize) -> Option<&mut MockedCall> {
self.mocked_responses.iter_mut().find(|response| response.id == id)
}

fn parse_string(param: &ForeignCallParam) -> String {
let fields: Vec<_> = param.values().into_iter().map(|value| value.to_field()).collect();
decode_string_value(&fields)
}

fn execute_println(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), NargoError> {
let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?;
println!("{display_values}");
Ok(())
}
}
2 changes: 1 addition & 1 deletion tooling/nargo/src/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub use self::execute::execute_circuit;
pub use self::foreign_calls::ForeignCallExecutor;
pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCallExecutor};
pub use self::optimize::{optimize_contract, optimize_program};
pub use self::test::{run_test, TestStatus};

Expand Down
10 changes: 7 additions & 3 deletions tooling/nargo/src/ops/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use noirc_frontend::hir::{def_map::TestFunction, Context};

use crate::{errors::try_to_diagnose_runtime_error, NargoError};

use super::execute_circuit;
use super::{execute_circuit, DefaultForeignCallExecutor};

pub enum TestStatus {
Pass,
Expand All @@ -26,8 +26,12 @@ pub fn run_test<B: BlackBoxFunctionSolver>(
Ok(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(blackbox_solver, &program.circuit, WitnessMap::new(), show_output);
let circuit_execution = execute_circuit(
&program.circuit,
WitnessMap::new(),
blackbox_solver,
&mut DefaultForeignCallExecutor::new(show_output),
);
test_status_program_compile_pass(test_function, program.debug, circuit_execution)
}
Err(err) => test_status_program_compile_fail(err, test_function),
Expand Down
5 changes: 3 additions & 2 deletions tooling/nargo_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use clap::Args;
use nargo::artifacts::debug::DebugArtifact;
use nargo::constants::PROVER_INPUT_FILE;
use nargo::errors::try_to_diagnose_runtime_error;
use nargo::ops::DefaultForeignCallExecutor;
use nargo::package::Package;
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection};
use noirc_abi::input_parser::{Format, InputValue};
Expand Down Expand Up @@ -106,10 +107,10 @@ pub(crate) fn execute_program(
let initial_witness = compiled_program.abi.encode(inputs_map, None)?;

let solved_witness_err = nargo::ops::execute_circuit(
&blackbox_solver,
&compiled_program.circuit,
initial_witness,
true,
&blackbox_solver,
&mut DefaultForeignCallExecutor::new(true),
);
match solved_witness_err {
Ok(solved_witness) => Ok(solved_witness),
Expand Down
Loading