Skip to content

Commit

Permalink
chore: move implementation of print foreign call into nargo (#6865)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Jan 2, 2025
1 parent f814b89 commit 79c894e
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 224 deletions.
5 changes: 1 addition & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions compiler/noirc_printable_type/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ workspace = true

[dependencies]
acvm.workspace = true
iter-extended.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
jsonrpc.workspace = true

[dev-dependencies]
212 changes: 21 additions & 191 deletions compiler/noirc_printable_type/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#![forbid(unsafe_code)]
#![warn(unused_crate_dependencies, unused_extern_crates)]
#![warn(unreachable_pub)]
#![warn(clippy::semicolon_if_nothing_returned)]

use std::{collections::BTreeMap, str};

use acvm::{acir::AcirField, brillig_vm::brillig::ForeignCallParam};
use iter_extended::vecmap;
use acvm::AcirField;

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
Expand Down Expand Up @@ -66,96 +69,23 @@ pub enum PrintableValueDisplay<F> {
Plain(PrintableValue<F>, PrintableType),
FmtString(String, Vec<(PrintableValue<F>, PrintableType)>),
}

#[derive(Debug, Error)]
pub enum ForeignCallError {
#[error("No handler could be found for foreign call `{0}`")]
NoHandler(String),

#[error("Foreign call inputs needed for execution are missing")]
MissingForeignCallInputs,

#[error("Could not parse PrintableType argument. {0}")]
ParsingError(#[from] serde_json::Error),

#[error("Failed calling external resolver. {0}")]
ExternalResolverError(#[from] jsonrpc::Error),

#[error("Assert message resolved after an unsatisified constrain. {0}")]
ResolvedAssertMessage(String),
}

impl<F: AcirField> TryFrom<&[ForeignCallParam<F>]> for PrintableValueDisplay<F> {
type Error = ForeignCallError;

fn try_from(foreign_call_inputs: &[ForeignCallParam<F>]) -> Result<Self, Self::Error> {
let (is_fmt_str, foreign_call_inputs) =
foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?;

if is_fmt_str.unwrap_field().is_one() {
convert_fmt_string_inputs(foreign_call_inputs)
} else {
convert_string_inputs(foreign_call_inputs)
impl<F: AcirField> std::fmt::Display for PrintableValueDisplay<F> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Plain(value, typ) => {
let output_string = to_string(value, typ).ok_or(std::fmt::Error)?;
write!(fmt, "{output_string}")
}
Self::FmtString(template, values) => {
let mut values_iter = values.iter();
write_template_replacing_interpolations(template, fmt, || {
values_iter.next().and_then(|(value, typ)| to_string(value, typ))
})
}
}
}
}

fn convert_string_inputs<F: AcirField>(
foreign_call_inputs: &[ForeignCallParam<F>],
) -> Result<PrintableValueDisplay<F>, ForeignCallError> {
// Fetch the PrintableType from the foreign call input
// The remaining input values should hold what is to be printed
let (printable_type_as_values, input_values) =
foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?;
let printable_type = fetch_printable_type(printable_type_as_values)?;

// We must use a flat map here as each value in a struct will be in a separate input value
let mut input_values_as_fields = input_values.iter().flat_map(|param| param.fields());

let value = decode_value(&mut input_values_as_fields, &printable_type);

Ok(PrintableValueDisplay::Plain(value, printable_type))
}

fn convert_fmt_string_inputs<F: AcirField>(
foreign_call_inputs: &[ForeignCallParam<F>],
) -> Result<PrintableValueDisplay<F>, ForeignCallError> {
let (message, input_and_printable_types) =
foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?;

let message_as_fields = message.fields();
let message_as_string = decode_string_value(&message_as_fields);

let (num_values, input_and_printable_types) = input_and_printable_types
.split_first()
.ok_or(ForeignCallError::MissingForeignCallInputs)?;

let mut output = Vec::new();
let num_values = num_values.unwrap_field().to_u128() as usize;

let types_start_at = input_and_printable_types.len() - num_values;
let mut input_iter =
input_and_printable_types[0..types_start_at].iter().flat_map(|param| param.fields());
for printable_type in input_and_printable_types.iter().skip(types_start_at) {
let printable_type = fetch_printable_type(printable_type)?;
let value = decode_value(&mut input_iter, &printable_type);

output.push((value, printable_type));
}

Ok(PrintableValueDisplay::FmtString(message_as_string, output))
}

fn fetch_printable_type<F: AcirField>(
printable_type: &ForeignCallParam<F>,
) -> Result<PrintableType, ForeignCallError> {
let printable_type_as_fields = printable_type.fields();
let printable_type_as_string = decode_string_value(&printable_type_as_fields);
let printable_type: PrintableType = serde_json::from_str(&printable_type_as_string)?;

Ok(printable_type)
}

fn to_string<F: AcirField>(value: &PrintableValue<F>, typ: &PrintableType) -> Option<String> {
let mut output = String::new();
match (value, typ) {
Expand Down Expand Up @@ -193,7 +123,7 @@ fn to_string<F: AcirField>(value: &PrintableValue<F>, typ: &PrintableType) -> Op
(PrintableValue::Vec { array_elements, is_slice }, PrintableType::Array { typ, .. })
| (PrintableValue::Vec { array_elements, is_slice }, PrintableType::Slice { typ }) => {
if *is_slice {
output.push('&')
output.push('&');
}
output.push('[');
let mut values = array_elements.iter().peekable();
Expand Down Expand Up @@ -253,23 +183,6 @@ fn to_string<F: AcirField>(value: &PrintableValue<F>, typ: &PrintableType) -> Op
Some(output)
}

impl<F: AcirField> std::fmt::Display for PrintableValueDisplay<F> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Plain(value, typ) => {
let output_string = to_string(value, typ).ok_or(std::fmt::Error)?;
write!(fmt, "{output_string}")
}
Self::FmtString(template, values) => {
let mut values_iter = values.iter();
write_template_replacing_interpolations(template, fmt, || {
values_iter.next().and_then(|(value, typ)| to_string(value, typ))
})
}
}
}
}

fn write_template_replacing_interpolations(
template: &str,
fmt: &mut std::fmt::Formatter<'_>,
Expand Down Expand Up @@ -346,94 +259,11 @@ fn format_field_string<F: AcirField>(field: F) -> String {
"0x".to_owned() + &trimmed_field
}

/// Assumes that `field_iterator` contains enough field elements in order to decode the [PrintableType]
pub fn decode_value<F: AcirField>(
field_iterator: &mut impl Iterator<Item = F>,
typ: &PrintableType,
) -> PrintableValue<F> {
match typ {
PrintableType::Field
| PrintableType::SignedInteger { .. }
| PrintableType::UnsignedInteger { .. }
| PrintableType::Boolean => {
let field_element = field_iterator.next().unwrap();

PrintableValue::Field(field_element)
}
PrintableType::Array { length, typ } => {
let length = *length as usize;
let mut array_elements = Vec::with_capacity(length);
for _ in 0..length {
array_elements.push(decode_value(field_iterator, typ));
}

PrintableValue::Vec { array_elements, is_slice: false }
}
PrintableType::Slice { typ } => {
let length = field_iterator
.next()
.expect("not enough data to decode variable array length")
.to_u128() as usize;
let mut array_elements = Vec::with_capacity(length);
for _ in 0..length {
array_elements.push(decode_value(field_iterator, typ));
}

PrintableValue::Vec { array_elements, is_slice: true }
}
PrintableType::Tuple { types } => PrintableValue::Vec {
array_elements: vecmap(types, |typ| decode_value(field_iterator, typ)),
is_slice: false,
},
PrintableType::String { length } => {
let field_elements: Vec<F> = field_iterator.take(*length as usize).collect();

PrintableValue::String(decode_string_value(&field_elements))
}
PrintableType::Struct { fields, .. } => {
let mut struct_map = BTreeMap::new();

for (field_key, param_type) in fields {
let field_value = decode_value(field_iterator, param_type);

struct_map.insert(field_key.to_owned(), field_value);
}

PrintableValue::Struct(struct_map)
}
PrintableType::Function { env, .. } => {
let field_element = field_iterator.next().unwrap();
let func_ref = PrintableValue::Field(field_element);
// we want to consume the fields from the environment, but for now they are not actually printed
decode_value(field_iterator, env);
func_ref
}
PrintableType::MutableReference { typ } => {
// we decode the reference, but it's not really used for printing
decode_value(field_iterator, typ)
}
PrintableType::Unit => PrintableValue::Field(F::zero()),
}
}

pub fn decode_string_value<F: AcirField>(field_elements: &[F]) -> String {
// TODO: Replace with `into` when Char is supported
let string_as_slice = vecmap(field_elements, |e| {
let mut field_as_bytes = e.to_be_bytes();
let char_byte = field_as_bytes.pop().unwrap(); // A character in a string is represented by a u8, thus we just want the last byte of the element
assert!(field_as_bytes.into_iter().all(|b| b == 0)); // Assert that the rest of the field element's bytes are empty
char_byte
});

let final_string = str::from_utf8(&string_as_slice).unwrap();
final_string.to_owned()
}

#[cfg(test)]
mod tests {
use acvm::FieldElement;

use crate::{PrintableType, PrintableValue, PrintableValueDisplay};
use super::{PrintableType, PrintableValue, PrintableValueDisplay};

#[test]
fn printable_value_display_to_string_without_interpolations() {
Expand Down
3 changes: 1 addition & 2 deletions tooling/debugger/src/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ use acvm::{
AcirField, FieldElement,
};
use nargo::{
foreign_calls::{DefaultForeignCallExecutor, ForeignCallExecutor},
foreign_calls::{DefaultForeignCallExecutor, ForeignCallError, ForeignCallExecutor},
PrintOutput,
};
use noirc_artifacts::debug::{DebugArtifact, DebugVars, StackFrame};
use noirc_errors::debug_info::{DebugFnId, DebugVarId};
use noirc_printable_type::ForeignCallError;

pub(crate) enum DebugForeignCall {
VarAssign,
Expand Down
1 change: 1 addition & 0 deletions tooling/nargo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ rayon.workspace = true
jsonrpc.workspace = true
rand.workspace = true
serde.workspace = true
serde_json.workspace = true
walkdir = "2.5.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand Down
3 changes: 2 additions & 1 deletion tooling/nargo/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ use noirc_errors::{
pub use noirc_errors::Location;

use noirc_driver::CrateName;
use noirc_printable_type::ForeignCallError;
use thiserror::Error;

use crate::foreign_calls::ForeignCallError;

/// Errors covering situations where a package cannot be compiled.
#[derive(Debug, Error)]
pub enum CompileError {
Expand Down
4 changes: 2 additions & 2 deletions tooling/nargo/src/foreign_calls/mocker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use acvm::{
pwg::ForeignCallWaitInfo,
AcirField,
};
use noirc_printable_type::{decode_string_value, ForeignCallError};
use noirc_abi::decode_string_value;
use serde::{Deserialize, Serialize};

use super::{ForeignCall, ForeignCallExecutor};
use super::{ForeignCall, ForeignCallError, ForeignCallExecutor};

/// This struct represents an oracle mock. It can be used for testing programs that use oracles.
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down
20 changes: 19 additions & 1 deletion tooling/nargo/src/foreign_calls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use std::path::PathBuf;

use acvm::{acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo, AcirField};
use mocker::MockForeignCallExecutor;
use noirc_printable_type::ForeignCallError;
use print::{PrintForeignCallExecutor, PrintOutput};
use rand::Rng;
use rpc::RPCForeignCallExecutor;
use serde::{Deserialize, Serialize};
use thiserror::Error;

pub(crate) mod mocker;
pub(crate) mod print;
Expand Down Expand Up @@ -64,6 +64,24 @@ impl ForeignCall {
}
}

#[derive(Debug, Error)]
pub enum ForeignCallError {
#[error("No handler could be found for foreign call `{0}`")]
NoHandler(String),

#[error("Foreign call inputs needed for execution are missing")]
MissingForeignCallInputs,

#[error("Could not parse PrintableType argument. {0}")]
ParsingError(#[from] serde_json::Error),

#[error("Failed calling external resolver. {0}")]
ExternalResolverError(#[from] jsonrpc::Error),

#[error("Assert message resolved after an unsatisfied constrain. {0}")]
ResolvedAssertMessage(String),
}

#[derive(Debug, Default)]
pub struct DefaultForeignCallExecutor<'a, F> {
/// The executor for any [`ForeignCall::Print`] calls.
Expand Down
Loading

0 comments on commit 79c894e

Please sign in to comment.