Skip to content

Commit

Permalink
Merge 666f3d8 into e2549df
Browse files Browse the repository at this point in the history
  • Loading branch information
aakoshh authored Dec 18, 2024
2 parents e2549df + 666f3d8 commit 0a9fd88
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 147 deletions.
52 changes: 37 additions & 15 deletions tooling/debugger/src/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use acvm::{
AcirField, FieldElement,
};
use nargo::{
foreign_calls::{DefaultForeignCallExecutor, ForeignCallExecutor},
foreign_calls::{layers::Layer, DefaultForeignCallExecutor, ForeignCallExecutor},
PrintOutput,
};
use noirc_artifacts::debug::{DebugArtifact, DebugVars, StackFrame};
Expand Down Expand Up @@ -44,23 +44,31 @@ pub trait DebugForeignCallExecutor: ForeignCallExecutor<FieldElement> {
fn current_stack_frame(&self) -> Option<StackFrame<FieldElement>>;
}

pub struct DefaultDebugForeignCallExecutor<'a> {
executor: DefaultForeignCallExecutor<'a, FieldElement>,
#[derive(Default)]
pub struct DefaultDebugForeignCallExecutor {
pub debug_vars: DebugVars<FieldElement>,
}

impl<'a> DefaultDebugForeignCallExecutor<'a> {
pub fn new(output: PrintOutput<'a>) -> Self {
Self {
executor: DefaultForeignCallExecutor::new(output, None, None, None),
debug_vars: DebugVars::default(),
}
impl DefaultDebugForeignCallExecutor {
pub fn make(
output: PrintOutput<'_>,
ex: DefaultDebugForeignCallExecutor,
) -> impl DebugForeignCallExecutor + '_ {
Layer::new(ex, DefaultForeignCallExecutor::new(output, None, None, None))
}

#[allow(clippy::new_ret_no_self, dead_code)]
pub fn new(output: PrintOutput<'_>) -> impl DebugForeignCallExecutor + '_ {
Self::make(output, Self::default())
}

pub fn from_artifact(output: PrintOutput<'a>, artifact: &DebugArtifact) -> Self {
let mut ex = Self::new(output);
pub fn from_artifact<'a>(
output: PrintOutput<'a>,
artifact: &DebugArtifact,
) -> impl DebugForeignCallExecutor + 'a {
let mut ex = Self::default();
ex.load_artifact(artifact);
ex
Self::make(output, ex)
}

pub fn load_artifact(&mut self, artifact: &DebugArtifact) {
Expand All @@ -73,7 +81,7 @@ impl<'a> DefaultDebugForeignCallExecutor<'a> {
}
}

impl DebugForeignCallExecutor for DefaultDebugForeignCallExecutor<'_> {
impl DebugForeignCallExecutor for DefaultDebugForeignCallExecutor {
fn get_variables(&self) -> Vec<StackFrame<FieldElement>> {
self.debug_vars.get_variables()
}
Expand All @@ -91,7 +99,7 @@ fn debug_fn_id(value: &FieldElement) -> DebugFnId {
DebugFnId(value.to_u128() as u32)
}

impl ForeignCallExecutor<FieldElement> for DefaultDebugForeignCallExecutor<'_> {
impl ForeignCallExecutor<FieldElement> for DefaultDebugForeignCallExecutor {
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo<FieldElement>,
Expand Down Expand Up @@ -166,7 +174,21 @@ impl ForeignCallExecutor<FieldElement> for DefaultDebugForeignCallExecutor<'_> {
self.debug_vars.pop_fn();
Ok(ForeignCallResult::default())
}
None => self.executor.execute(foreign_call),
None => Err(ForeignCallError::NoHandler(foreign_call_name.to_string())),
}
}
}

impl<H, I> DebugForeignCallExecutor for Layer<H, I, FieldElement>
where
H: DebugForeignCallExecutor,
I: ForeignCallExecutor<FieldElement>,
{
fn get_variables(&self) -> Vec<StackFrame<FieldElement>> {
self.handler().get_variables()
}

fn current_stack_frame(&self) -> Option<StackFrame<FieldElement>> {
self.handler().current_stack_frame()
}
}
144 changes: 144 additions & 0 deletions tooling/nargo/src/foreign_calls/layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use std::marker::PhantomData;

use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField};
use noirc_printable_type::ForeignCallError;

use super::ForeignCallExecutor;

/// Returns an empty result when called.
///
/// If all executors have no handler for the given foreign call then we cannot
/// return a correct response to the ACVM. The best we can do is to return an empty response,
/// this allows us to ignore any foreign calls which exist solely to pass information from inside
/// the circuit to the environment (e.g. custom logging) as the execution will still be able to progress.
///
/// We optimistically return an empty response for all oracle calls as the ACVM will error
/// should a response have been required.
pub struct Empty;

impl<F: AcirField> ForeignCallExecutor<F> for Empty {
fn execute(
&mut self,
_foreign_call: &ForeignCallWaitInfo<F>,
) -> Result<ForeignCallResult<F>, ForeignCallError> {
Ok(ForeignCallResult::default())
}
}

/// Returns `NoHandler` for every call.
pub struct Unhandled;

impl<F: AcirField> ForeignCallExecutor<F> for Unhandled {
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo<F>,
) -> Result<ForeignCallResult<F>, ForeignCallError> {
Err(ForeignCallError::NoHandler(foreign_call.function.clone()))
}
}

/// Forwards to the inner executor if its own handler doesn't handle the call.
pub struct Layer<H, I, F> {
pub handler: H,
pub inner: I,
_field: PhantomData<F>,
}

impl<H, I, F> ForeignCallExecutor<F> for Layer<H, I, F>
where
H: ForeignCallExecutor<F>,
I: ForeignCallExecutor<F>,
{
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo<F>,
) -> Result<ForeignCallResult<F>, ForeignCallError> {
match self.handler.execute(foreign_call) {
Err(ForeignCallError::NoHandler(_)) => self.inner.execute(foreign_call),
handled => handled,
}
}
}

impl<H, I, F> Layer<H, I, F> {
/// Create a layer from two handlers
pub fn new(handler: H, inner: I) -> Self {
Self { handler, inner, _field: PhantomData }
}
}

impl<H, F> Layer<H, Empty, F> {
/// Create a layer from a handler.
/// If the handler doesn't handle a call, a default empty response is returned.
pub fn or_empty(handler: H) -> Self {
Self { handler, inner: Empty, _field: PhantomData }
}
}

impl<H, F> Layer<H, Unhandled, F> {
/// Create a layer from a handler.
/// If the handler doesn't handle a call, `NoHandler` error is returned.
pub fn or_unhandled(handler: H) -> Self {
Self { handler, inner: Unhandled, _field: PhantomData }
}
}

impl<F> Layer<Unhandled, Unhandled, F> {
/// A base layer that doesn't handle anything.
pub fn unhandled() -> Self {
Self { handler: Unhandled, inner: Unhandled, _field: PhantomData }
}
}

impl<H, I, F> Layer<H, I, F> {
/// Add another layer on top of this one.
pub fn add_layer<J>(self, handler: J) -> Layer<J, Self, F> {
Layer::new(handler, self)
}

pub fn handler(&self) -> &H {
&self.handler
}

pub fn inner(&self) -> &I {
&self.inner
}
}

/// Compose handlers.
pub trait Layering {
/// Layer an executor on top of this one.
/// The `other` executor will be called first.
fn add_layer<L, F>(self, other: L) -> Layer<L, Self, F>
where
Self: Sized + ForeignCallExecutor<F>,
L: ForeignCallExecutor<F>;
}

impl<T> Layering for T {
fn add_layer<L, F>(self, other: L) -> Layer<L, T, F>
where
T: Sized + ForeignCallExecutor<F>,
L: ForeignCallExecutor<F>,
{
Layer::new(other, self)
}
}

/// Support disabling a layer by making it optional.
/// This way we can still have a known static type for a composition,
/// because layers are always added, potentially wrapped in an `Option`.
impl<H, F> ForeignCallExecutor<F> for Option<H>
where
H: ForeignCallExecutor<F>,
{
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo<F>,
) -> Result<ForeignCallResult<F>, ForeignCallError> {
match self {
Some(handler) => handler.execute(foreign_call),
None => Err(ForeignCallError::NoHandler(foreign_call.function.clone())),
}
}
}
38 changes: 35 additions & 3 deletions tooling/nargo/src/foreign_calls/mocker.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::marker::PhantomData;

use acvm::{
acir::brillig::{ForeignCallParam, ForeignCallResult},
pwg::ForeignCallWaitInfo,
Expand Down Expand Up @@ -45,7 +47,7 @@ impl<F: PartialEq> MockedCall<F> {
}

#[derive(Debug, Default)]
pub(crate) struct MockForeignCallExecutor<F> {
pub struct MockForeignCallExecutor<F> {
/// Mocks have unique ids used to identify them in Noir, allowing to update or remove them.
last_mock_id: usize,
/// The registered mocks
Expand Down Expand Up @@ -78,8 +80,9 @@ impl<F: AcirField> MockForeignCallExecutor<F> {
}
}

impl<F: AcirField + Serialize + for<'a> Deserialize<'a>> ForeignCallExecutor<F>
for MockForeignCallExecutor<F>
impl<F> ForeignCallExecutor<F> for MockForeignCallExecutor<F>
where
F: AcirField + Serialize + for<'a> Deserialize<'a>,
{
fn execute(
&mut self,
Expand Down Expand Up @@ -174,3 +177,32 @@ impl<F: AcirField + Serialize + for<'a> Deserialize<'a>> ForeignCallExecutor<F>
}
}
}

/// Handler that panics if any of the mock functions are called.
#[allow(dead_code)] // TODO: Make the mocker optional
pub(crate) struct DisabledMockForeignCallExecutor<F> {
_field: PhantomData<F>,
}

impl<F> ForeignCallExecutor<F> for DisabledMockForeignCallExecutor<F> {
fn execute(
&mut self,
foreign_call: &ForeignCallWaitInfo<F>,
) -> Result<ForeignCallResult<F>, ForeignCallError> {
let foreign_call_name = foreign_call.function.as_str();
if let Some(call) = ForeignCall::lookup(foreign_call_name) {
match call {
ForeignCall::CreateMock
| ForeignCall::SetMockParams
| ForeignCall::GetMockLastParams
| ForeignCall::SetMockReturns
| ForeignCall::SetMockTimes
| ForeignCall::ClearMock => {
panic!("unexpected mock call: {}", foreign_call.function)
}
_ => {}
}
}
Err(ForeignCallError::NoHandler(foreign_call.function.clone()))
}
}
Loading

0 comments on commit 0a9fd88

Please sign in to comment.