Skip to content

Commit

Permalink
feat: allow providing custom foreign call executors to `execute_circu…
Browse files Browse the repository at this point in the history
…it` (#3506)

Co-authored-by: kevaundray <[email protected]>
  • Loading branch information
TomAFrench and kevaundray authored Nov 20, 2023
1 parent 9e1ad85 commit d27db33
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 51 deletions.
13 changes: 5 additions & 8 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use acvm::{BlackBoxFunctionSolver, FieldElement};

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 @@ -24,9 +24,8 @@ pub(super) enum DebugCommandResult {
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 @@ -40,9 +39,8 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
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 Down Expand Up @@ -119,15 +117,14 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
}
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,6 +1,6 @@
pub use self::compile::{compile_program, compile_workspace};
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

0 comments on commit d27db33

Please sign in to comment.