Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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