From f630db54757375b058a2e35fb58edcd60f962ff4 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 22 Apr 2024 17:17:04 +0000 Subject: [PATCH 01/25] wip: first iteration of dynamic assert messages v2 --- avm-transpiler/Cargo.lock | 1 + noir/noir-repo/Cargo.lock | 1 + noir/noir-repo/Cargo.toml | 1 + .../acvm-repo/acir/src/circuit/mod.rs | 55 +- .../acvm-repo/acvm/src/compiler/mod.rs | 6 +- .../acvm-repo/acvm/src/pwg/arithmetic.rs | 4 + .../acvm-repo/acvm/src/pwg/blackbox/range.rs | 1 + .../acvm-repo/acvm/src/pwg/brillig.rs | 30 +- .../acvm-repo/acvm/src/pwg/directives/mod.rs | 1 + noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 94 +- noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 1264 ++++++++--------- .../acvm-repo/acvm_js/src/execute.rs | 33 +- .../acvm-repo/brillig/src/opcodes.rs | 9 +- .../noir-repo/acvm-repo/brillig_vm/src/lib.rs | 10 +- .../compiler/noirc_driver/src/abi_gen.rs | 11 +- .../compiler/noirc_driver/src/lib.rs | 2 + .../compiler/noirc_errors/Cargo.toml | 4 +- .../src/brillig/brillig_gen/brillig_block.rs | 56 +- .../brillig/brillig_gen/brillig_directive.rs | 3 + .../noirc_evaluator/src/brillig/brillig_ir.rs | 12 +- .../src/brillig/brillig_ir/artifact.rs | 14 +- .../brillig_ir/codegen_control_flow.rs | 69 +- .../src/brillig/brillig_ir/debug_show.rs | 9 +- .../src/brillig/brillig_ir/entry_point.rs | 6 +- .../src/brillig/brillig_ir/instructions.rs | 8 +- .../compiler/noirc_evaluator/src/errors.rs | 20 +- .../compiler/noirc_evaluator/src/ssa.rs | 22 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 20 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 19 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 33 +- .../src/ssa/function_builder/mod.rs | 3 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 38 +- .../src/ssa/ir/instruction/constrain.rs | 2 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 12 +- .../src/ssa/opt/defunctionalize.rs | 29 +- .../src/ssa/ssa_gen/context.rs | 12 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 66 +- .../src/ssa/ssa_gen/program.rs | 1 + .../src/hir/resolution/resolver.rs | 49 +- .../src/monomorphization/ast.rs | 6 +- .../src/monomorphization/mod.rs | 9 +- .../compiler/noirc_frontend/src/tests.rs | 2 +- noir/noir-repo/noir_stdlib/src/internal.nr | 8 - .../noir-repo/tooling/debugger/src/context.rs | 1 + noir/noir-repo/tooling/nargo/src/errors.rs | 117 +- .../tooling/nargo/src/ops/execute.rs | 55 +- noir/noir-repo/tooling/nargo/src/ops/test.rs | 23 +- .../tooling/nargo_cli/src/cli/execute_cmd.rs | 4 +- .../tooling/nargo_cli/src/cli/fs/inputs.rs | 1 + noir/noir-repo/tooling/noirc_abi/Cargo.toml | 1 + .../tooling/noirc_abi/src/input_parser/mod.rs | 1 + noir/noir-repo/tooling/noirc_abi/src/lib.rs | 35 +- 52 files changed, 1233 insertions(+), 1060 deletions(-) diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index 6fc2aa934f5b..e7ea90b346c5 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -1206,6 +1206,7 @@ dependencies = [ "num-traits", "serde", "serde_json", + "serde_with", "thiserror", "toml", ] diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index ee83f7f8ddf3..d172098831d6 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -3073,6 +3073,7 @@ dependencies = [ "num-traits", "serde", "serde_json", + "serde_with", "strum", "strum_macros", "thiserror", diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 5dd453415aac..66717a7f7dd9 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -107,6 +107,7 @@ chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default dirs = "4" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0" +serde_with = "3.2.0" smol_str = { version = "0.1.17", features = ["serde"] } thiserror = "1.0.21" toml = "0.7.2" diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index d655d136bc8c..9730d9fb7b63 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -3,7 +3,7 @@ pub mod brillig; pub mod directives; pub mod opcodes; -use crate::native_types::Witness; +use crate::native_types::{Expression, Witness}; pub use opcodes::Opcode; use thiserror::Error; @@ -70,7 +70,7 @@ pub struct Circuit { // TODO: These are only used for constraints that are explicitly created during code generation (such as index out of bounds on slices) // TODO: We should move towards having all the checks being evaluated in the same manner // TODO: as runtime assert messages specified by the user. This will also be a breaking change as the `Circuit` structure will change. - pub assert_messages: Vec<(OpcodeLocation, String)>, + pub assert_messages: Vec<(OpcodeLocation, AssertionPayload)>, /// States whether the backend should use a SNARK recursion friendly prover. /// If implemented by a backend, this means that proofs generated with this circuit @@ -78,18 +78,14 @@ pub struct Circuit { pub recursive: bool, } -impl Circuit { - /// Returns the assert message associated with the provided [`OpcodeLocation`]. - /// Returns `None` if no such assert message exists. - pub fn get_assert_message(&self, opcode_location: OpcodeLocation) -> Option<&str> { - self.assert_messages - .iter() - .find(|(loc, _)| *loc == opcode_location) - .map(|(_, message)| message.as_str()) - } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssertionPayload { + StaticString(String), + Expression(/* error_id */ usize, Vec), + BrilligOutput(/* error_id */ usize), } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// The opcode location for a call to a separate ACIR circuit /// This includes the function index of the caller within a [program][Program] /// and the index in the callers ACIR to the specific call opcode. @@ -99,6 +95,41 @@ pub struct ResolvedOpcodeLocation { pub opcode_location: OpcodeLocation, } +impl std::fmt::Display for ResolvedOpcodeLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.acir_function_index, self.opcode_location) + } +} + +#[derive(Error, Debug)] +pub enum ResolvedOpcodeLocationFromStrError { + #[error(transparent)] + OpcodeLocationFromStrError(#[from] OpcodeLocationFromStrError), + #[error("Invalid resolved opcode location string: {0}")] + InvalidResolvedOpcodeLocationString(String), +} + +/// The implementation of display and FromStr allows serializing and deserializing a OpcodeLocation to a string. +/// This is useful when used as key in a map that has to be serialized to JSON/TOML, for example when mapping an opcode to its metadata. +impl FromStr for ResolvedOpcodeLocation { + type Err = ResolvedOpcodeLocationFromStrError; + fn from_str(s: &str) -> Result { + let parts: Vec<_> = s.split(':').collect(); + + if parts.len() != 2 { + return Err(ResolvedOpcodeLocationFromStrError::InvalidResolvedOpcodeLocationString( + s.to_string(), + )); + } + + let acir_function_index = parts[0].parse().map_err(|_| { + OpcodeLocationFromStrError::InvalidOpcodeLocationString(parts[0].to_string()) + })?; + let opcode_location = parts[1].parse()?; + Ok(ResolvedOpcodeLocation { acir_function_index, opcode_location }) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// Opcodes are locatable so that callers can /// map opcodes to debug information related to their context. diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/mod.rs index 6543c70958be..436db648ea8a 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use acir::circuit::{Circuit, ExpressionWidth, OpcodeLocation}; +use acir::circuit::{AssertionPayload, Circuit, ExpressionWidth, OpcodeLocation}; // The various passes that we can use over ACIR mod optimizers; @@ -54,9 +54,9 @@ impl AcirTransformationMap { } fn transform_assert_messages( - assert_messages: Vec<(OpcodeLocation, String)>, + assert_messages: Vec<(OpcodeLocation, AssertionPayload)>, map: &AcirTransformationMap, -) -> Vec<(OpcodeLocation, String)> { +) -> Vec<(OpcodeLocation, AssertionPayload)> { assert_messages .into_iter() .flat_map(|(location, message)| { diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs index dc9e13d44b63..b971e4a0efb9 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs @@ -53,6 +53,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -81,6 +82,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -96,6 +98,7 @@ impl ExpressionSolver { if !(a + b + opcode.q_c).is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) @@ -113,6 +116,7 @@ impl ExpressionSolver { if !total_sum.is_zero() { Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }) } else { Ok(()) diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs index 2afe820b6362..aac50b32fc85 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs @@ -12,6 +12,7 @@ pub(crate) fn solve_range_opcode( if w_value.num_bits() > input.num_bits { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } Ok(()) diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 67faf7f50073..87c6f8e43a66 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -15,7 +15,7 @@ use brillig_vm::{FailureReason, MemoryValue, VMStatus, VM}; use crate::{pwg::OpcodeNotSolvable, OpcodeResolutionError}; -use super::{get_value, insert_value, memory_op::MemoryOpSolver}; +use super::{get_value, insert_value, memory_op::MemoryOpSolver, ResolvedAssertionPayload}; #[derive(Debug)] pub enum BrilligSolverStatus { @@ -193,32 +193,34 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { VMStatus::Finished { .. } => Ok(BrilligSolverStatus::Finished), VMStatus::InProgress => Ok(BrilligSolverStatus::InProgress), VMStatus::Failure { reason, call_stack } => { - let message = match reason { - FailureReason::RuntimeError { message } => Some(message), + let payload = match reason { + FailureReason::RuntimeError { message } => { + Some(ResolvedAssertionPayload::String(message)) + } FailureReason::Trap { revert_data_offset, revert_data_size } => { // Since noir can only revert with strings currently, we can parse return data as a string if revert_data_size == 0 { None } else { let memory = self.vm.get_memory(); - let bytes = memory + let mut fields: Vec<_> = memory [revert_data_offset..(revert_data_offset + revert_data_size)] .iter() - .map(|memory_value| { - memory_value - .try_into() - .expect("Assert message character is not a byte") - }) + .map(|memory_value| memory_value.to_field()) .collect(); - Some( - String::from_utf8(bytes) - .expect("Assert message is not valid UTF-8"), - ) + let revert_data_type_id = fields + .remove(0) + .try_to_u64() + .expect("Error id doesn't fit in a u64") + .try_into() + .expect("Error id doesn't fit in a usize"); + + Some(ResolvedAssertionPayload::Raw(revert_data_type_id, fields)) } } }; Err(OpcodeResolutionError::BrilligFunctionFailed { - message, + payload, call_stack: call_stack .iter() .map(|brillig_index| OpcodeLocation::Brillig { diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/directives/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/directives/mod.rs index ee544521fc7c..db79379a3748 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/directives/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/directives/mod.rs @@ -26,6 +26,7 @@ pub(crate) fn solve_directives( if b.len() < decomposed_integer.len() { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 652e173867ab..00c5924a082f 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -1,10 +1,12 @@ // Re-usable methods that backends can use to implement their PWG -use std::collections::HashMap; +use std::{any::Any, collections::HashMap}; use acir::{ brillig::ForeignCallResult, - circuit::{brillig::BrilligBytecode, opcodes::BlockId, Opcode, OpcodeLocation}, + circuit::{ + brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, Opcode, OpcodeLocation, + }, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, }; @@ -112,18 +114,30 @@ impl std::fmt::Display for ErrorLocation { } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ResolvedAssertionPayload { + String(String), + Raw(/*type_id:*/ usize, Vec), +} + #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum OpcodeResolutionError { #[error("Cannot solve opcode: {0}")] OpcodeNotSolvable(#[from] OpcodeNotSolvable), #[error("Cannot satisfy constraint")] - UnsatisfiedConstrain { opcode_location: ErrorLocation }, + UnsatisfiedConstrain { + opcode_location: ErrorLocation, + payload: Option, + }, #[error("Index out of bounds, array has size {array_size:?}, but index was {index:?}")] IndexOutOfBounds { opcode_location: ErrorLocation, index: u32, array_size: u32 }, #[error("Failed to solve blackbox function: {0}, reason: {1}")] BlackBoxFunctionFailed(BlackBoxFunc, String), - #[error("Failed to solve brillig function{}", .message.as_ref().map(|m| format!(", reason: {}", m)).unwrap_or_default())] - BrilligFunctionFailed { message: Option, call_stack: Vec }, + #[error("Failed to solve brillig function")] + BrilligFunctionFailed { + call_stack: Vec, + payload: Option, + }, #[error("Attempted to call `main` with a `Call` opcode")] AcirMainCallAttempted { opcode_location: ErrorLocation }, #[error("{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen")] @@ -168,6 +182,8 @@ pub struct ACVM<'a, B: BlackBoxFunctionSolver> { // Each unconstrained function referenced in the program unconstrained_functions: &'a [BrilligBytecode], + + assertion_payloads: &'a [(OpcodeLocation, AssertionPayload)], } impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { @@ -176,6 +192,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { opcodes: &'a [Opcode], initial_witness: WitnessMap, unconstrained_functions: &'a [BrilligBytecode], + assertion_payloads: &'a [(OpcodeLocation, AssertionPayload)], ) -> Self { let status = if opcodes.is_empty() { ACVMStatus::Solved } else { ACVMStatus::InProgress }; ACVM { @@ -190,6 +207,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { acir_call_counter: 0, acir_call_results: Vec::default(), unconstrained_functions, + assertion_payloads, } } @@ -366,14 +384,44 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { OpcodeResolutionError::IndexOutOfBounds { opcode_location: opcode_index, .. - } - | OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: opcode_index, } => { *opcode_index = ErrorLocation::Resolved(OpcodeLocation::Acir( self.instruction_pointer(), )); } + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: opcode_index, + payload: assertion_payload, + } => { + let location = OpcodeLocation::Acir(self.instruction_pointer()); + *opcode_index = ErrorLocation::Resolved(location); + *assertion_payload = + self.assertion_payloads.iter().find_map(|(loc, payload)| { + if location == *loc { + match payload { + AssertionPayload::StaticString(string) => { + Some(ResolvedAssertionPayload::String(string.clone())) + } + AssertionPayload::Expression(type_id, expression) => { + let elements: Result, _> = expression + .iter() + .map(|expr| get_value(expr, &self.witness_map)) + .collect(); + elements + .map(|fields| { + ResolvedAssertionPayload::Raw(*type_id, fields) + }) + .ok() + } + AssertionPayload::BrilligOutput(_) => unreachable!( + "Brillig output error type set in an ACIR opcode" + ), + } + } else { + None + } + }); + } // All other errors are thrown normally. _ => (), }; @@ -451,7 +499,34 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { self.instruction_pointer, )?, }; - match solver.solve()? { + + let result = solver.solve().map_err(|mut err| { + dbg!(&err); + match &mut err { + OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { + // Some brillig errors have static strings as payloads, we can resolve them here + let last_location = + call_stack.last().expect("Call stacks should have at least one item"); + let assertion_descriptor = + self.assertion_payloads.iter().find_map(|(loc, payload)| { + if loc == last_location { + Some(payload) + } else { + None + } + }); + + if let Some(AssertionPayload::StaticString(string)) = assertion_descriptor { + *payload = Some(ResolvedAssertionPayload::String(string.clone())); + } + + err + } + _ => err, + } + }); + + match result? { BrilligSolverStatus::ForeignCallWait(foreign_call) => { // Cache the current state of the solver self.brillig_solver = Some(solver); @@ -604,6 +679,7 @@ pub fn insert_value( if old_value != value_to_insert { return Err(OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Unresolved, + payload: None, }); } diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index f009e2c05b82..349aefec713e 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -17,635 +17,635 @@ use brillig_vm::brillig::HeapValueType; // Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. -#[test] -fn inversion_brillig_oracle_equivalence() { - // Opcodes below describe the following: - // fn main(x : Field, y : pub Field) { - // let z = x + y; - // assert( 1/z == Oracle("inverse", x + y) ); - // } - // Also performs an unrelated equality check - // just for the sake of testing multiple brillig opcodes. - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - let w_y = Witness(2); - let w_oracle = Witness(3); - let w_z = Witness(4); - let w_z_inverse = Witness(5); - let w_x_plus_y = Witness(6); - let w_equal_res = Witness(7); - - let equal_opcode = BrilligOpcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - // Input Register 0 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), // Input Register 1 - ], - // This tells the BrilligSolver which witnesses its output values correspond to - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input - BrilligOutputs::Simple(w_oracle), // Output Register 1 - BrilligOutputs::Simple(w_equal_res), // Output Register 2 - ], - bytecode: vec![ - BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(0), - size: 2, - offset: 0, - }, - equal_opcode, - // Oracles are named 'foreign calls' in brillig - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, - ], - predicate: None, - }; - - let opcodes = vec![ - Opcode::Brillig(brillig_data), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], - q_c: fe_0, - }), - // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), - Opcode::AssertZero(Expression { - mul_terms: vec![(fe_1, w_z, w_z_inverse)], - linear_combinations: vec![], - q_c: -fe_1, - }), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], - q_c: fe_0, - }), - ]; - - let witness_assignments = BTreeMap::from([ - (Witness(1), FieldElement::from(2u128)), - (Witness(2), FieldElement::from(3u128)), - ]) - .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - // use the partial witness generation solver with our acir program - let solver_status = acvm.solve(); - - assert!( - matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), - "should require foreign call response" - ); - assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed"); - - let foreign_call_wait_info: &ForeignCallWaitInfo = - acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); - assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - - // As caller of VM, need to resolve foreign calls - let foreign_call_result = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - // Alter Brillig oracle opcode with foreign call resolution - acvm.resolve_pending_foreign_call(foreign_call_result.into()); - - // After filling data request, continue solving - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - - // ACVM should be able to be finalized in `Solved` state. - acvm.finalize(); -} - -#[test] -fn double_inversion_brillig_oracle() { - // Opcodes below describe the following: - // fn main(x : Field, y : pub Field) { - // let z = x + y; - // let ij = i + j; - // assert( 1/z == Oracle("inverse", x + y) ); - // assert( 1/ij == Oracle("inverse", i + j) ); - // } - // Also performs an unrelated equality check - // just for the sake of testing multiple brillig opcodes. - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - let w_y = Witness(2); - let w_oracle = Witness(3); - let w_z = Witness(4); - let w_z_inverse = Witness(5); - let w_x_plus_y = Witness(6); - let w_equal_res = Witness(7); - let w_i = Witness(8); - let w_j = Witness(9); - let w_ij_oracle = Witness(10); - let w_i_plus_j = Witness(11); - - let equal_opcode = BrilligOpcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(4), - }; - - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - // Input Register 0 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), // Input Register 1 - BrilligInputs::Single(Expression { - // Input Register 2 - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], - q_c: fe_0, - }), - ], - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input - BrilligOutputs::Simple(w_oracle), // Output Register 1 - BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input - BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 - BrilligOutputs::Simple(w_equal_res), // Output Register 4 - ], - bytecode: vec![ - BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(0), - size: 3, - offset: 0, - }, - equal_opcode, - // Oracles are named 'foreign calls' in brillig - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 }, - ], - predicate: None, - }; - - let opcodes = vec![ - Opcode::Brillig(brillig_data), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], - q_c: fe_0, - }), - // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), - Opcode::AssertZero(Expression { - mul_terms: vec![(fe_1, w_z, w_z_inverse)], - linear_combinations: vec![], - q_c: -fe_1, - }), - Opcode::AssertZero(Expression { - mul_terms: vec![], - linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], - q_c: fe_0, - }), - ]; - - let witness_assignments = BTreeMap::from([ - (Witness(1), FieldElement::from(2u128)), - (Witness(2), FieldElement::from(3u128)), - (Witness(8), FieldElement::from(5u128)), - (Witness(9), FieldElement::from(10u128)), - ]) - .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - - // use the partial witness generation solver with our acir program - let solver_status = acvm.solve(); - assert!( - matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), - "should require foreign call response" - ); - assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); - - let foreign_call_wait_info: &ForeignCallWaitInfo = - acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); - assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - - let x_plus_y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - - // Resolve Brillig foreign call - acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); - - // After filling data request, continue solving - let solver_status = acvm.solve(); - assert!( - matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), - "should require foreign call response" - ); - assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); - - let foreign_call_wait_info = - acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); - assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - - let i_plus_j_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - assert_ne!(x_plus_y_inverse, i_plus_j_inverse); - - // Alter Brillig oracle opcode - acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); - - // After filling data request, continue solving - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - - // ACVM should be able to be finalized in `Solved` state. - acvm.finalize(); -} - -#[test] -fn oracle_dependent_execution() { - // This test ensures that we properly track the list of opcodes which still need to be resolved - // across any brillig foreign calls we may have to perform. - // - // Opcodes below describe the following: - // fn main(x : Field, y : pub Field) { - // assert(x == y); - // let x_inv = Oracle("inverse", x); - // let y_inv = Oracle("inverse", y); - // - // assert(x_inv == y_inv); - // } - // Also performs an unrelated equality check - // just for the sake of testing multiple brillig opcodes. - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - let w_y = Witness(2); - let w_x_inv = Witness(3); - let w_y_inv = Witness(4); - - let brillig_data = Brillig { - inputs: vec![ - BrilligInputs::Single(w_x.into()), // Input Register 0 - BrilligInputs::Single(Expression::default()), // Input Register 1 - BrilligInputs::Single(w_y.into()), // Input Register 2, - ], - outputs: vec![ - BrilligOutputs::Simple(w_x), // Output Register 0 - from input - BrilligOutputs::Simple(w_y_inv), // Output Register 1 - BrilligOutputs::Simple(w_y), // Output Register 2 - from input - BrilligOutputs::Simple(w_y_inv), // Output Register 3 - ], - bytecode: vec![ - BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(0), - size: 3, - offset: 0, - }, - // Oracles are named 'foreign calls' in brillig - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], - input_value_types: vec![HeapValueType::field()], - }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 }, - ], - predicate: None, - }; - - // This equality check can be executed immediately before resolving any foreign calls. - let equality_check = Expression { - mul_terms: vec![], - linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }; - - // This equality check relies on the outputs of the Brillig call. - // It then cannot be solved until the foreign calls are resolved. - let inverse_equality_check = Expression { - mul_terms: vec![], - linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], - q_c: fe_0, - }; - - let opcodes = vec![ - Opcode::AssertZero(equality_check), - Opcode::Brillig(brillig_data), - Opcode::AssertZero(inverse_equality_check), - ]; - - let witness_assignments = - BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - - // use the partial witness generation solver with our acir program - let solver_status = acvm.solve(); - assert!( - matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), - "should require foreign call response" - ); - assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); - - let foreign_call_wait_info: &ForeignCallWaitInfo = - acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); - assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - - // Resolve Brillig foreign call - let x_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - acvm.resolve_pending_foreign_call(x_inverse.into()); - - // After filling data request, continue solving - let solver_status = acvm.solve(); - assert!( - matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), - "should require foreign call response" - ); - assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); - - let foreign_call_wait_info: &ForeignCallWaitInfo = - acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); - assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - - // Resolve Brillig foreign call - let y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - acvm.resolve_pending_foreign_call(y_inverse.into()); - - // We've resolved all the brillig foreign calls so we should be able to complete execution now. - - // After filling data request, continue solving - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - - // ACVM should be able to be finalized in `Solved` state. - acvm.finalize(); -} - -#[test] -fn brillig_oracle_predicate() { - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - let w_y = Witness(2); - let w_oracle = Witness(3); - let w_x_plus_y = Witness(4); - let w_equal_res = Witness(5); - let w_lt_res = Witness(6); - - let equal_opcode = BrilligOpcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - - let brillig_opcode = Opcode::Brillig(Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression::default()), - ], - outputs: vec![ - BrilligOutputs::Simple(w_x_plus_y), - BrilligOutputs::Simple(w_oracle), - BrilligOutputs::Simple(w_equal_res), - BrilligOutputs::Simple(w_lt_res), - ], - bytecode: vec![ - BrilligOpcode::CalldataCopy { - destination_address: MemoryAddress(0), - size: 2, - offset: 0, - }, - equal_opcode, - // Oracles are named 'foreign calls' in brillig - BrilligOpcode::ForeignCall { - function: "invert".into(), - destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], - destination_value_types: vec![HeapValueType::field()], - inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], - input_value_types: vec![HeapValueType::field()], - }, - ], - predicate: Some(Expression::default()), - }); - - let opcodes = vec![brillig_opcode]; - - let witness_assignments = BTreeMap::from([ - (Witness(1), FieldElement::from(2u128)), - (Witness(2), FieldElement::from(3u128)), - ]) - .into(); - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - - // ACVM should be able to be finalized in `Solved` state. - acvm.finalize(); -} - -#[test] -fn unsatisfied_opcode_resolved() { - let a = Witness(0); - let b = Witness(1); - let c = Witness(2); - let d = Witness(3); - - // a = b + c + d; - let opcode_a = Expression { - mul_terms: vec![], - linear_combinations: vec![ - (FieldElement::one(), a), - (-FieldElement::one(), b), - (-FieldElement::one(), c), - (-FieldElement::one(), d), - ], - q_c: FieldElement::zero(), - }; - - let mut values = WitnessMap::new(); - values.insert(a, FieldElement::from(4_i128)); - values.insert(b, FieldElement::from(2_i128)); - values.insert(c, FieldElement::from(1_i128)); - values.insert(d, FieldElement::from(2_i128)); - - let opcodes = vec![Opcode::AssertZero(opcode_a)]; - let unconstrained_functions = vec![]; - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); - let solver_status = acvm.solve(); - assert_eq!( - solver_status, - ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), - }), - "The first opcode is not satisfiable, expected an error indicating this" - ); -} - -#[test] -fn unsatisfied_opcode_resolved_brillig() { - let a = Witness(0); - let b = Witness(1); - let c = Witness(2); - let d = Witness(3); - - let fe_1 = FieldElement::one(); - let fe_0 = FieldElement::zero(); - let w_x = Witness(4); - let w_y = Witness(5); - let w_result = Witness(6); - - let calldata_copy_opcode = - BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; - - let equal_opcode = BrilligOpcode::BinaryFieldOp { - op: BinaryFieldOp::Equals, - lhs: MemoryAddress::from(0), - rhs: MemoryAddress::from(1), - destination: MemoryAddress::from(2), - }; - // Jump pass the trap if the values are equal, else - // jump to the trap - let location_of_stop = 3; - - let jmp_if_opcode = - BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; - - let trap_opcode = BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; - let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; - - let brillig_opcode = Opcode::Brillig(Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x)], - q_c: fe_0, - }), - BrilligInputs::Single(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_y)], - q_c: fe_0, - }), - ], - outputs: vec![BrilligOutputs::Simple(w_result)], - bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], - predicate: Some(Expression::one()), - }); - - let opcode_a = Expression { - mul_terms: vec![], - linear_combinations: vec![ - (FieldElement::one(), a), - (-FieldElement::one(), b), - (-FieldElement::one(), c), - (-FieldElement::one(), d), - ], - q_c: FieldElement::zero(), - }; - - let mut values = WitnessMap::new(); - values.insert(a, FieldElement::from(4_i128)); - values.insert(b, FieldElement::from(2_i128)); - values.insert(c, FieldElement::from(1_i128)); - values.insert(d, FieldElement::from(2_i128)); - values.insert(w_x, FieldElement::from(0_i128)); - values.insert(w_y, FieldElement::from(1_i128)); - values.insert(w_result, FieldElement::from(0_i128)); - - let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; - let unconstrained_functions = vec![]; - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); - let solver_status = acvm.solve(); - assert_eq!( - solver_status, - ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { - message: None, - call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] - }), - "The first opcode is not satisfiable, expected an error indicating this" - ); -} - -#[test] -fn memory_operations() { - let initial_witness = WitnessMap::from(BTreeMap::from_iter([ - (Witness(1), FieldElement::from(1u128)), - (Witness(2), FieldElement::from(2u128)), - (Witness(3), FieldElement::from(3u128)), - (Witness(4), FieldElement::from(4u128)), - (Witness(5), FieldElement::from(5u128)), - (Witness(6), FieldElement::from(4u128)), - ])); - - let block_id = BlockId(0); - - let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() }; - - let read_op = Opcode::MemoryOp { - block_id, - op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)), - predicate: None, - }; - - let expression = Opcode::AssertZero(Expression { - mul_terms: Vec::new(), - linear_combinations: vec![ - (FieldElement::one(), Witness(7)), - (-FieldElement::one(), Witness(8)), - ], - q_c: FieldElement::one(), - }); - - let opcodes = vec![init, read_op, expression]; - let unconstrained_functions = vec![]; - let mut acvm = - ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions); - let solver_status = acvm.solve(); - assert_eq!(solver_status, ACVMStatus::Solved); - let witness_map = acvm.finalize(); - - assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); -} +// #[test] +// fn inversion_brillig_oracle_equivalence() { +// // Opcodes below describe the following: +// // fn main(x : Field, y : pub Field) { +// // let z = x + y; +// // assert( 1/z == Oracle("inverse", x + y) ); +// // } +// // Also performs an unrelated equality check +// // just for the sake of testing multiple brillig opcodes. +// let fe_0 = FieldElement::zero(); +// let fe_1 = FieldElement::one(); +// let w_x = Witness(1); +// let w_y = Witness(2); +// let w_oracle = Witness(3); +// let w_z = Witness(4); +// let w_z_inverse = Witness(5); +// let w_x_plus_y = Witness(6); +// let w_equal_res = Witness(7); + +// let equal_opcode = BrilligOpcode::BinaryFieldOp { +// op: BinaryFieldOp::Equals, +// lhs: MemoryAddress::from(0), +// rhs: MemoryAddress::from(1), +// destination: MemoryAddress::from(2), +// }; + +// let brillig_data = Brillig { +// inputs: vec![ +// BrilligInputs::Single(Expression { +// // Input Register 0 +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], +// q_c: fe_0, +// }), +// BrilligInputs::Single(Expression::default()), // Input Register 1 +// ], +// // This tells the BrilligSolver which witnesses its output values correspond to +// outputs: vec![ +// BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input +// BrilligOutputs::Simple(w_oracle), // Output Register 1 +// BrilligOutputs::Simple(w_equal_res), // Output Register 2 +// ], +// bytecode: vec![ +// BrilligOpcode::CalldataCopy { +// destination_address: MemoryAddress(0), +// size: 2, +// offset: 0, +// }, +// equal_opcode, +// // Oracles are named 'foreign calls' in brillig +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], +// input_value_types: vec![HeapValueType::field()], +// }, +// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, +// ], +// predicate: None, +// }; + +// let opcodes = vec![ +// Opcode::Brillig(brillig_data), +// Opcode::AssertZero(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], +// q_c: fe_0, +// }), +// // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), +// Opcode::AssertZero(Expression { +// mul_terms: vec![(fe_1, w_z, w_z_inverse)], +// linear_combinations: vec![], +// q_c: -fe_1, +// }), +// Opcode::AssertZero(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], +// q_c: fe_0, +// }), +// ]; + +// let witness_assignments = BTreeMap::from([ +// (Witness(1), FieldElement::from(2u128)), +// (Witness(2), FieldElement::from(3u128)), +// ]) +// .into(); +// let unconstrained_functions = vec![]; +// let mut acvm = +// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); +// // use the partial witness generation solver with our acir program +// let solver_status = acvm.solve(); + +// assert!( +// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), +// "should require foreign call response" +// ); +// assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed"); + +// let foreign_call_wait_info: &ForeignCallWaitInfo = +// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); +// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + +// // As caller of VM, need to resolve foreign calls +// let foreign_call_result = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); +// // Alter Brillig oracle opcode with foreign call resolution +// acvm.resolve_pending_foreign_call(foreign_call_result.into()); + +// // After filling data request, continue solving +// let solver_status = acvm.solve(); +// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + +// // ACVM should be able to be finalized in `Solved` state. +// acvm.finalize(); +// } + +// #[test] +// fn double_inversion_brillig_oracle() { +// // Opcodes below describe the following: +// // fn main(x : Field, y : pub Field) { +// // let z = x + y; +// // let ij = i + j; +// // assert( 1/z == Oracle("inverse", x + y) ); +// // assert( 1/ij == Oracle("inverse", i + j) ); +// // } +// // Also performs an unrelated equality check +// // just for the sake of testing multiple brillig opcodes. +// let fe_0 = FieldElement::zero(); +// let fe_1 = FieldElement::one(); +// let w_x = Witness(1); +// let w_y = Witness(2); +// let w_oracle = Witness(3); +// let w_z = Witness(4); +// let w_z_inverse = Witness(5); +// let w_x_plus_y = Witness(6); +// let w_equal_res = Witness(7); +// let w_i = Witness(8); +// let w_j = Witness(9); +// let w_ij_oracle = Witness(10); +// let w_i_plus_j = Witness(11); + +// let equal_opcode = BrilligOpcode::BinaryFieldOp { +// op: BinaryFieldOp::Equals, +// lhs: MemoryAddress::from(0), +// rhs: MemoryAddress::from(1), +// destination: MemoryAddress::from(4), +// }; + +// let brillig_data = Brillig { +// inputs: vec![ +// BrilligInputs::Single(Expression { +// // Input Register 0 +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], +// q_c: fe_0, +// }), +// BrilligInputs::Single(Expression::default()), // Input Register 1 +// BrilligInputs::Single(Expression { +// // Input Register 2 +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], +// q_c: fe_0, +// }), +// ], +// outputs: vec![ +// BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input +// BrilligOutputs::Simple(w_oracle), // Output Register 1 +// BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input +// BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 +// BrilligOutputs::Simple(w_equal_res), // Output Register 4 +// ], +// bytecode: vec![ +// BrilligOpcode::CalldataCopy { +// destination_address: MemoryAddress(0), +// size: 3, +// offset: 0, +// }, +// equal_opcode, +// // Oracles are named 'foreign calls' in brillig +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], +// input_value_types: vec![HeapValueType::field()], +// }, +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], +// input_value_types: vec![HeapValueType::field()], +// }, +// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 }, +// ], +// predicate: None, +// }; + +// let opcodes = vec![ +// Opcode::Brillig(brillig_data), +// Opcode::AssertZero(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], +// q_c: fe_0, +// }), +// // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), +// Opcode::AssertZero(Expression { +// mul_terms: vec![(fe_1, w_z, w_z_inverse)], +// linear_combinations: vec![], +// q_c: -fe_1, +// }), +// Opcode::AssertZero(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], +// q_c: fe_0, +// }), +// ]; + +// let witness_assignments = BTreeMap::from([ +// (Witness(1), FieldElement::from(2u128)), +// (Witness(2), FieldElement::from(3u128)), +// (Witness(8), FieldElement::from(5u128)), +// (Witness(9), FieldElement::from(10u128)), +// ]) +// .into(); +// let unconstrained_functions = vec![]; +// let mut acvm = +// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + +// // use the partial witness generation solver with our acir program +// let solver_status = acvm.solve(); +// assert!( +// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), +// "should require foreign call response" +// ); +// assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + +// let foreign_call_wait_info: &ForeignCallWaitInfo = +// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); +// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + +// let x_plus_y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + +// // Resolve Brillig foreign call +// acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); + +// // After filling data request, continue solving +// let solver_status = acvm.solve(); +// assert!( +// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), +// "should require foreign call response" +// ); +// assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + +// let foreign_call_wait_info = +// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); +// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + +// let i_plus_j_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); +// assert_ne!(x_plus_y_inverse, i_plus_j_inverse); + +// // Alter Brillig oracle opcode +// acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); + +// // After filling data request, continue solving +// let solver_status = acvm.solve(); +// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + +// // ACVM should be able to be finalized in `Solved` state. +// acvm.finalize(); +// } + +// #[test] +// fn oracle_dependent_execution() { +// // This test ensures that we properly track the list of opcodes which still need to be resolved +// // across any brillig foreign calls we may have to perform. +// // +// // Opcodes below describe the following: +// // fn main(x : Field, y : pub Field) { +// // assert(x == y); +// // let x_inv = Oracle("inverse", x); +// // let y_inv = Oracle("inverse", y); +// // +// // assert(x_inv == y_inv); +// // } +// // Also performs an unrelated equality check +// // just for the sake of testing multiple brillig opcodes. +// let fe_0 = FieldElement::zero(); +// let fe_1 = FieldElement::one(); +// let w_x = Witness(1); +// let w_y = Witness(2); +// let w_x_inv = Witness(3); +// let w_y_inv = Witness(4); + +// let brillig_data = Brillig { +// inputs: vec![ +// BrilligInputs::Single(w_x.into()), // Input Register 0 +// BrilligInputs::Single(Expression::default()), // Input Register 1 +// BrilligInputs::Single(w_y.into()), // Input Register 2, +// ], +// outputs: vec![ +// BrilligOutputs::Simple(w_x), // Output Register 0 - from input +// BrilligOutputs::Simple(w_y_inv), // Output Register 1 +// BrilligOutputs::Simple(w_y), // Output Register 2 - from input +// BrilligOutputs::Simple(w_y_inv), // Output Register 3 +// ], +// bytecode: vec![ +// BrilligOpcode::CalldataCopy { +// destination_address: MemoryAddress(0), +// size: 3, +// offset: 0, +// }, +// // Oracles are named 'foreign calls' in brillig +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], +// input_value_types: vec![HeapValueType::field()], +// }, +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], +// input_value_types: vec![HeapValueType::field()], +// }, +// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 }, +// ], +// predicate: None, +// }; + +// // This equality check can be executed immediately before resolving any foreign calls. +// let equality_check = Expression { +// mul_terms: vec![], +// linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], +// q_c: fe_0, +// }; + +// // This equality check relies on the outputs of the Brillig call. +// // It then cannot be solved until the foreign calls are resolved. +// let inverse_equality_check = Expression { +// mul_terms: vec![], +// linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], +// q_c: fe_0, +// }; + +// let opcodes = vec![ +// Opcode::AssertZero(equality_check), +// Opcode::Brillig(brillig_data), +// Opcode::AssertZero(inverse_equality_check), +// ]; + +// let witness_assignments = +// BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); +// let unconstrained_functions = vec![]; +// let mut acvm = +// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); + +// // use the partial witness generation solver with our acir program +// let solver_status = acvm.solve(); +// assert!( +// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), +// "should require foreign call response" +// ); +// assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + +// let foreign_call_wait_info: &ForeignCallWaitInfo = +// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); +// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + +// // Resolve Brillig foreign call +// let x_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); +// acvm.resolve_pending_foreign_call(x_inverse.into()); + +// // After filling data request, continue solving +// let solver_status = acvm.solve(); +// assert!( +// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), +// "should require foreign call response" +// ); +// assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + +// let foreign_call_wait_info: &ForeignCallWaitInfo = +// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); +// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + +// // Resolve Brillig foreign call +// let y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); +// acvm.resolve_pending_foreign_call(y_inverse.into()); + +// // We've resolved all the brillig foreign calls so we should be able to complete execution now. + +// // After filling data request, continue solving +// let solver_status = acvm.solve(); +// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + +// // ACVM should be able to be finalized in `Solved` state. +// acvm.finalize(); +// } + +// #[test] +// fn brillig_oracle_predicate() { +// let fe_0 = FieldElement::zero(); +// let fe_1 = FieldElement::one(); +// let w_x = Witness(1); +// let w_y = Witness(2); +// let w_oracle = Witness(3); +// let w_x_plus_y = Witness(4); +// let w_equal_res = Witness(5); +// let w_lt_res = Witness(6); + +// let equal_opcode = BrilligOpcode::BinaryFieldOp { +// op: BinaryFieldOp::Equals, +// lhs: MemoryAddress::from(0), +// rhs: MemoryAddress::from(1), +// destination: MemoryAddress::from(2), +// }; + +// let brillig_opcode = Opcode::Brillig(Brillig { +// inputs: vec![ +// BrilligInputs::Single(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], +// q_c: fe_0, +// }), +// BrilligInputs::Single(Expression::default()), +// ], +// outputs: vec![ +// BrilligOutputs::Simple(w_x_plus_y), +// BrilligOutputs::Simple(w_oracle), +// BrilligOutputs::Simple(w_equal_res), +// BrilligOutputs::Simple(w_lt_res), +// ], +// bytecode: vec![ +// BrilligOpcode::CalldataCopy { +// destination_address: MemoryAddress(0), +// size: 2, +// offset: 0, +// }, +// equal_opcode, +// // Oracles are named 'foreign calls' in brillig +// BrilligOpcode::ForeignCall { +// function: "invert".into(), +// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], +// destination_value_types: vec![HeapValueType::field()], +// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], +// input_value_types: vec![HeapValueType::field()], +// }, +// ], +// predicate: Some(Expression::default()), +// }); + +// let opcodes = vec![brillig_opcode]; + +// let witness_assignments = BTreeMap::from([ +// (Witness(1), FieldElement::from(2u128)), +// (Witness(2), FieldElement::from(3u128)), +// ]) +// .into(); +// let unconstrained_functions = vec![]; +// let mut acvm = +// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); +// let solver_status = acvm.solve(); +// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + +// // ACVM should be able to be finalized in `Solved` state. +// acvm.finalize(); +// } + +// #[test] +// fn unsatisfied_opcode_resolved() { +// let a = Witness(0); +// let b = Witness(1); +// let c = Witness(2); +// let d = Witness(3); + +// // a = b + c + d; +// let opcode_a = Expression { +// mul_terms: vec![], +// linear_combinations: vec![ +// (FieldElement::one(), a), +// (-FieldElement::one(), b), +// (-FieldElement::one(), c), +// (-FieldElement::one(), d), +// ], +// q_c: FieldElement::zero(), +// }; + +// let mut values = WitnessMap::new(); +// values.insert(a, FieldElement::from(4_i128)); +// values.insert(b, FieldElement::from(2_i128)); +// values.insert(c, FieldElement::from(1_i128)); +// values.insert(d, FieldElement::from(2_i128)); + +// let opcodes = vec![Opcode::AssertZero(opcode_a)]; +// let unconstrained_functions = vec![]; +// let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); +// let solver_status = acvm.solve(); +// assert_eq!( +// solver_status, +// ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { +// opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), +// }), +// "The first opcode is not satisfiable, expected an error indicating this" +// ); +// } + +// #[test] +// fn unsatisfied_opcode_resolved_brillig() { +// let a = Witness(0); +// let b = Witness(1); +// let c = Witness(2); +// let d = Witness(3); + +// let fe_1 = FieldElement::one(); +// let fe_0 = FieldElement::zero(); +// let w_x = Witness(4); +// let w_y = Witness(5); +// let w_result = Witness(6); + +// let calldata_copy_opcode = +// BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; + +// let equal_opcode = BrilligOpcode::BinaryFieldOp { +// op: BinaryFieldOp::Equals, +// lhs: MemoryAddress::from(0), +// rhs: MemoryAddress::from(1), +// destination: MemoryAddress::from(2), +// }; +// // Jump pass the trap if the values are equal, else +// // jump to the trap +// let location_of_stop = 3; + +// let jmp_if_opcode = +// BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; + +// let trap_opcode = BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; +// let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; + +// let brillig_opcode = Opcode::Brillig(Brillig { +// inputs: vec![ +// BrilligInputs::Single(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_x)], +// q_c: fe_0, +// }), +// BrilligInputs::Single(Expression { +// mul_terms: vec![], +// linear_combinations: vec![(fe_1, w_y)], +// q_c: fe_0, +// }), +// ], +// outputs: vec![BrilligOutputs::Simple(w_result)], +// bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], +// predicate: Some(Expression::one()), +// }); + +// let opcode_a = Expression { +// mul_terms: vec![], +// linear_combinations: vec![ +// (FieldElement::one(), a), +// (-FieldElement::one(), b), +// (-FieldElement::one(), c), +// (-FieldElement::one(), d), +// ], +// q_c: FieldElement::zero(), +// }; + +// let mut values = WitnessMap::new(); +// values.insert(a, FieldElement::from(4_i128)); +// values.insert(b, FieldElement::from(2_i128)); +// values.insert(c, FieldElement::from(1_i128)); +// values.insert(d, FieldElement::from(2_i128)); +// values.insert(w_x, FieldElement::from(0_i128)); +// values.insert(w_y, FieldElement::from(1_i128)); +// values.insert(w_result, FieldElement::from(0_i128)); + +// let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; +// let unconstrained_functions = vec![]; +// let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); +// let solver_status = acvm.solve(); +// assert_eq!( +// solver_status, +// ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { +// message: None, +// call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] +// }), +// "The first opcode is not satisfiable, expected an error indicating this" +// ); +// } + +// #[test] +// fn memory_operations() { +// let initial_witness = WitnessMap::from(BTreeMap::from_iter([ +// (Witness(1), FieldElement::from(1u128)), +// (Witness(2), FieldElement::from(2u128)), +// (Witness(3), FieldElement::from(3u128)), +// (Witness(4), FieldElement::from(4u128)), +// (Witness(5), FieldElement::from(5u128)), +// (Witness(6), FieldElement::from(4u128)), +// ])); + +// let block_id = BlockId(0); + +// let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() }; + +// let read_op = Opcode::MemoryOp { +// block_id, +// op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)), +// predicate: None, +// }; + +// let expression = Opcode::AssertZero(Expression { +// mul_terms: Vec::new(), +// linear_combinations: vec![ +// (FieldElement::one(), Witness(7)), +// (-FieldElement::one(), Witness(8)), +// ], +// q_c: FieldElement::one(), +// }); + +// let opcodes = vec![init, read_op, expression]; +// let unconstrained_functions = vec![]; +// let mut acvm = +// ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions); +// let solver_status = acvm.solve(); +// assert_eq!(solver_status, ACVMStatus::Solved); +// let witness_map = acvm.finalize(); + +// assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); +// } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 2fab684467e5..845c52a242c4 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -239,6 +239,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { &circuit.opcodes, initial_witness, self.unconstrained_functions, + &circuit.assert_messages, ); loop { @@ -250,39 +251,23 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { unreachable!("Execution should not stop while in `InProgress` state.") } ACVMStatus::Failure(error) => { - let (assert_message, call_stack): (Option<&str>, _) = match &error { + let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), + .. } | OpcodeResolutionError::IndexOutOfBounds { opcode_location: ErrorLocation::Resolved(opcode_location), .. - } => ( - circuit.get_assert_message(*opcode_location), - Some(vec![*opcode_location]), - ), - OpcodeResolutionError::BrilligFunctionFailed { - call_stack, - message, - } => { - let revert_message = message.as_ref().map(String::as_str); - let failing_opcode = call_stack - .last() - .expect("Brillig error call stacks cannot be empty"); - ( - revert_message.or(circuit.get_assert_message(*failing_opcode)), - Some(call_stack.clone()), - ) + } => Some(vec![*opcode_location]), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + Some(call_stack.clone()) } - _ => (None, None), - }; - - let error_string = match &assert_message { - Some(assert_message) => format!("Assertion failed: {}", assert_message), - None => error.to_string(), + _ => None, }; + // TODO add payload to JsExecutionError - return Err(JsExecutionError::new(error_string, call_stack).into()); + return Err(JsExecutionError::new(error.to_string(), call_stack).into()); } ACVMStatus::RequiresForeignCall(foreign_call) => { let result = diff --git a/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs b/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs index 468fd88db45d..a060aa83d411 100644 --- a/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs +++ b/noir/noir-repo/acvm-repo/brillig/src/opcodes.rs @@ -52,6 +52,12 @@ pub struct HeapArray { pub size: usize, } +impl Default for HeapArray { + fn default() -> Self { + Self { pointer: MemoryAddress(0), size: 0 } + } +} + /// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] pub struct HeapVector { @@ -179,8 +185,7 @@ pub enum BrilligOpcode { BlackBox(BlackBoxOp), /// Used to denote execution failure, returning data after the offset Trap { - revert_data_offset: usize, - revert_data_size: usize, + revert_data: HeapArray, }, /// Stop execution, returning data after the offset Stop { diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs index 75299670f948..7901c3135964 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs @@ -305,8 +305,12 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { } self.increment_program_counter() } - Opcode::Trap { revert_data_offset, revert_data_size } => { - self.trap(*revert_data_offset, *revert_data_size) + Opcode::Trap { revert_data } => { + if revert_data.size > 0 { + self.trap(self.memory.read_ref(revert_data.pointer).0, revert_data.size) + } else { + self.trap(0, 0) + } } Opcode::Stop { return_data_offset, return_data_size } => { self.finish(*return_data_offset, *return_data_size) @@ -715,7 +719,7 @@ mod tests { let jump_opcode = Opcode::Jump { location: 3 }; - let trap_opcode = Opcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; + let trap_opcode = Opcode::Trap { revert_data: HeapArray::default() }; let not_equal_cmp_opcode = Opcode::BinaryFieldOp { op: BinaryFieldOp::Equals, diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 86f10818dbcc..4deb6dc2e281 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; -use acvm::acir::native_types::Witness; +use acvm::acir::{circuit::ResolvedOpcodeLocation, native_types::Witness}; use iter_extended::{btree_map, vecmap}; use noirc_abi::{Abi, AbiParameter, AbiReturnType, AbiType, AbiValue}; use noirc_frontend::{ hir::Context, - hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern}, + hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern, types::Type}, macros_api::{HirExpression, HirLiteral}, node_interner::{FuncId, NodeInterner}, Visibility, @@ -20,12 +20,17 @@ pub(super) fn gen_abi( input_witnesses: Vec, return_witnesses: Vec, return_visibility: Visibility, + error_types: BTreeMap, ) -> Abi { let (parameters, return_type) = compute_function_abi(context, func_id); let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); let return_type = return_type .map(|typ| AbiReturnType { abi_type: typ, visibility: return_visibility.into() }); - Abi { parameters, return_type, param_witnesses, return_witnesses } + let error_types = error_types + .into_iter() + .map(|(loc, typ)| (loc, AbiType::from_type(context, &typ))) + .collect(); + Abi { parameters, return_type, param_witnesses, return_witnesses, error_types } } pub(super) fn compute_function_abi( diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 8a554879e9f8..135d95b473f6 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -529,6 +529,7 @@ pub fn compile_no_check( main_input_witnesses, main_return_witnesses, names, + error_types, } = create_program( program, options.show_ssa, @@ -543,6 +544,7 @@ pub fn compile_no_check( main_input_witnesses, main_return_witnesses, visibility, + error_types, ); let file_map = filter_relevant_files(&debug, &context.file_manager); diff --git a/noir/noir-repo/compiler/noirc_errors/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index da18399971ea..d4e0a55cf0dc 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -15,8 +15,8 @@ fm.workspace = true chumsky.workspace = true noirc_printable_type.workspace = true serde.workspace = true -serde_with = "3.2.0" +serde_with.workspace = true tracing.workspace = true flate2.workspace = true serde_json.workspace = true -base64.workspace = true \ No newline at end of file +base64.workspace = true diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 22407fc56959..1148303e3ba1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -5,7 +5,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; -use crate::ssa::ir::instruction::{ConstrainError, UserDefinedConstrainError}; +use crate::ssa::ir::instruction::ConstrainError; use crate::ssa::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -248,34 +248,6 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_binary(binary, dfg, result_var); } Instruction::Constrain(lhs, rhs, assert_message) => { - let (has_revert_data, static_assert_message) = if let Some(error) = assert_message { - match error.as_ref() { - ConstrainError::Intrinsic(string) => (false, Some(string.clone())), - ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { - (true, Some(string.clone())) - } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instruction, - )) => { - let Instruction::Call { func, arguments } = call_instruction else { - unreachable!("expected a call instruction") - }; - - let Value::Function(func_id) = &dfg[*func] else { - unreachable!("expected a function value") - }; - - self.convert_ssa_function_call(*func_id, arguments, dfg, &[]); - - // Dynamic assert messages are handled in the generated function call. - // We then don't need to attach one to the constrain instruction. - (false, None) - } - } - } else { - (false, None) - }; - let condition = SingleAddrVariable { address: self.brillig_context.allocate_register(), bit_size: 1, @@ -286,11 +258,27 @@ impl<'block> BrilligBlock<'block> { dfg, condition, ); - if has_revert_data { - self.brillig_context - .codegen_constrain_with_revert_data(condition, static_assert_message); - } else { - self.brillig_context.codegen_constrain(condition, static_assert_message); + match assert_message { + Some(ConstrainError::UserDefined(values, typ)) => { + let payload_values = + vecmap(values, |value| self.convert_ssa_value(*value, dfg)); + let payload_as_params = vecmap(values, |value| { + let value_type = dfg.type_of_value(*value); + FunctionContext::ssa_type_to_parameter(&value_type) + }); + self.brillig_context.codegen_constrain_with_revert_data( + condition, + payload_values, + payload_as_params, + typ.clone(), + ); + } + Some(ConstrainError::Intrinsic(message)) => { + self.brillig_context.codegen_constrain(condition, Some(message.clone())); + } + None => { + self.brillig_context.codegen_constrain(condition, None); + } } self.brillig_context.deallocate_single_addr(condition); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index 4b97a61491d0..6edfa0278215 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -55,6 +55,7 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + error_types: Default::default(), } } @@ -113,6 +114,7 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + error_types: Default::default(), } } else { // Integer version @@ -166,6 +168,7 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), + error_types: Default::default(), } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 7e37e1da434a..1ebcbe556eac 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -26,7 +26,7 @@ mod instructions; pub(crate) use instructions::BrilligBinaryOp; use self::{artifact::BrilligArtifact, registers::BrilligRegistersContext}; -use crate::ssa::ir::dfg::CallStack; +use crate::ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}; use acvm::acir::brillig::{MemoryAddress, Opcode as BrilligOpcode}; use debug_show::DebugShow; @@ -123,6 +123,10 @@ impl BrilligContext { pub(crate) fn set_call_stack(&mut self, call_stack: CallStack) { self.obj.set_call_stack(call_stack); } + + pub(crate) fn record_error_type(&mut self, error_type: UserDefinedErrorType) { + self.obj.error_types.push(error_type); + } } #[cfg(test)] @@ -130,7 +134,7 @@ pub(crate) mod tests { use std::vec; use acvm::acir::brillig::{ - ForeignCallParam, ForeignCallResult, HeapVector, MemoryAddress, ValueOrArray, + ForeignCallParam, ForeignCallResult, HeapArray, HeapVector, MemoryAddress, ValueOrArray, }; use acvm::brillig_vm::brillig::HeapValueType; use acvm::brillig_vm::{VMStatus, VM}; @@ -270,7 +274,9 @@ pub(crate) mod tests { // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 8 }); - context.push_opcode(BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }); + context.push_opcode(BrilligOpcode::Trap { + revert_data: HeapArray { pointer: MemoryAddress(0), size: 0 }, + }); context.stop_instruction(); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 8a4f469f5c93..84ae60813cce 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -1,7 +1,7 @@ use acvm::acir::brillig::Opcode as BrilligOpcode; use std::collections::{BTreeMap, HashMap}; -use crate::ssa::ir::dfg::CallStack; +use crate::ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}; /// Represents a parameter or a return value of an entry point function. #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -22,6 +22,7 @@ pub(crate) struct GeneratedBrillig { pub(crate) byte_code: Vec, pub(crate) locations: BTreeMap, pub(crate) assert_messages: BTreeMap, + pub(crate) error_types: Vec, } #[derive(Default, Debug, Clone)] @@ -29,8 +30,12 @@ pub(crate) struct GeneratedBrillig { /// It includes the bytecode of the function and all the metadata that allows linking with other functions. pub(crate) struct BrilligArtifact { pub(crate) byte_code: Vec, - /// A map of bytecode positions to assertion messages + /// A map of bytecode positions to assertion messages. + /// Some error messages (compiler intrinsics) are not emitted via revert data, + /// instead, they are handled externally so they don't add size to user programs. pub(crate) assert_messages: BTreeMap, + /// Types of errors this artifact can emit via revert data + pub(crate) error_types: Vec, /// The set of jumps that need to have their locations /// resolved. unresolved_jumps: Vec<(JumpInstructionPosition, UnresolvedJumpLocation)>, @@ -79,6 +84,7 @@ impl BrilligArtifact { byte_code: self.byte_code, locations: self.locations, assert_messages: self.assert_messages, + error_types: self.error_types, } } @@ -146,6 +152,10 @@ impl BrilligArtifact { self.assert_messages.insert(position_in_bytecode + offset, message.clone()); } + for error_typ in &obj.error_types { + self.error_types.push(error_typ.clone()); + } + for (position_in_bytecode, call_stack) in obj.locations.iter() { self.locations.insert(position_in_bytecode + offset, call_stack.clone()); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index f8f39f03df4d..ae08de7350d1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -1,7 +1,11 @@ -use acvm::acir::brillig::MemoryAddress; +use acvm::acir::brillig::{HeapArray, MemoryAddress}; + +use crate::ssa::ir::instruction::UserDefinedErrorType; use super::{ - brillig_variable::SingleAddrVariable, BrilligBinaryOp, BrilligContext, ReservedRegisters, + artifact::BrilligParameter, + brillig_variable::{BrilligVariable, SingleAddrVariable}, + BrilligBinaryOp, BrilligContext, ReservedRegisters, }; impl BrilligContext { @@ -144,26 +148,59 @@ impl BrilligContext { pub(crate) fn codegen_constrain_with_revert_data( &mut self, condition: SingleAddrVariable, - assert_message: Option, + revert_data_items: Vec, + revert_data_types: Vec, + revert_data_type: UserDefinedErrorType, ) { assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - let (revert_data_offset, revert_data_size) = - if let Some(assert_message) = assert_message { - let bytes = assert_message.as_bytes(); - for (i, byte) in bytes.iter().enumerate() { - ctx.const_instruction( - SingleAddrVariable::new(MemoryAddress(i), 8), - (*byte as usize).into(), + let revert_data = HeapArray { + pointer: ctx.allocate_register(), + // + 1 due to the revert data id being the first item returned + size: BrilligContext::flattened_tuple_size(&revert_data_types) + 1, + }; + ctx.codegen_allocate_fixed_length_array(revert_data.pointer, revert_data.size); + + let current_revert_data_pointer = ctx.allocate_register(); + ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); + let revert_data_id = ctx.make_usize_constant_instruction(revert_data_type.id.into()); + ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); + ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); + for (revert_variable, revert_param) in + revert_data_items.into_iter().zip(revert_data_types.into_iter()) + { + let flattened_size = BrilligContext::flattened_size(&revert_param); + match revert_param { + BrilligParameter::SingleAddr(_) => { + ctx.store_instruction( + current_revert_data_pointer, + revert_variable.extract_single_addr().address, + ); + } + BrilligParameter::Array(item_type, item_count) => { + let variable_pointer = revert_variable.extract_array().pointer; + + ctx.flatten_array( + &item_type, + item_count, + current_revert_data_pointer, + variable_pointer, ); } - (0, bytes.len()) - } else { - (0, 0) - }; - ctx.trap_instruction(revert_data_offset, revert_data_size); + BrilligParameter::Slice(_, _) => { + unimplemented!("Slices are not supported as revert data") + } + } + ctx.codegen_usize_op_in_place( + current_revert_data_pointer, + BrilligBinaryOp::Add, + flattened_size, + ); + } + ctx.trap_instruction(revert_data); }); + self.record_error_type(revert_data_type); } /// Emits brillig bytecode to jump to a trap condition if `condition` @@ -176,7 +213,7 @@ impl BrilligContext { assert!(condition.bit_size == 1); self.codegen_if_not(condition.address, |ctx| { - ctx.trap_instruction(0, 0); + ctx.trap_instruction(HeapArray::default()); if let Some(assert_message) = assert_message { ctx.obj.add_assert_message_to_last_opcode(assert_message); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 41a6d1873e48..5601bbde8772 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -114,13 +114,8 @@ impl DebugShow { } /// Emits a `trap` instruction. - pub(crate) fn trap_instruction(&self, revert_data_offset: usize, revert_data_size: usize) { - debug_println!( - self.enable_debug_trace, - " TRAP {}..{}", - revert_data_offset, - revert_data_offset + revert_data_size - ); + pub(crate) fn trap_instruction(&self, revert_data: HeapArray) { + debug_println!(self.enable_debug_trace, " TRAP {}", revert_data); } /// Emits a `mov` instruction. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 88cf987325d6..732bd3cbc598 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -164,7 +164,7 @@ impl BrilligContext { } /// Computes the size of a parameter if it was flattened - fn flattened_size(param: &BrilligParameter) -> usize { + pub(super) fn flattened_size(param: &BrilligParameter) -> usize { match param { BrilligParameter::SingleAddr(_) => 1, BrilligParameter::Array(item_types, item_count) @@ -176,7 +176,7 @@ impl BrilligContext { } /// Computes the size of a parameter if it was flattened - fn flattened_tuple_size(tuple: &[BrilligParameter]) -> usize { + pub(super) fn flattened_tuple_size(tuple: &[BrilligParameter]) -> usize { tuple.iter().map(BrilligContext::flattened_size).sum() } @@ -369,7 +369,7 @@ impl BrilligContext { } // Flattens an array by recursively copying nested arrays and regular items. - fn flatten_array( + pub(super) fn flatten_array( &mut self, item_type: &[BrilligParameter], item_count: usize, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index 901ccc58036f..5f4e8c4a1717 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -1,6 +1,6 @@ use acvm::{ acir::brillig::{ - BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapValueType, MemoryAddress, + BinaryFieldOp, BinaryIntOp, BlackBoxOp, HeapArray, HeapValueType, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray, }, FieldElement, @@ -466,10 +466,10 @@ impl BrilligContext { }); } - pub(super) fn trap_instruction(&mut self, revert_data_offset: usize, revert_data_size: usize) { - self.debug_show.trap_instruction(revert_data_offset, revert_data_size); + pub(super) fn trap_instruction(&mut self, revert_data: HeapArray) { + self.debug_show.trap_instruction(revert_data); - self.push_opcode(BrilligOpcode::Trap { revert_data_offset, revert_data_size }); + self.push_opcode(BrilligOpcode::Trap { revert_data }); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs index b3e838e708eb..1e9220601009 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs @@ -7,7 +7,7 @@ //! An Error of the former is a user Error //! //! An Error of the latter is an error in the implementation of the compiler -use acvm::{acir::native_types::Expression, FieldElement}; +use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; @@ -17,13 +17,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Clone, Error)] pub enum RuntimeError { - #[error("{}", format_failed_constraint(.assert_message))] - FailedConstraint { - lhs: Box, - rhs: Box, - call_stack: CallStack, - assert_message: Option, - }, #[error(transparent)] InternalError(#[from] InternalError), #[error("Index out of bounds, array has size {array_size}, but index was {index}")] @@ -52,16 +45,6 @@ pub enum RuntimeError { UnconstrainedOracleReturnToConstrained { call_stack: CallStack }, } -// We avoid showing the actual lhs and rhs since most of the time they are just 0 -// and 1 respectively. This would confuse users if a constraint such as -// assert(foo < bar) fails with "failed constraint: 0 = 1." -fn format_failed_constraint(message: &Option) -> String { - match message { - Some(message) => format!("Failed constraint: '{message}'"), - None => "Failed constraint".to_owned(), - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SsaReport { Warning(InternalWarning), @@ -129,7 +112,6 @@ impl RuntimeError { | InternalError::UndeclaredAcirVar { call_stack } | InternalError::Unexpected { call_stack, .. }, ) - | RuntimeError::FailedConstraint { call_stack, .. } | RuntimeError::IndexOutOfBounds { call_stack, .. } | RuntimeError::InvalidRangeConstraint { call_stack, .. } | RuntimeError::TypeConversion { call_stack, .. } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 760340f1a881..f12ecf0d8de2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -7,7 +7,7 @@ //! This module heavily borrows from Cranelift #![allow(dead_code)] -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use crate::{ brillig::Brillig, @@ -16,6 +16,7 @@ use crate::{ use acvm::acir::{ circuit::{ brillig::BrilligBytecode, Circuit, ExpressionWidth, Program as AcirProgram, PublicInputs, + ResolvedOpcodeLocation, }, native_types::Witness, }; @@ -23,7 +24,8 @@ use acvm::acir::{ use noirc_errors::debug_info::{DebugFunctions, DebugInfo, DebugTypes, DebugVariables}; use noirc_frontend::{ - hir_def::function::FunctionSignature, monomorphization::ast::Program, Visibility, + hir_def::function::FunctionSignature, hir_def::types::Type as HirType, + monomorphization::ast::Program, Visibility, }; use tracing::{span, Level}; @@ -100,10 +102,14 @@ pub struct SsaProgramArtifact { pub main_input_witnesses: Vec, pub main_return_witnesses: Vec, pub names: Vec, + pub error_types: BTreeMap, } impl SsaProgramArtifact { - fn new(unconstrained_functions: Vec) -> Self { + fn new( + unconstrained_functions: Vec, + error_types: BTreeMap, + ) -> Self { let program = AcirProgram { functions: Vec::default(), unconstrained_functions }; Self { program, @@ -112,6 +118,7 @@ impl SsaProgramArtifact { main_input_witnesses: Vec::default(), main_return_witnesses: Vec::default(), names: Vec::default(), + error_types, } } @@ -159,7 +166,14 @@ pub fn create_program( "The generated ACIRs should match the supplied function signatures" ); - let mut program_artifact = SsaProgramArtifact::new(generated_brillig); + let error_types: BTreeMap = generated_acirs + .iter() + .flat_map(|acir| { + acir.error_types.iter().map(move |error_typ| (error_typ.id, error_typ.typ.clone())) + }) + .collect(); + + let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); // For setting up the ABI we need separately specify main's input and return witnesses let mut is_main = true; for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index b94e02e51199..f33ce9bb810d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -5,11 +5,12 @@ use crate::brillig::brillig_ir::artifact::GeneratedBrillig; use crate::errors::{InternalError, RuntimeError, SsaReport}; use crate::ssa::acir_gen::{AcirDynamicArray, AcirValue}; use crate::ssa::ir::dfg::CallStack; +use crate::ssa::ir::instruction::UserDefinedErrorType; use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; use acvm::acir::circuit::opcodes::{BlockId, MemOp}; -use acvm::acir::circuit::Opcode; +use acvm::acir::circuit::{AssertionPayload, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -23,6 +24,7 @@ use acvm::{ }; use fxhash::FxHashMap as HashMap; use iter_extended::{try_vecmap, vecmap}; +use noirc_frontend::hir_def::types::Type as HirType; use num_bigint::BigUint; use std::{borrow::Cow, hash::Hash}; @@ -493,7 +495,7 @@ impl AcirContext { &mut self, lhs: AcirVar, rhs: AcirVar, - assert_message: Option, + assert_message: Option<(AssertionPayload, Option)>, ) -> Result<(), RuntimeError> { let lhs_expr = self.var_to_expression(lhs)?; let rhs_expr = self.var_to_expression(rhs)?; @@ -509,8 +511,11 @@ impl AcirContext { } self.acir_ir.assert_is_zero(diff_expr); - if let Some(message) = assert_message { - self.acir_ir.assert_messages.insert(self.acir_ir.last_acir_opcode_location(), message); + if let Some((payload, typ)) = assert_message { + self.acir_ir.assert_messages.insert(self.acir_ir.last_acir_opcode_location(), payload); + if let Some(typ) = typ { + self.acir_ir.error_types.push(typ); + } } self.mark_variables_equivalent(lhs, rhs)?; @@ -981,9 +986,10 @@ impl AcirContext { let witness = self.var_to_witness(witness_var)?; self.acir_ir.range_constraint(witness, *bit_size)?; if let Some(message) = message { - self.acir_ir - .assert_messages - .insert(self.acir_ir.last_acir_opcode_location(), message); + self.acir_ir.assert_messages.insert( + self.acir_ir.last_acir_opcode_location(), + AssertionPayload::StaticString(message.clone()), + ); } } NumericType::NativeField => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 999ff2ddb5d0..2e70e292e119 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -5,14 +5,13 @@ use std::collections::BTreeMap; use crate::{ brillig::{brillig_gen::brillig_directive, brillig_ir::artifact::GeneratedBrillig}, errors::{InternalError, RuntimeError, SsaReport}, - ssa::ir::dfg::CallStack, + ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}, }; - use acvm::acir::{ circuit::{ brillig::{Brillig as AcvmBrillig, BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, - OpcodeLocation, + AssertionPayload, OpcodeLocation, }, native_types::Witness, BlackBoxFunc, @@ -22,6 +21,7 @@ use acvm::{ FieldElement, }; use iter_extended::vecmap; +use noirc_frontend::hir_def::types::Type as HirType; use num_bigint::BigUint; #[derive(Debug, Default)] @@ -55,7 +55,8 @@ pub(crate) struct GeneratedAcir { pub(crate) call_stack: CallStack, /// Correspondence between an opcode index and the error message associated with it. - pub(crate) assert_messages: BTreeMap, + pub(crate) assert_messages: BTreeMap, + pub(crate) error_types: Vec, pub(crate) warnings: Vec, @@ -613,9 +614,12 @@ impl GeneratedAcir { for (brillig_index, message) in generated_brillig.assert_messages { self.assert_messages.insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index }, - message, + AssertionPayload::StaticString(message.clone()), ); } + for error_type in generated_brillig.error_types { + self.error_types.push(error_type); + } } pub(crate) fn brillig_call( @@ -644,9 +648,12 @@ impl GeneratedAcir { acir_index: self.opcodes.len() - 1, brillig_index: *brillig_index, }, - message.clone(), + AssertionPayload::StaticString(message.clone()), ); } + for error_type in generated_brillig.error_types.iter() { + self.error_types.push(error_type.clone()); + } } pub(crate) fn last_acir_opcode_location(&self) -> OpcodeLocation { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 02b381d79fc9..b365c7f95856 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -8,7 +8,7 @@ use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; -use super::ir::instruction::{ConstrainError, UserDefinedConstrainError}; +use super::ir::instruction::{ConstrainError, UserDefinedErrorType}; use super::{ ir::{ dfg::DataFlowGraph, @@ -30,6 +30,7 @@ use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::AssertionPayload; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -530,24 +531,32 @@ impl<'a> Context<'a> { let lhs = self.convert_numeric_value(*lhs, dfg)?; let rhs = self.convert_numeric_value(*rhs, dfg)?; - let assert_message = if let Some(error) = assert_message { - match error.as_ref() { - ConstrainError::Intrinsic(string) - | ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { - Some(string.clone()) + let assert_payload = if let Some(error) = assert_message { + match error { + ConstrainError::Intrinsic(string) => { + Some((AssertionPayload::StaticString(string.clone()), None)) } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instruction, - )) => { - self.convert_ssa_call(call_instruction, dfg, ssa, brillig, &[])?; - None + ConstrainError::UserDefined(values, UserDefinedErrorType { typ, id }) => { + let acir_vars: Vec<(AcirVar, AcirType)> = values + .iter() + .flat_map(|value| self.convert_value(*value, dfg).flatten()) + .collect(); + + let expressions = try_vecmap(acir_vars, |(var, _)| { + self.acir_context.var_to_expression(var) + })?; + + Some(( + AssertionPayload::Expression(*id, expressions), + Some(UserDefinedErrorType { typ: typ.clone(), id: *id }), + )) } } } else { None }; - self.acir_context.assert_eq_var(lhs, rhs, assert_message)?; + self.acir_context.assert_eq_var(lhs, rhs, assert_payload)?; } Instruction::Cast(value_id, _) => { let acir_var = self.convert_numeric_value(*value_id, dfg)?; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 75a427397b62..3b9ce4220217 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -4,6 +4,7 @@ use std::{borrow::Cow, rc::Rc}; use acvm::FieldElement; use noirc_errors::Location; +use noirc_frontend::hir_def::types::Type as HirType; use crate::ssa::ir::{ basic_block::BasicBlockId, @@ -263,7 +264,7 @@ impl FunctionBuilder { &mut self, lhs: ValueId, rhs: ValueId, - assert_message: Option>, + assert_message: Option, ) { self.insert_instruction(Instruction::Constrain(lhs, rhs, assert_message), None); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 04f33d528cd9..c51d344937f6 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,5 +1,6 @@ use acvm::{acir::BlackBoxFunc, FieldElement}; use iter_extended::vecmap; +use noirc_frontend::hir_def::types::Type as HirType; use super::{ basic_block::BasicBlockId, @@ -157,7 +158,7 @@ pub(crate) enum Instruction { Truncate { value: ValueId, bit_size: u32, max_bit_size: u32 }, /// Constrains two values to be equal to one another. - Constrain(ValueId, ValueId, Option>), + Constrain(ValueId, ValueId, Option), /// Range constrain `value` to `max_bit_size` RangeCheck { value: ValueId, max_bit_size: u32, assert_message: Option }, @@ -346,12 +347,12 @@ impl Instruction { // Must map the `lhs` and `rhs` first as the value `f` is moved with the closure let lhs = f(*lhs); let rhs = f(*rhs); - let assert_message = assert_message.as_ref().map(|error| match error.as_ref() { - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(call_instr)) => { - let new_instr = call_instr.map_values(f); - Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - new_instr, - ))) + let assert_message = assert_message.as_ref().map(|error| match error { + ConstrainError::UserDefined(payload_values, typ) => { + ConstrainError::UserDefined( + payload_values.iter().map(|&value| f(value)).collect(), + typ.clone(), + ) } _ => error.clone(), }); @@ -412,13 +413,10 @@ impl Instruction { Instruction::Constrain(lhs, rhs, assert_error) => { f(*lhs); f(*rhs); - if let Some(error) = assert_error.as_ref() { - if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - call_instr, - )) = error.as_ref() - { - call_instr.for_each_value(f); - } + if let Some(ConstrainError::UserDefined(values, _)) = assert_error.as_ref() { + values.iter().for_each(|&val| { + f(val); + }); } } @@ -604,17 +602,13 @@ pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), // These are errors issued by the user - UserDefined(UserDefinedConstrainError), + UserDefined(Vec, UserDefinedErrorType), } #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum UserDefinedConstrainError { - // These are errors which come from static strings specified by a Noir program - Static(String), - // These are errors which come from runtime expressions specified by a Noir program - // We store an `Instruction` as we want this Instruction to be atomic in SSA with - // a constrain instruction, and leave codegen of this instruction to lower level passes. - Dynamic(Instruction), +pub(crate) struct UserDefinedErrorType { + pub(crate) typ: HirType, + pub(crate) id: usize, } impl From for ConstrainError { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs index b4198e2cfecb..d844f3509274 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs @@ -7,7 +7,7 @@ use super::{Binary, BinaryOp, ConstrainError, DataFlowGraph, Instruction, Type, pub(super) fn decompose_constrain( lhs: ValueId, rhs: ValueId, - msg: &Option>, + msg: &Option, dfg: &mut DataFlowGraph, ) -> Vec { let lhs = dfg.resolve(lhs); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index d17d2989341d..7808747a5365 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -9,10 +9,7 @@ use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, function::Function, - instruction::{ - ConstrainError, Instruction, InstructionId, TerminatorInstruction, - UserDefinedConstrainError, - }, + instruction::{ConstrainError, Instruction, InstructionId, TerminatorInstruction}, value::ValueId, }; @@ -204,12 +201,11 @@ fn display_constrain_error( f: &mut Formatter, ) -> Result { match error { - ConstrainError::Intrinsic(assert_message_string) - | ConstrainError::UserDefined(UserDefinedConstrainError::Static(assert_message_string)) => { + ConstrainError::Intrinsic(assert_message_string) => { writeln!(f, "{assert_message_string:?}") } - ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(assert_message_call)) => { - display_instruction_inner(function, assert_message_call, f) + ConstrainError::UserDefined(values, typ) => { + writeln!(f, "{}: {}", value_list(function, values), typ.typ) } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index aa0368cc2dd2..3427236b2001 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -14,7 +14,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, function::{Function, FunctionId, Signature}, - instruction::{BinaryOp, ConstrainError, Instruction, UserDefinedConstrainError}, + instruction::{BinaryOp, Instruction}, types::{NumericType, Type}, value::{Value, ValueId}, }, @@ -90,18 +90,6 @@ impl DefunctionalizationContext { Instruction::Call { func: target_func_id, arguments } => { (*target_func_id, arguments) } - // Constrain instruction potentially hold a call instruction themselves - // thus we need to account for them. - Instruction::Constrain(_, _, Some(constrain_error)) => { - if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( - Instruction::Call { func: target_func_id, arguments }, - )) = constrain_error.as_ref() - { - (*target_func_id, arguments) - } else { - continue; - } - } _ => continue, }; @@ -134,21 +122,6 @@ impl DefunctionalizationContext { _ => {} } if let Some(mut new_instruction) = replacement_instruction { - if let Instruction::Constrain(lhs, rhs, constrain_error_call) = instruction { - let new_error_call = if let Some(error) = constrain_error_call { - match error.as_ref() { - ConstrainError::UserDefined( - UserDefinedConstrainError::Dynamic(_), - ) => Some(Box::new(ConstrainError::UserDefined( - UserDefinedConstrainError::Dynamic(new_instruction), - ))), - _ => None, - } - } else { - None - }; - new_instruction = Instruction::Constrain(lhs, rhs, new_error_call); - } func.dfg[instruction_id] = new_instruction; } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 569e543ce40f..a63c11f553d6 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -1,4 +1,5 @@ use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Mutex, RwLock}; use acvm::FieldElement; @@ -16,7 +17,7 @@ use crate::ssa::ir::function::{Function, RuntimeType}; use crate::ssa::ir::function::{FunctionId as IrFunctionId, InlineType}; use crate::ssa::ir::instruction::BinaryOp; use crate::ssa::ir::instruction::Instruction; -use crate::ssa::ir::map::AtomicCounter; +use crate::ssa::ir::map::{AtomicCounter, Id}; use crate::ssa::ir::types::{NumericType, Type}; use crate::ssa::ir::value::ValueId; @@ -38,7 +39,7 @@ pub(super) struct FunctionContext<'a> { definitions: HashMap, pub(super) builder: FunctionBuilder, - shared_context: &'a SharedContext, + pub(super) shared_context: &'a SharedContext, /// Contains any loops we're currently in the middle of translating. /// These are ordered such that an inner loop is at the end of the vector and @@ -73,6 +74,8 @@ pub(super) struct SharedContext { /// Shared counter used to assign the ID of the next function function_counter: AtomicCounter, + error_id_counter: AtomicUsize, + /// The entire monomorphized source program pub(super) program: Program, } @@ -1201,6 +1204,7 @@ impl SharedContext { functions: Default::default(), function_queue: Default::default(), function_counter: Default::default(), + error_id_counter: Default::default(), program, } } @@ -1232,6 +1236,10 @@ impl SharedContext { next_id } + + pub(super) fn create_error_id(&self) -> usize { + self.error_id_counter.fetch_add(1, Ordering::Relaxed) + } } /// Used to remember the results of each step of extracting a value from an ast::LValue diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 2a4b5276547b..b0fe3f5d79f7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -8,12 +8,13 @@ use context::SharedContext; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use noirc_frontend::{ + hir_def::types::Type as HirType, monomorphization::ast::{self, Expression, Program}, Visibility, }; use crate::{ - errors::{InternalError, RuntimeError}, + errors::RuntimeError, ssa::{ function_builder::data_bus::DataBusBuilder, ir::{function::InlineType, instruction::Intrinsic}, @@ -29,9 +30,7 @@ use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{ - BinaryOp, ConstrainError, Instruction, TerminatorInstruction, UserDefinedConstrainError, - }, + instruction::{BinaryOp, ConstrainError, TerminatorInstruction, UserDefinedErrorType}, types::Type, value::ValueId, }, @@ -153,8 +152,8 @@ impl<'a> FunctionContext<'a> { } Expression::Call(call) => self.codegen_call(call), Expression::Let(let_expr) => self.codegen_let(let_expr), - Expression::Constrain(expr, location, assert_message) => { - self.codegen_constrain(expr, *location, assert_message) + Expression::Constrain(expr, location, assert_payload) => { + self.codegen_constrain(expr, *location, assert_payload) } Expression::Assign(assign) => self.codegen_assign(assign), Expression::Semi(semi) => self.codegen_semi(semi), @@ -455,7 +454,7 @@ impl<'a> FunctionContext<'a> { self.builder.insert_constrain( is_offset_out_of_bounds, true_const, - Some(Box::new("Index out of bounds".to_owned().into())), + Some("Index out of bounds".to_owned().into()), ); } @@ -682,7 +681,7 @@ impl<'a> FunctionContext<'a> { &mut self, expr: &Expression, location: Location, - assert_message: &Option>, + assert_payload: &Option>, ) -> Result { let expr = self.codegen_non_tuple_expression(expr)?; let true_literal = self.builder.numeric_constant(true, Type::bool()); @@ -690,7 +689,7 @@ impl<'a> FunctionContext<'a> { // Set the location here for any errors that may occur when we codegen the assert message self.builder.set_location(location); - let assert_message = self.codegen_constrain_error(assert_message)?; + let assert_message = self.codegen_constrain_error(assert_payload)?; self.builder.insert_constrain(expr, true_literal, assert_message); @@ -703,42 +702,19 @@ impl<'a> FunctionContext<'a> { // inserted to the SSA as we want that instruction to be atomic in SSA with a constrain instruction. fn codegen_constrain_error( &mut self, - assert_message: &Option>, - ) -> Result>, RuntimeError> { - let Some(assert_message_expr) = assert_message else { return Ok(None) }; - - if let ast::Expression::Literal(ast::Literal::Str(assert_message)) = - assert_message_expr.as_ref() - { - return Ok(Some(Box::new(ConstrainError::UserDefined( - UserDefinedConstrainError::Static(assert_message.to_string()), - )))); - } - - let ast::Expression::Call(call) = assert_message_expr.as_ref() else { - return Err(InternalError::Unexpected { - expected: "Expected a call expression".to_owned(), - found: "Instead found {expr:?}".to_owned(), - call_stack: self.builder.get_call_stack(), - } - .into()); - }; - - let func = self.codegen_non_tuple_expression(&call.func)?; - let mut arguments = Vec::with_capacity(call.arguments.len()); - - for argument in &call.arguments { - let mut values = self.codegen_expression(argument)?.into_value_list(self); - arguments.append(&mut values); - } - - // If an array is passed as an argument we increase its reference count - for argument in &arguments { - self.builder.increment_array_reference_count(*argument); - } - - let instr = Instruction::Call { func, arguments }; - Ok(Some(Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(instr))))) + assert_message: &Option>, + ) -> Result, RuntimeError> { + let Some(assert_message_payload) = assert_message else { return Ok(None) }; + let (assert_message_expression, assert_message_typ) = assert_message_payload.as_ref(); + + let values = self.codegen_expression(assert_message_expression)?.into_value_list(self); + Ok(Some(ConstrainError::UserDefined( + values, + UserDefinedErrorType { + typ: assert_message_typ.clone(), + id: self.shared_context.create_error_id(), + }, + ))) } fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index b05a2cbc7414..291b694b9be8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -6,6 +6,7 @@ use crate::ssa::ir::{ function::{Function, FunctionId, RuntimeType}, map::AtomicCounter, }; +use noirc_frontend::hir_def::types::Type as HirType; /// Contains the entire SSA representation of the program. pub(crate) struct Ssa { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 9180201fe17e..0519c15c0aae 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1208,15 +1208,14 @@ impl<'a> Resolver<'a> { }) } StatementKind::Constrain(constrain_stmt) => { - let span = constrain_stmt.0.span; - let assert_msg_call_expr_id = - self.resolve_assert_message(constrain_stmt.1, span, constrain_stmt.0.clone()); let expr_id = self.resolve_expression(constrain_stmt.0); + let assert_message_expr_id = + constrain_stmt.1.map(|assert_expr_id| self.resolve_expression(assert_expr_id)); HirStatement::Constrain(HirConstrainStatement( expr_id, self.file, - assert_msg_call_expr_id, + assert_message_expr_id, )) } StatementKind::Expression(expr) => { @@ -1278,48 +1277,6 @@ impl<'a> Resolver<'a> { } } - fn resolve_assert_message( - &mut self, - assert_message_expr: Option, - span: Span, - condition: Expression, - ) -> Option { - let assert_message_expr = assert_message_expr?; - - if matches!( - assert_message_expr, - Expression { kind: ExpressionKind::Literal(Literal::Str(..)), .. } - ) { - return Some(self.resolve_expression(assert_message_expr)); - } - - let is_in_stdlib = self.path_resolver.module_id().krate.is_stdlib(); - let assert_msg_call_path = if is_in_stdlib { - ExpressionKind::Variable(Path { - segments: vec![Ident::from("internal"), Ident::from("resolve_assert_message")], - kind: PathKind::Crate, - span, - }) - } else { - ExpressionKind::Variable(Path { - segments: vec![ - Ident::from("std"), - Ident::from("internal"), - Ident::from("resolve_assert_message"), - ], - kind: PathKind::Dep, - span, - }) - }; - let assert_msg_call_args = vec![assert_message_expr.clone(), condition]; - let assert_msg_call_expr = Expression::call( - Expression { kind: assert_msg_call_path, span }, - assert_msg_call_args, - span, - ); - Some(self.resolve_expression(assert_msg_call_expr)) - } - pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); self.interner.push_stmt(hir_stmt) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs index d9c33d8604e2..c99b9620ae63 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -6,8 +6,8 @@ use noirc_errors::{ }; use crate::{ - hir_def::function::FunctionSignature, BinaryOpKind, Distinctness, IntegerBitSize, Signedness, - Visibility, + hir_def::function::FunctionSignature, hir_def::types::Type as HirType, BinaryOpKind, + Distinctness, IntegerBitSize, Signedness, Visibility, }; /// The monomorphized AST is expression-based, all statements are also @@ -35,7 +35,7 @@ pub enum Expression { ExtractTupleField(Box, usize), Call(Call), Let(Let), - Constrain(Box, Location, Option>), + Constrain(Box, Location, Option>), Assign(Assign), Semi(Box), Break, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 4e779244d309..44ad44e9804a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -581,7 +581,11 @@ impl<'interner> Monomorphizer<'interner> { let location = self.interner.expr_location(&constrain.0); let assert_message = constrain .2 - .map(|assert_msg_expr| self.expr(assert_msg_expr)) + .map(|assert_msg_expr| { + self.expr(assert_msg_expr).map(|expr| { + (expr, self.interner.id_type(assert_msg_expr).follow_bindings()) + }) + }) .transpose()? .map(Box::new); Ok(ast::Expression::Constrain(Box::new(expr), location, assert_message)) @@ -1085,9 +1089,6 @@ impl<'interner> Monomorphizer<'interner> { // The first argument to the `print` oracle is a bool, indicating a newline to be inserted at the end of the input // The second argument is expected to always be an ident self.append_printable_type_info(&hir_arguments[1], &mut arguments); - } else if name.as_str() == "assert_message" { - // The first argument to the `assert_message` oracle is the expression passed as a message to an `assert` or `assert_eq` statement - self.append_printable_type_info(&hir_arguments[0], &mut arguments); } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index e4d308fbb6b6..c4f0a8d67ba9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -66,7 +66,7 @@ mod test { // Allocate a default Module for the root, giving it a ModuleId let mut modules: Arena = Arena::default(); let location = Location::new(Default::default(), root_file_id); - let root = modules.insert(ModuleData::new(None, None, location, false)); + let root = modules.insert(ModuleData::new(None, location, false)); let def_map = CrateDefMap { root: LocalModuleId(root), diff --git a/noir/noir-repo/noir_stdlib/src/internal.nr b/noir/noir-repo/noir_stdlib/src/internal.nr index 8d5c01dda7f6..22440fe60ede 100644 --- a/noir/noir-repo/noir_stdlib/src/internal.nr +++ b/noir/noir-repo/noir_stdlib/src/internal.nr @@ -2,11 +2,3 @@ // These functions should not be called manually in user code. // // Changes to this file will not be considered breaking. - -#[oracle(assert_message)] -unconstrained fn assert_message_oracle(_input: T) {} -unconstrained pub fn resolve_assert_message(input: T, condition: bool) { - if !condition { - assert_message_oracle(input); - } -} diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 9b535075484e..3c246d91e273 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -53,6 +53,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { &circuit.opcodes, initial_witness, unconstrained_functions, + &circuit.assert_messages, ), brillig_solver: None, foreign_call_executor, diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index ac03330a7c86..9bffaf7c51b8 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -1,7 +1,11 @@ +use std::collections::BTreeMap; + use acvm::{ acir::circuit::{OpcodeLocation, ResolvedOpcodeLocation}, - pwg::{ErrorLocation, OpcodeResolutionError}, + pwg::{ErrorLocation, OpcodeResolutionError, ResolvedAssertionPayload}, + FieldElement, }; +use noirc_abi::{Abi, AbiType, Sign}; use noirc_errors::{ debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic, }; @@ -9,7 +13,9 @@ use noirc_errors::{ pub use noirc_errors::Location; use noirc_frontend::graph::CrateName; -use noirc_printable_type::ForeignCallError; +use noirc_printable_type::{ + decode_value, ForeignCallError, PrintableType, PrintableValue, PrintableValueDisplay, +}; use thiserror::Error; /// Errors covering situations where a package cannot be compiled. @@ -53,24 +59,37 @@ impl NargoError { /// /// We want to extract the user defined error so that we can compare it /// in tests to expected failure messages - pub fn user_defined_failure_message(&self) -> Option<&str> { + pub fn user_defined_failure_message( + &self, + error_types: BTreeMap, + ) -> Option { let execution_error = match self { NargoError::ExecutionError(error) => error, _ => return None, }; match execution_error { - ExecutionError::AssertionFailed(message, _) => Some(message), + ExecutionError::AssertionFailed(payload, _) => match payload { + ResolvedAssertionPayload::String(message) => Some(message.to_string()), + ResolvedAssertionPayload::Raw(error_id, fields) => { + if let Some(abi_type) = error_types.get(error_id) { + let decoded = prepare_for_display(fields, abi_type.clone()); + Some(decoded.to_string()) + } else { + None + } + } + }, ExecutionError::SolvingError(error, _) => match error { OpcodeResolutionError::IndexOutOfBounds { .. } | OpcodeResolutionError::OpcodeNotSolvable(_) | OpcodeResolutionError::UnsatisfiedConstrain { .. } | OpcodeResolutionError::AcirMainCallAttempted { .. } + | OpcodeResolutionError::BrilligFunctionFailed { .. } | OpcodeResolutionError::AcirCallOutputsMismatch { .. } => None, - OpcodeResolutionError::BrilligFunctionFailed { message, .. } => { - message.as_ref().map(|s| s.as_str()) + OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => { + Some(reason.to_string()) } - OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => Some(reason), }, } } @@ -78,8 +97,8 @@ impl NargoError { #[derive(Debug, Error)] pub enum ExecutionError { - #[error("Failed assertion: '{}'", .0)] - AssertionFailed(String, Vec), + #[error("Failed assertion")] + AssertionFailed(ResolvedAssertionPayload, Vec), #[error("Failed to solve program: '{}'", .0)] SolvingError(OpcodeResolutionError, Option>), @@ -101,7 +120,7 @@ fn extract_locations_from_error( acir_call_stack, ) | ExecutionError::SolvingError( - OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: error_location }, + OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: error_location, .. }, acir_call_stack, ) => match error_location { ErrorLocation::Unresolved => { @@ -144,11 +163,82 @@ fn extract_locations_from_error( ) } -fn extract_message_from_error(nargo_err: &NargoError) -> String { +fn map_non_fmt_to_printable_type(abi_typ: AbiType) -> PrintableType { + match abi_typ { + AbiType::Field => PrintableType::Field, + AbiType::String { length } => PrintableType::String { length }, + AbiType::Tuple { fields } => { + let fields = + fields.iter().map(|field| map_non_fmt_to_printable_type(field.clone())).collect(); + PrintableType::Tuple { types: fields } + } + AbiType::Array { length, typ } => { + let typ = map_non_fmt_to_printable_type(*typ); + PrintableType::Array { length: Some(length), typ: Box::new(typ) } + } + AbiType::Boolean => PrintableType::Boolean, + AbiType::Struct { path, fields } => { + let fields = fields + .iter() + .map(|(name, field)| (name.clone(), map_non_fmt_to_printable_type(field.clone()))) + .collect(); + PrintableType::Struct { + name: path.split("::").last().unwrap_or_default().to_string(), + fields, + } + } + AbiType::Integer { sign: Sign::Unsigned, width } => { + PrintableType::UnsignedInteger { width } + } + AbiType::Integer { sign: Sign::Signed, width } => PrintableType::SignedInteger { width }, + AbiType::FmtString { .. } => unreachable!("Nested fmt strings are unsupported"), + } +} + +fn prepare_for_display(fields: &[FieldElement], abi_typ: AbiType) -> PrintableValueDisplay { + if let AbiType::FmtString { length, item_types } = abi_typ { + let mut fields_iter = fields.iter().copied(); + let PrintableValue::String(string) = + decode_value(&mut fields_iter, &PrintableType::String { length }) + else { + unreachable!("Got non-string from string decoding"); + }; + let _length_of_items = fields_iter.next(); + let items = item_types.into_iter().map(|abi_type| { + let printable_typ = map_non_fmt_to_printable_type(abi_type); + let decoded = decode_value(&mut fields_iter, &printable_typ); + (decoded, printable_typ) + }); + PrintableValueDisplay::FmtString(string, items.collect()) + } else { + let printable_type = map_non_fmt_to_printable_type(abi_typ); + let decoded = decode_value(&mut fields.iter().copied(), &printable_type); + PrintableValueDisplay::Plain(decoded, printable_type) + } +} + +fn extract_message_from_error( + error_types: &BTreeMap, + nargo_err: &NargoError, +) -> String { + dbg!(nargo_err); match nargo_err { - NargoError::ExecutionError(ExecutionError::AssertionFailed(message, _)) => { + NargoError::ExecutionError(ExecutionError::AssertionFailed( + ResolvedAssertionPayload::String(message), + _, + )) => { format!("Assertion failed: '{message}'") } + NargoError::ExecutionError(ExecutionError::AssertionFailed( + ResolvedAssertionPayload::Raw(error_id, fields), + .., + )) => { + if let Some(abi_type) = error_types.get(error_id) { + format!("Assertion failed: {}", prepare_for_display(fields, abi_type.clone())) + } else { + format!("Assertion failed {:?}", fields) + } + } NargoError::ExecutionError(ExecutionError::SolvingError( OpcodeResolutionError::IndexOutOfBounds { index, array_size, .. }, _, @@ -166,6 +256,7 @@ fn extract_message_from_error(nargo_err: &NargoError) -> String { /// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. pub fn try_to_diagnose_runtime_error( nargo_err: &NargoError, + abi: &Abi, debug: &[DebugInfo], ) -> Option { let source_locations = match nargo_err { @@ -177,7 +268,7 @@ pub fn try_to_diagnose_runtime_error( // The location of the error itself will be the location at the top // of the call stack (the last item in the Vec). let location = source_locations.last()?; - let message = extract_message_from_error(nargo_err); + let message = extract_message_from_error(&abi.error_types, nargo_err); Some( CustomDiagnostic::simple_error(message, String::new(), location.span) .in_file(location.file) diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index 97584aff1507..86a569f6713e 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -63,10 +63,9 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, &circuit.opcodes, initial_witness, self.unconstrained_functions, + &circuit.assert_messages, ); - // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. - let mut assert_message: Option = None; loop { let solver_status = acvm.solve(); @@ -79,6 +78,7 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), + .. } | OpcodeResolutionError::IndexOutOfBounds { opcode_location: ErrorLocation::Resolved(opcode_location), @@ -103,38 +103,20 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, _ => None, }; - return Err(NargoError::ExecutionError(match call_stack { - Some(call_stack) => { - // First check whether we have a runtime assertion message that should be resolved on an ACVM failure - // If we do not have a runtime assertion message, we check wether the error is a brillig error with a user-defined message, - // and finally we should check whether the circuit has any hardcoded messages associated with a specific `OpcodeLocation`. - // Otherwise return the provided opcode resolution error. - if let Some(assert_message) = assert_message { - ExecutionError::AssertionFailed( - assert_message.to_owned(), - call_stack, - ) - } else if let OpcodeResolutionError::BrilligFunctionFailed { - message: Some(message), - .. - } = &error - { - ExecutionError::AssertionFailed(message.to_owned(), call_stack) - } else if let Some(assert_message) = circuit.get_assert_message( - call_stack - .last() - .expect("Call stacks should not be empty") - .opcode_location, - ) { - ExecutionError::AssertionFailed( - assert_message.to_owned(), - call_stack, - ) - } else { - ExecutionError::SolvingError(error, Some(call_stack)) - } + let assertion_payload = match &error { + OpcodeResolutionError::BrilligFunctionFailed { payload, .. } + | OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => { + payload.clone() } - None => ExecutionError::SolvingError(error, None), + _ => None, + }; + + return Err(NargoError::ExecutionError(match assertion_payload { + Some(payload) => ExecutionError::AssertionFailed( + payload, + call_stack.expect("Should have call stack for an assertion failure"), + ), + None => ExecutionError::SolvingError(error, call_stack), })); } ACVMStatus::RequiresForeignCall(foreign_call) => { @@ -144,12 +126,7 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, acvm.resolve_pending_foreign_call(foreign_call_result); } NargoForeignCallResult::ResolvedAssertMessage(message) => { - if assert_message.is_some() { - unreachable!("Resolving an assert message should happen only once as the VM should have failed"); - } - assert_message = Some(message); - - acvm.resolve_pending_foreign_call(ForeignCallResult::default()); + unreachable!("Assert messages shouln't be resolved via foreign calls") } } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index b216fff827db..a76c6f57de56 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -2,9 +2,9 @@ use acvm::{ acir::native_types::{WitnessMap, WitnessStack}, BlackBoxFunctionSolver, }; +use noirc_abi::Abi; use noirc_driver::{compile_no_check, CompileError, CompileOptions}; use noirc_errors::{debug_info::DebugInfo, FileDiagnostic}; -use noirc_evaluator::errors::RuntimeError; use noirc_frontend::hir::{def_map::TestFunction, Context}; use crate::{errors::try_to_diagnose_runtime_error, NargoError}; @@ -44,6 +44,7 @@ pub fn run_test( ); test_status_program_compile_pass( test_function, + compiled_program.abi, compiled_program.debug, circuit_execution, ) @@ -64,18 +65,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct return TestStatus::CompileError(err.into()); } - // The test has failed compilation, extract the assertion message if present and check if it's expected. - let assert_message = if let CompileError::RuntimeError(RuntimeError::FailedConstraint { - assert_message, - .. - }) = &err - { - assert_message.clone() - } else { - None - }; - - check_expected_failure_message(test_function, assert_message, Some(err.into())) + check_expected_failure_message(test_function, None, Some(err.into())) } /// The test function compiled successfully. @@ -84,6 +74,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct /// passed/failed to determine the test status. fn test_status_program_compile_pass( test_function: &TestFunction, + abi: Abi, debug: Vec, circuit_execution: Result, ) -> TestStatus { @@ -102,10 +93,12 @@ fn test_status_program_compile_pass( Err(err) => err, }; + dbg!(&abi.error_types); + // If we reach here, then the circuit execution failed. // // Check if the function should have passed - let diagnostic = try_to_diagnose_runtime_error(&circuit_execution_err, &debug); + let diagnostic = try_to_diagnose_runtime_error(&circuit_execution_err, &abi, &debug); let test_should_have_passed = !test_function.should_fail(); if test_should_have_passed { return TestStatus::Fail { @@ -116,7 +109,7 @@ fn test_status_program_compile_pass( check_expected_failure_message( test_function, - circuit_execution_err.user_defined_failure_message().map(|s| s.to_string()), + circuit_execution_err.user_defined_failure_message(abi.error_types).map(|s| s.to_string()), diagnostic, ) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index a353065491f2..854ad5590121 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -154,7 +154,9 @@ pub(crate) fn execute_program( warnings: compiled_program.warnings.clone(), }; - if let Some(diagnostic) = try_to_diagnose_runtime_error(&err, &compiled_program.debug) { + if let Some(diagnostic) = + try_to_diagnose_runtime_error(&err, &compiled_program.abi, &compiled_program.debug) + { diagnostic.report(&debug_artifact, false); } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs index 023195010ac7..bd038c51ad5a 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/fs/inputs.rs @@ -107,6 +107,7 @@ mod tests { // Neither of these should be relevant so we leave them empty. param_witnesses: BTreeMap::new(), return_witnesses: Vec::new(), + error_types: BTreeMap::new(), }; let input_map = BTreeMap::from([ ("foo".to_owned(), InputValue::Field(42u128.into())), diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index b7fe1ef8084a..3f633f86bbc2 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -14,6 +14,7 @@ noirc_frontend.workspace = true toml.workspace = true serde_json = "1.0" serde.workspace = true +serde_with.workspace = true thiserror.workspace = true num-bigint = "0.4" num-traits = "0.2" diff --git a/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs b/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs index 4cf66820b8d0..9629ddc87ab2 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs @@ -270,6 +270,7 @@ mod serialization_tests { // These two fields are unused when serializing/deserializing to file. param_witnesses: BTreeMap::new(), return_witnesses: Vec::new(), + error_types: Default::default(), }; let input_map: BTreeMap = BTreeMap::from([ diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 6ad13500bdd3..e3ffcc74a27f 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -4,7 +4,10 @@ #![warn(clippy::semicolon_if_nothing_returned)] use acvm::{ - acir::native_types::{Witness, WitnessMap}, + acir::{ + circuit::ResolvedOpcodeLocation, + native_types::{Witness, WitnessMap}, + }, FieldElement, }; use errors::AbiError; @@ -12,6 +15,8 @@ use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; use noirc_frontend::{hir::Context, Signedness, Type, TypeBinding, TypeVariableKind, Visibility}; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use serde_with::DisplayFromStr; use std::ops::Range; use std::{collections::BTreeMap, str}; // This is the ABI used to bridge the different TOML formats for the initial @@ -66,6 +71,10 @@ pub enum AbiType { String { length: u64, }, + FmtString { + length: u64, + item_types: Vec, + }, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -171,6 +180,15 @@ impl AbiType { let fields = vecmap(fields, |typ| Self::from_type(context, typ)); Self::Tuple { fields } } + Type::FmtString(len, item_types) => { + let length = len.evaluate_to_u64().expect("Cannot evaluate fmt length"); + let Type::Tuple(item_types) = item_types.as_ref() else { + unreachable!("FmtString items must be a tuple") + }; + let item_types = vecmap(item_types, |typ| Self::from_type(context, typ)); + Self::FmtString { length, item_types } + } + Type::Error | Type::Unit | Type::Constant(_) @@ -181,7 +199,6 @@ impl AbiType { | Type::Code | Type::Slice(_) | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), - Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), } } @@ -198,6 +215,12 @@ impl AbiType { fields.iter().fold(0, |acc, field_typ| acc + field_typ.field_count()) } AbiType::String { length } => *length as u32, + AbiType::FmtString { length, item_types } => { + let items_size = item_types.iter().fold(0, |acc, item| acc + item.field_count()); + let string_size = *length as u32; + // Fmt strings include the number of items to be encoded as an extra field + string_size + 1 + items_size + } } } } @@ -222,6 +245,8 @@ pub struct AbiReturnType { pub abi_type: AbiType, pub visibility: AbiVisibility, } + +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. @@ -231,6 +256,7 @@ pub struct Abi { pub param_witnesses: BTreeMap>>, pub return_type: Option, pub return_witnesses: Vec, + pub error_types: BTreeMap, } impl Abi { @@ -280,6 +306,7 @@ impl Abi { param_witnesses, return_type: self.return_type, return_witnesses: self.return_witnesses, + error_types: self.error_types, } } @@ -488,6 +515,9 @@ fn decode_value( InputValue::Vec(tuple_elements) } + AbiType::FmtString { .. } => { + unreachable!("FmtString is not supported in decoding") + } }; Ok(value) @@ -582,6 +612,7 @@ mod test { visibility: AbiVisibility::Public, }), return_witnesses: vec![Witness(3)], + error_types: Default::default(), }; // Note we omit return value from inputs From d42e713de2d49e7e59109ab50fee7c35d1403cd8 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 12:06:03 +0000 Subject: [PATCH 02/25] wip: more dynamic assertion messages --- avm-transpiler/Cargo.lock | 1 - avm-transpiler/src/transpile.rs | 11 +- noir/noir-repo/Cargo.lock | 1 - .../acvm-repo/acir/src/circuit/mod.rs | 48 +- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 5 +- .../acvm-repo/acvm_js/src/execute.rs | 25 +- .../acvm_js/src/js_execution_error.rs | 35 +- .../compiler/noirc_driver/src/abi_gen.rs | 2 +- .../src/brillig/brillig_gen/brillig_block.rs | 4 +- .../brillig/brillig_gen/brillig_directive.rs | 3 - .../noirc_evaluator/src/brillig/brillig_ir.rs | 6 +- .../src/brillig/brillig_ir/artifact.rs | 10 +- .../brillig_ir/codegen_control_flow.rs | 7 +- .../compiler/noirc_evaluator/src/ssa.rs | 22 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 9 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 10 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 826 +++++++++--------- .../src/ssa/function_builder/mod.rs | 13 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 11 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 4 +- .../src/ssa/opt/defunctionalize.rs | 2 +- .../src/ssa/ssa_gen/context.rs | 14 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 23 +- .../src/ssa/ssa_gen/program.rs | 8 +- .../noir-repo/tooling/debugger/src/context.rs | 4 - .../tooling/debugger/src/foreign_calls.rs | 16 +- noir/noir-repo/tooling/nargo/src/errors.rs | 14 +- .../tooling/nargo/src/ops/execute.rs | 12 +- .../tooling/nargo/src/ops/foreign_calls.rs | 84 +- noir/noir-repo/tooling/nargo/src/ops/mod.rs | 4 +- noir/noir-repo/tooling/noirc_abi/Cargo.toml | 1 - noir/noir-repo/tooling/noirc_abi/src/lib.rs | 8 +- yarn-project/simulator/src/acvm/acvm.ts | 14 + .../simulator/src/avm/avm_machine_state.ts | 2 +- .../simulator/src/avm/avm_simulator.test.ts | 5 +- .../src/avm/opcodes/external_calls.test.ts | 1 + .../simulator/src/client/private_execution.ts | 8 +- .../src/client/unconstrained_execution.ts | 6 +- yarn-project/simulator/src/public/executor.ts | 6 +- 39 files changed, 611 insertions(+), 674 deletions(-) diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index e7ea90b346c5..6fc2aa934f5b 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -1206,7 +1206,6 @@ dependencies = [ "num-traits", "serde", "serde_json", - "serde_with", "thiserror", "toml", ] diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index fda785f1ff9c..f065dc160455 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -251,19 +251,16 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode]) -> Vec { ..Default::default() }); } - BrilligOpcode::Trap { - revert_data_offset, - revert_data_size, - } => { + BrilligOpcode::Trap { revert_data } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::REVERT, - indirect: Some(ALL_DIRECT), + indirect: Some(ZEROTH_OPERAND_INDIRECT), operands: vec![ AvmOperand::U32 { - value: *revert_data_offset as u32, + value: revert_data.pointer.0 as u32, }, AvmOperand::U32 { - value: *revert_data_size as u32, + value: revert_data.size as u32, }, ], ..Default::default() diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index d172098831d6..ee83f7f8ddf3 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -3073,7 +3073,6 @@ dependencies = [ "num-traits", "serde", "serde_json", - "serde_with", "strum", "strum_macros", "thiserror", diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 9730d9fb7b63..6c294353c6df 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -59,17 +59,13 @@ pub struct Circuit { pub public_parameters: PublicInputs, /// The set of public inputs calculated within the circuit. pub return_values: PublicInputs, - /// Maps opcode locations to failed assertion messages. - /// These messages are embedded in the circuit to provide useful feedback to users + /// Maps opcode locations to failed assertion payloads. + /// The data in the payload is embedded in the circuit to provide useful feedback to users /// when a constraint in the circuit is not satisfied. /// // Note: This should be a BTreeMap, but serde-reflect is creating invalid // c++ code at the moment when it is, due to OpcodeLocation needing a comparison // implementation which is never generated. - // - // TODO: These are only used for constraints that are explicitly created during code generation (such as index out of bounds on slices) - // TODO: We should move towards having all the checks being evaluated in the same manner - // TODO: as runtime assert messages specified by the user. This will also be a breaking change as the `Circuit` structure will change. pub assert_messages: Vec<(OpcodeLocation, AssertionPayload)>, /// States whether the backend should use a SNARK recursion friendly prover. @@ -81,11 +77,10 @@ pub struct Circuit { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AssertionPayload { StaticString(String), - Expression(/* error_id */ usize, Vec), - BrilligOutput(/* error_id */ usize), + Expression(/* type_id */ usize, Vec), } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone)] /// The opcode location for a call to a separate ACIR circuit /// This includes the function index of the caller within a [program][Program] /// and the index in the callers ACIR to the specific call opcode. @@ -95,41 +90,6 @@ pub struct ResolvedOpcodeLocation { pub opcode_location: OpcodeLocation, } -impl std::fmt::Display for ResolvedOpcodeLocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.acir_function_index, self.opcode_location) - } -} - -#[derive(Error, Debug)] -pub enum ResolvedOpcodeLocationFromStrError { - #[error(transparent)] - OpcodeLocationFromStrError(#[from] OpcodeLocationFromStrError), - #[error("Invalid resolved opcode location string: {0}")] - InvalidResolvedOpcodeLocationString(String), -} - -/// The implementation of display and FromStr allows serializing and deserializing a OpcodeLocation to a string. -/// This is useful when used as key in a map that has to be serialized to JSON/TOML, for example when mapping an opcode to its metadata. -impl FromStr for ResolvedOpcodeLocation { - type Err = ResolvedOpcodeLocationFromStrError; - fn from_str(s: &str) -> Result { - let parts: Vec<_> = s.split(':').collect(); - - if parts.len() != 2 { - return Err(ResolvedOpcodeLocationFromStrError::InvalidResolvedOpcodeLocationString( - s.to_string(), - )); - } - - let acir_function_index = parts[0].parse().map_err(|_| { - OpcodeLocationFromStrError::InvalidOpcodeLocationString(parts[0].to_string()) - })?; - let opcode_location = parts[1].parse()?; - Ok(ResolvedOpcodeLocation { acir_function_index, opcode_location }) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// Opcodes are locatable so that callers can /// map opcodes to debug information related to their context. diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 00c5924a082f..6080fa18924e 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -1,6 +1,6 @@ // Re-usable methods that backends can use to implement their PWG -use std::{any::Any, collections::HashMap}; +use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, @@ -413,9 +413,6 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { }) .ok() } - AssertionPayload::BrilligOutput(_) => unreachable!( - "Brillig output error type set in an ACIR opcode" - ), } } else { None diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 845c52a242c4..a5b557a18fb1 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -78,7 +78,7 @@ pub async fn execute_circuit_with_return_witness( console_error_panic_hook::set_once(); let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None, None))?; let mut witness_stack = execute_program_with_native_program_and_return( solver, @@ -93,7 +93,7 @@ pub async fn execute_circuit_with_return_witness( let main_circuit = &program.functions[0]; let return_witness = extract_indices(&solved_witness, main_circuit.return_values.0.iter().copied().collect()) - .map_err(|err| JsExecutionError::new(err, None))?; + .map_err(|err| JsExecutionError::new(err, None, None))?; Ok((solved_witness, return_witness).into()) } @@ -165,7 +165,10 @@ async fn execute_program_with_native_type_return( foreign_call_executor: &ForeignCallHandler, ) -> Result { let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + .map_err(|_| JsExecutionError::new( + "Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), + None , + None))?; execute_program_with_native_program_and_return( solver, @@ -265,9 +268,21 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { } _ => None, }; + let assertion_payload = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } + | OpcodeResolutionError::BrilligFunctionFailed { payload, .. } => { + payload.clone() + } + _ => None, + }; // TODO add payload to JsExecutionError - return Err(JsExecutionError::new(error.to_string(), call_stack).into()); + return Err(JsExecutionError::new( + error.to_string(), + call_stack, + assertion_payload, + ) + .into()); } ACVMStatus::RequiresForeignCall(foreign_call) => { let result = @@ -289,7 +304,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { call_resolved_outputs.push(*return_value); } else { // TODO: look at changing this call stack from None - return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None).into()); + return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None, None).into()); } } acvm.resolve_pending_acir_call(call_resolved_outputs); diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs index d91a9425f7ed..73bb86845fcd 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs @@ -1,11 +1,19 @@ -use acvm::acir::circuit::OpcodeLocation; -use js_sys::{Array, Error, JsString, Reflect}; +use acvm::{acir::circuit::OpcodeLocation, pwg::ResolvedAssertionPayload}; +use js_sys::{Array, Error, JsString, Map, Object, Reflect}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; +use crate::js_witness_map::field_element_to_js_string; + #[wasm_bindgen(typescript_custom_section)] const EXECUTION_ERROR: &'static str = r#" +export type RawAssertionPayload = { + typeId: number; + fields: string[]; +}; +export type ResolvedAssertionPayload = string | RawAssertionPayload; export type ExecutionError = Error & { callStack?: string[]; + assertionPayload?: ResolvedAssertionPayload; }; "#; @@ -25,7 +33,11 @@ extern "C" { impl JsExecutionError { /// Creates a new execution error with the given call stack. /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. - pub fn new(message: String, call_stack: Option>) -> Self { + pub fn new( + message: String, + call_stack: Option>, + assertion_payload: Option, + ) -> Self { let mut error = JsExecutionError::constructor(JsString::from(message)); let js_call_stack = match call_stack { Some(call_stack) => { @@ -37,8 +49,25 @@ impl JsExecutionError { } None => JsValue::UNDEFINED, }; + let assertion_payload = match assertion_payload { + Some(ResolvedAssertionPayload::String(string)) => JsValue::from(string), + Some(ResolvedAssertionPayload::Raw(type_id, fields)) => { + let raw_payload_map = Map::new(); + raw_payload_map + .set(&JsValue::from_str("typeId"), &JsValue::from(type_id.to_string())); + let js_fields = Array::new(); + for field in fields { + js_fields.push(&field_element_to_js_string(&field)); + } + raw_payload_map.set(&JsValue::from_str("fields"), &js_fields.into()); + + Object::from_entries(&raw_payload_map).unwrap().into() + } + None => JsValue::UNDEFINED, + }; error.set_property("callStack", js_call_stack); + error.set_property("assertionPayload", assertion_payload); error } diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 4deb6dc2e281..b6b2ef97678b 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use acvm::acir::{circuit::ResolvedOpcodeLocation, native_types::Witness}; +use acvm::acir::native_types::Witness; use iter_extended::{btree_map, vecmap}; use noirc_abi::{Abi, AbiParameter, AbiReturnType, AbiType, AbiValue}; use noirc_frontend::{ diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 1148303e3ba1..0dec057815b0 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -259,7 +259,7 @@ impl<'block> BrilligBlock<'block> { condition, ); match assert_message { - Some(ConstrainError::UserDefined(values, typ)) => { + Some(ConstrainError::UserDefined(values, typ_id)) => { let payload_values = vecmap(values, |value| self.convert_ssa_value(*value, dfg)); let payload_as_params = vecmap(values, |value| { @@ -270,7 +270,7 @@ impl<'block> BrilligBlock<'block> { condition, payload_values, payload_as_params, - typ.clone(), + typ_id.to_usize(), ); } Some(ConstrainError::Intrinsic(message)) => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index 6edfa0278215..4b97a61491d0 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -55,7 +55,6 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), - error_types: Default::default(), } } @@ -114,7 +113,6 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), - error_types: Default::default(), } } else { // Integer version @@ -168,7 +166,6 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { ], assert_messages: Default::default(), locations: Default::default(), - error_types: Default::default(), } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 1ebcbe556eac..2ef56eaa1ef1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -26,7 +26,7 @@ mod instructions; pub(crate) use instructions::BrilligBinaryOp; use self::{artifact::BrilligArtifact, registers::BrilligRegistersContext}; -use crate::ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}; +use crate::ssa::ir::dfg::CallStack; use acvm::acir::brillig::{MemoryAddress, Opcode as BrilligOpcode}; use debug_show::DebugShow; @@ -123,10 +123,6 @@ impl BrilligContext { pub(crate) fn set_call_stack(&mut self, call_stack: CallStack) { self.obj.set_call_stack(call_stack); } - - pub(crate) fn record_error_type(&mut self, error_type: UserDefinedErrorType) { - self.obj.error_types.push(error_type); - } } #[cfg(test)] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 84ae60813cce..dee6c6076f44 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -1,7 +1,7 @@ use acvm::acir::brillig::Opcode as BrilligOpcode; use std::collections::{BTreeMap, HashMap}; -use crate::ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}; +use crate::ssa::ir::dfg::CallStack; /// Represents a parameter or a return value of an entry point function. #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -22,7 +22,6 @@ pub(crate) struct GeneratedBrillig { pub(crate) byte_code: Vec, pub(crate) locations: BTreeMap, pub(crate) assert_messages: BTreeMap, - pub(crate) error_types: Vec, } #[derive(Default, Debug, Clone)] @@ -34,8 +33,6 @@ pub(crate) struct BrilligArtifact { /// Some error messages (compiler intrinsics) are not emitted via revert data, /// instead, they are handled externally so they don't add size to user programs. pub(crate) assert_messages: BTreeMap, - /// Types of errors this artifact can emit via revert data - pub(crate) error_types: Vec, /// The set of jumps that need to have their locations /// resolved. unresolved_jumps: Vec<(JumpInstructionPosition, UnresolvedJumpLocation)>, @@ -84,7 +81,6 @@ impl BrilligArtifact { byte_code: self.byte_code, locations: self.locations, assert_messages: self.assert_messages, - error_types: self.error_types, } } @@ -152,10 +148,6 @@ impl BrilligArtifact { self.assert_messages.insert(position_in_bytecode + offset, message.clone()); } - for error_typ in &obj.error_types { - self.error_types.push(error_typ.clone()); - } - for (position_in_bytecode, call_stack) in obj.locations.iter() { self.locations.insert(position_in_bytecode + offset, call_stack.clone()); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index ae08de7350d1..d73c93075904 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -1,7 +1,5 @@ use acvm::acir::brillig::{HeapArray, MemoryAddress}; -use crate::ssa::ir::instruction::UserDefinedErrorType; - use super::{ artifact::BrilligParameter, brillig_variable::{BrilligVariable, SingleAddrVariable}, @@ -150,7 +148,7 @@ impl BrilligContext { condition: SingleAddrVariable, revert_data_items: Vec, revert_data_types: Vec, - revert_data_type: UserDefinedErrorType, + revert_data_type: usize, ) { assert!(condition.bit_size == 1); @@ -164,7 +162,7 @@ impl BrilligContext { let current_revert_data_pointer = ctx.allocate_register(); ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); - let revert_data_id = ctx.make_usize_constant_instruction(revert_data_type.id.into()); + let revert_data_id = ctx.make_usize_constant_instruction(revert_data_type.into()); ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); for (revert_variable, revert_param) in @@ -200,7 +198,6 @@ impl BrilligContext { } ctx.trap_instruction(revert_data); }); - self.record_error_type(revert_data_type); } /// Emits brillig bytecode to jump to a trap condition if `condition` diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index f12ecf0d8de2..2c2dbedf5611 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -16,7 +16,6 @@ use crate::{ use acvm::acir::{ circuit::{ brillig::BrilligBytecode, Circuit, ExpressionWidth, Program as AcirProgram, PublicInputs, - ResolvedOpcodeLocation, }, native_types::Witness, }; @@ -29,7 +28,11 @@ use noirc_frontend::{ }; use tracing::{span, Level}; -use self::{acir_gen::GeneratedAcir, ssa_gen::Ssa}; +use self::{ + acir_gen::GeneratedAcir, + ir::instruction::{ErrorType, ErrorTypeId}, + ssa_gen::Ssa, +}; mod acir_gen; pub(super) mod function_builder; @@ -48,7 +51,10 @@ pub(crate) fn optimize_into_acir( print_brillig_trace: bool, force_brillig_output: bool, print_timings: bool, -) -> Result<(Vec, Vec), RuntimeError> { +) -> Result< + (Vec, Vec, BTreeMap), + RuntimeError, +> { let abi_distinctness = program.return_distinctness; let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); @@ -153,7 +159,7 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let (generated_acirs, generated_brillig) = optimize_into_acir( + let (generated_acirs, generated_brillig, error_types) = optimize_into_acir( program, enable_ssa_logging, enable_brillig_logging, @@ -166,11 +172,9 @@ pub fn create_program( "The generated ACIRs should match the supplied function signatures" ); - let error_types: BTreeMap = generated_acirs - .iter() - .flat_map(|acir| { - acir.error_types.iter().map(move |error_typ| (error_typ.id, error_typ.typ.clone())) - }) + let error_types = error_types + .into_iter() + .map(|(error_typ_id, error_typ)| (error_typ_id.to_usize(), error_typ)) .collect(); let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index f33ce9bb810d..9b044ecfafcd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -5,7 +5,6 @@ use crate::brillig::brillig_ir::artifact::GeneratedBrillig; use crate::errors::{InternalError, RuntimeError, SsaReport}; use crate::ssa::acir_gen::{AcirDynamicArray, AcirValue}; use crate::ssa::ir::dfg::CallStack; -use crate::ssa::ir::instruction::UserDefinedErrorType; use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; @@ -24,7 +23,6 @@ use acvm::{ }; use fxhash::FxHashMap as HashMap; use iter_extended::{try_vecmap, vecmap}; -use noirc_frontend::hir_def::types::Type as HirType; use num_bigint::BigUint; use std::{borrow::Cow, hash::Hash}; @@ -495,7 +493,7 @@ impl AcirContext { &mut self, lhs: AcirVar, rhs: AcirVar, - assert_message: Option<(AssertionPayload, Option)>, + assert_message: Option, ) -> Result<(), RuntimeError> { let lhs_expr = self.var_to_expression(lhs)?; let rhs_expr = self.var_to_expression(rhs)?; @@ -511,11 +509,8 @@ impl AcirContext { } self.acir_ir.assert_is_zero(diff_expr); - if let Some((payload, typ)) = assert_message { + if let Some(payload) = assert_message { self.acir_ir.assert_messages.insert(self.acir_ir.last_acir_opcode_location(), payload); - if let Some(typ) = typ { - self.acir_ir.error_types.push(typ); - } } self.mark_variables_equivalent(lhs, rhs)?; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 2e70e292e119..9a1c999983e4 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use crate::{ brillig::{brillig_gen::brillig_directive, brillig_ir::artifact::GeneratedBrillig}, errors::{InternalError, RuntimeError, SsaReport}, - ssa::ir::{dfg::CallStack, instruction::UserDefinedErrorType}, + ssa::ir::dfg::CallStack, }; use acvm::acir::{ circuit::{ @@ -21,7 +21,6 @@ use acvm::{ FieldElement, }; use iter_extended::vecmap; -use noirc_frontend::hir_def::types::Type as HirType; use num_bigint::BigUint; #[derive(Debug, Default)] @@ -56,7 +55,6 @@ pub(crate) struct GeneratedAcir { /// Correspondence between an opcode index and the error message associated with it. pub(crate) assert_messages: BTreeMap, - pub(crate) error_types: Vec, pub(crate) warnings: Vec, @@ -617,9 +615,6 @@ impl GeneratedAcir { AssertionPayload::StaticString(message.clone()), ); } - for error_type in generated_brillig.error_types { - self.error_types.push(error_type); - } } pub(crate) fn brillig_call( @@ -651,9 +646,6 @@ impl GeneratedAcir { AssertionPayload::StaticString(message.clone()), ); } - for error_type in generated_brillig.error_types.iter() { - self.error_types.push(error_type.clone()); - } } pub(crate) fn last_acir_opcode_location(&self) -> OpcodeLocation { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index b365c7f95856..a2db801811cd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -8,7 +8,7 @@ use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; -use super::ir::instruction::{ConstrainError, UserDefinedErrorType}; +use super::ir::instruction::{ConstrainError, ErrorType, ErrorTypeId}; use super::{ ir::{ dfg::DataFlowGraph, @@ -232,7 +232,10 @@ impl Ssa { self, brillig: &Brillig, abi_distinctness: Distinctness, - ) -> Result<(Vec, Vec), RuntimeError> { + ) -> Result< + (Vec, Vec, BTreeMap), + RuntimeError, + > { let mut acirs = Vec::new(); // TODO: can we parallelise this? let mut shared_context = SharedContext::default(); @@ -274,7 +277,7 @@ impl Ssa { } Distinctness::DuplicationAllowed => {} } - Ok((acirs, brillig)) + Ok((acirs, brillig, self.error_id_to_type)) } } @@ -534,9 +537,9 @@ impl<'a> Context<'a> { let assert_payload = if let Some(error) = assert_message { match error { ConstrainError::Intrinsic(string) => { - Some((AssertionPayload::StaticString(string.clone()), None)) + Some(AssertionPayload::StaticString(string.clone())) } - ConstrainError::UserDefined(values, UserDefinedErrorType { typ, id }) => { + ConstrainError::UserDefined(values, id) => { let acir_vars: Vec<(AcirVar, AcirType)> = values .iter() .flat_map(|value| self.convert_value(*value, dfg).flatten()) @@ -546,10 +549,7 @@ impl<'a> Context<'a> { self.acir_context.var_to_expression(var) })?; - Some(( - AssertionPayload::Expression(*id, expressions), - Some(UserDefinedErrorType { typ: typ.clone(), id: *id }), - )) + Some(AssertionPayload::Expression(id.to_usize(), expressions)) } } } else { @@ -2525,407 +2525,407 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { !types.iter().any(|typ| typ.contains_an_array()) } -#[cfg(test)] -mod test { - use acvm::{ - acir::{circuit::Opcode, native_types::Witness}, - FieldElement, - }; - - use crate::{ - brillig::Brillig, - ssa::{ - function_builder::FunctionBuilder, - ir::{ - function::{FunctionId, InlineType}, - instruction::BinaryOp, - map::Id, - types::Type, - }, - }, - }; - - fn build_basic_foo_with_return( - builder: &mut FunctionBuilder, - foo_id: FunctionId, - is_brillig_func: bool, - ) { - // fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - if is_brillig_func { - builder.new_brillig_function("foo".into(), foo_id); - } else { - builder.new_function("foo".into(), foo_id, InlineType::Fold); - } - let foo_v0 = builder.add_parameter(Type::field()); - let foo_v1 = builder.add_parameter(Type::field()); - - let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); - let zero = builder.numeric_constant(0u128, Type::unsigned(1)); - builder.insert_constrain(foo_equality_check, zero, None); - builder.terminate_with_return(vec![foo_v0]); - } - - #[test] - fn basic_call_with_outputs_assert() { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v2 = call f1(v0, v1) - // v3 = call f1(v0, v1) - // constrain v2 == v3 - // return - // } - // acir(fold) fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let main_call1_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - let main_call2_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, false); - - let ssa = builder.finish(); - - let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) - .expect("Should compile manually written SSA into ACIR"); - // Expected result: - // main f0 - // GeneratedAcir { - // ... - // opcodes: [ - // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(2)], - // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(3)], - // EXPR [ (1, _2) (-1, _3) 0 ], - // ], - // return_witnesses: [], - // input_witnesses: [ - // Witness( - // 0, - // ), - // Witness( - // 1, - // ), - // ], - // ... - // } - // foo f1 - // GeneratedAcir { - // ... - // opcodes: [ - // Same as opcodes as the expected result of `basic_call_codegen` - // ], - // return_witnesses: [ - // Witness( - // 0, - // ), - // ], - // input_witnesses: [ - // Witness( - // 0, - // ), - // Witness( - // 1, - // ), - // ], - // ... - // }, - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); - - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); - check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); - - if let Opcode::AssertZero(expr) = &main_opcodes[2] { - assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); - assert_eq!(expr.linear_combinations[0].1, Witness(2)); - - assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); - assert_eq!(expr.linear_combinations[1].1, Witness(3)); - assert_eq!(expr.q_c, FieldElement::from(0u128)); - } - } - - #[test] - fn call_output_as_next_call_input() { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) - // v4 = call f1(v3, v1) - // constrain v3 == v4 - // return - // } - // acir(fold) fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let main_call1_results = - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - let main_call2_results = builder - .insert_call(foo, vec![main_call1_results[0], main_v1], vec![Type::field()]) - .to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, false); - - let ssa = builder.finish(); - - let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) - .expect("Should compile manually written SSA into ACIR"); - // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` - // opcodes will be different. The changes can discerned from the checks below. - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); - // The output of the first call should be the input of the second call - check_call_opcode(&main_opcodes[1], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); - } - - #[test] - fn basic_nested_call() { - // SSA for the following Noir program: - // fn main(x: Field, y: pub Field) { - // let z = func_with_nested_foo_call(x, y); - // let z2 = func_with_nested_foo_call(x, y); - // assert(z == z2); - // } - // #[fold] - // fn func_with_nested_foo_call(x: Field, y: Field) -> Field { - // nested_call(x + 2, y) - // } - // #[fold] - // fn foo(x: Field, y: Field) -> Field { - // assert(x != y); - // x - // } - // - // SSA: - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) - // v4 = call f1(v0, v1) - // constrain v3 == v4 - // return - // } - // acir(fold) fn func_with_nested_foo_call f1 { - // b0(v0: Field, v1: Field): - // v3 = add v0, Field 2 - // v5 = call f2(v3, v1) - // return v5 - // } - // acir(fold) fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == Field 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let func_with_nested_foo_call_id = Id::test_new(1); - let func_with_nested_foo_call = builder.import_function(func_with_nested_foo_call_id); - let main_call1_results = builder - .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) - .to_vec(); - let main_call2_results = builder - .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) - .to_vec(); - builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); - builder.terminate_with_return(vec![]); - - builder.new_function( - "func_with_nested_foo_call".into(), - func_with_nested_foo_call_id, - InlineType::Fold, - ); - let func_with_nested_call_v0 = builder.add_parameter(Type::field()); - let func_with_nested_call_v1 = builder.add_parameter(Type::field()); - - let two = builder.field_constant(2u128); - let v0_plus_two = builder.insert_binary(func_with_nested_call_v0, BinaryOp::Add, two); - - let foo_id = Id::test_new(2); - let foo_call = builder.import_function(foo_id); - let foo_call = builder - .insert_call(foo_call, vec![v0_plus_two, func_with_nested_call_v1], vec![Type::field()]) - .to_vec(); - builder.terminate_with_return(vec![foo_call[0]]); - - build_basic_foo_with_return(&mut builder, foo_id, false); - - let ssa = builder.finish(); - - let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - - // Both of these should call func_with_nested_foo_call f1 - check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); - // The output of the first call should be the input of the second call - check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); - - let func_with_nested_call_acir = &acir_functions[1]; - let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); - assert_eq!( - func_with_nested_call_opcodes.len(), - 2, - "Should have an expression and a call to a nested `foo`" - ); - // Should call foo f2 - check_call_opcode( - &func_with_nested_call_opcodes[1], - 2, - vec![Witness(2), Witness(1)], - vec![Witness(3)], - ); - } - - fn check_call_opcode( - opcode: &Opcode, - expected_id: u32, - expected_inputs: Vec, - expected_outputs: Vec, - ) { - match opcode { - Opcode::Call { id, inputs, outputs, .. } => { - assert_eq!( - *id, expected_id, - "Main was expected to call {expected_id} but got {}", - *id - ); - for (expected_input, input) in expected_inputs.iter().zip(inputs) { - assert_eq!( - expected_input, input, - "Expected witness {expected_input:?} but got {input:?}" - ); - } - for (expected_output, output) in expected_outputs.iter().zip(outputs) { - assert_eq!( - expected_output, output, - "Expected witness {expected_output:?} but got {output:?}" - ); - } - } - _ => panic!("Expected only Call opcode"), - } - } - - // Test that given multiple calls to the same brillig function we generate only one bytecode - // and the appropriate Brillig call opcodes are generated - #[test] - fn multiple_brillig_calls_one_bytecode() { - // acir(inline) fn main f0 { - // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) - // v4 = call f1(v0, v1) - // v5 = call f1(v0, v1) - // v6 = call f1(v0, v1) - // return - // } - // brillig fn foo f1 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - // brillig fn foo f2 { - // b0(v0: Field, v1: Field): - // v2 = eq v0, v1 - // constrain v2 == u1 0 - // return v0 - // } - let foo_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), foo_id); - let main_v0 = builder.add_parameter(Type::field()); - let main_v1 = builder.add_parameter(Type::field()); - - let foo_id = Id::test_new(1); - let foo = builder.import_function(foo_id); - let bar_id = Id::test_new(2); - let bar = builder.import_function(bar_id); - - // Insert multiple calls to the same Brillig function - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); - builder.terminate_with_return(vec![]); - - build_basic_foo_with_return(&mut builder, foo_id, true); - build_basic_foo_with_return(&mut builder, bar_id, true); - - let ssa = builder.finish(); - let brillig = ssa.to_brillig(false); - println!("{}", ssa); - - let (acir_functions, brillig_functions) = ssa - .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) - .expect("Should compile manually written SSA into ACIR"); - - assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); - assert_eq!( - brillig_functions.len(), - 2, - "Should only have generated a single Brillig function" - ); - - let main_acir = &acir_functions[0]; - let main_opcodes = main_acir.opcodes(); - assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); - - // We should only have `BrilligCall` opcodes in `main` - for (i, opcode) in main_opcodes.iter().enumerate() { - match opcode { - Opcode::BrilligCall { id, .. } => { - let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; - assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); - } - _ => panic!("Expected only Brillig call opcode"), - } - } - } -} +// #[cfg(test)] +// mod test { +// use acvm::{ +// acir::{circuit::Opcode, native_types::Witness}, +// FieldElement, +// }; + +// use crate::{ +// brillig::Brillig, +// ssa::{ +// function_builder::FunctionBuilder, +// ir::{ +// function::{FunctionId, InlineType}, +// instruction::BinaryOp, +// map::Id, +// types::Type, +// }, +// }, +// }; + +// fn build_basic_foo_with_return( +// builder: &mut FunctionBuilder, +// foo_id: FunctionId, +// is_brillig_func: bool, +// ) { +// // fn foo f1 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == u1 0 +// // return v0 +// // } +// if is_brillig_func { +// builder.new_brillig_function("foo".into(), foo_id); +// } else { +// builder.new_function("foo".into(), foo_id, InlineType::Fold); +// } +// let foo_v0 = builder.add_parameter(Type::field()); +// let foo_v1 = builder.add_parameter(Type::field()); + +// let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); +// let zero = builder.numeric_constant(0u128, Type::unsigned(1)); +// builder.insert_constrain(foo_equality_check, zero, None); +// builder.terminate_with_return(vec![foo_v0]); +// } + +// #[test] +// fn basic_call_with_outputs_assert() { +// // acir(inline) fn main f0 { +// // b0(v0: Field, v1: Field): +// // v2 = call f1(v0, v1) +// // v3 = call f1(v0, v1) +// // constrain v2 == v3 +// // return +// // } +// // acir(fold) fn foo f1 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == u1 0 +// // return v0 +// // } +// let foo_id = Id::test_new(0); +// let mut builder = FunctionBuilder::new("main".into(), foo_id); +// let main_v0 = builder.add_parameter(Type::field()); +// let main_v1 = builder.add_parameter(Type::field()); + +// let foo_id = Id::test_new(1); +// let foo = builder.import_function(foo_id); +// let main_call1_results = +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// let main_call2_results = +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); +// builder.terminate_with_return(vec![]); + +// build_basic_foo_with_return(&mut builder, foo_id, false); + +// let ssa = builder.finish(); + +// let (acir_functions, _) = ssa +// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) +// .expect("Should compile manually written SSA into ACIR"); +// // Expected result: +// // main f0 +// // GeneratedAcir { +// // ... +// // opcodes: [ +// // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(2)], +// // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(3)], +// // EXPR [ (1, _2) (-1, _3) 0 ], +// // ], +// // return_witnesses: [], +// // input_witnesses: [ +// // Witness( +// // 0, +// // ), +// // Witness( +// // 1, +// // ), +// // ], +// // ... +// // } +// // foo f1 +// // GeneratedAcir { +// // ... +// // opcodes: [ +// // Same as opcodes as the expected result of `basic_call_codegen` +// // ], +// // return_witnesses: [ +// // Witness( +// // 0, +// // ), +// // ], +// // input_witnesses: [ +// // Witness( +// // 0, +// // ), +// // Witness( +// // 1, +// // ), +// // ], +// // ... +// // }, + +// let main_acir = &acir_functions[0]; +// let main_opcodes = main_acir.opcodes(); +// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); + +// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); +// check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + +// if let Opcode::AssertZero(expr) = &main_opcodes[2] { +// assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); +// assert_eq!(expr.linear_combinations[0].1, Witness(2)); + +// assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); +// assert_eq!(expr.linear_combinations[1].1, Witness(3)); +// assert_eq!(expr.q_c, FieldElement::from(0u128)); +// } +// } + +// #[test] +// fn call_output_as_next_call_input() { +// // acir(inline) fn main f0 { +// // b0(v0: Field, v1: Field): +// // v3 = call f1(v0, v1) +// // v4 = call f1(v3, v1) +// // constrain v3 == v4 +// // return +// // } +// // acir(fold) fn foo f1 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == u1 0 +// // return v0 +// // } +// let foo_id = Id::test_new(0); +// let mut builder = FunctionBuilder::new("main".into(), foo_id); +// let main_v0 = builder.add_parameter(Type::field()); +// let main_v1 = builder.add_parameter(Type::field()); + +// let foo_id = Id::test_new(1); +// let foo = builder.import_function(foo_id); +// let main_call1_results = +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// let main_call2_results = builder +// .insert_call(foo, vec![main_call1_results[0], main_v1], vec![Type::field()]) +// .to_vec(); +// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); +// builder.terminate_with_return(vec![]); + +// build_basic_foo_with_return(&mut builder, foo_id, false); + +// let ssa = builder.finish(); + +// let (acir_functions, _) = ssa +// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) +// .expect("Should compile manually written SSA into ACIR"); +// // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` +// // opcodes will be different. The changes can discerned from the checks below. + +// let main_acir = &acir_functions[0]; +// let main_opcodes = main_acir.opcodes(); +// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); + +// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); +// // The output of the first call should be the input of the second call +// check_call_opcode(&main_opcodes[1], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); +// } + +// #[test] +// fn basic_nested_call() { +// // SSA for the following Noir program: +// // fn main(x: Field, y: pub Field) { +// // let z = func_with_nested_foo_call(x, y); +// // let z2 = func_with_nested_foo_call(x, y); +// // assert(z == z2); +// // } +// // #[fold] +// // fn func_with_nested_foo_call(x: Field, y: Field) -> Field { +// // nested_call(x + 2, y) +// // } +// // #[fold] +// // fn foo(x: Field, y: Field) -> Field { +// // assert(x != y); +// // x +// // } +// // +// // SSA: +// // acir(inline) fn main f0 { +// // b0(v0: Field, v1: Field): +// // v3 = call f1(v0, v1) +// // v4 = call f1(v0, v1) +// // constrain v3 == v4 +// // return +// // } +// // acir(fold) fn func_with_nested_foo_call f1 { +// // b0(v0: Field, v1: Field): +// // v3 = add v0, Field 2 +// // v5 = call f2(v3, v1) +// // return v5 +// // } +// // acir(fold) fn foo f2 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == Field 0 +// // return v0 +// // } +// let foo_id = Id::test_new(0); +// let mut builder = FunctionBuilder::new("main".into(), foo_id); +// let main_v0 = builder.add_parameter(Type::field()); +// let main_v1 = builder.add_parameter(Type::field()); + +// let func_with_nested_foo_call_id = Id::test_new(1); +// let func_with_nested_foo_call = builder.import_function(func_with_nested_foo_call_id); +// let main_call1_results = builder +// .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) +// .to_vec(); +// let main_call2_results = builder +// .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) +// .to_vec(); +// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); +// builder.terminate_with_return(vec![]); + +// builder.new_function( +// "func_with_nested_foo_call".into(), +// func_with_nested_foo_call_id, +// InlineType::Fold, +// ); +// let func_with_nested_call_v0 = builder.add_parameter(Type::field()); +// let func_with_nested_call_v1 = builder.add_parameter(Type::field()); + +// let two = builder.field_constant(2u128); +// let v0_plus_two = builder.insert_binary(func_with_nested_call_v0, BinaryOp::Add, two); + +// let foo_id = Id::test_new(2); +// let foo_call = builder.import_function(foo_id); +// let foo_call = builder +// .insert_call(foo_call, vec![v0_plus_two, func_with_nested_call_v1], vec![Type::field()]) +// .to_vec(); +// builder.terminate_with_return(vec![foo_call[0]]); + +// build_basic_foo_with_return(&mut builder, foo_id, false); + +// let ssa = builder.finish(); + +// let (acir_functions, _) = ssa +// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) +// .expect("Should compile manually written SSA into ACIR"); + +// assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); + +// let main_acir = &acir_functions[0]; +// let main_opcodes = main_acir.opcodes(); +// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); + +// // Both of these should call func_with_nested_foo_call f1 +// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); +// // The output of the first call should be the input of the second call +// check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + +// let func_with_nested_call_acir = &acir_functions[1]; +// let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); +// assert_eq!( +// func_with_nested_call_opcodes.len(), +// 2, +// "Should have an expression and a call to a nested `foo`" +// ); +// // Should call foo f2 +// check_call_opcode( +// &func_with_nested_call_opcodes[1], +// 2, +// vec![Witness(2), Witness(1)], +// vec![Witness(3)], +// ); +// } + +// fn check_call_opcode( +// opcode: &Opcode, +// expected_id: u32, +// expected_inputs: Vec, +// expected_outputs: Vec, +// ) { +// match opcode { +// Opcode::Call { id, inputs, outputs, .. } => { +// assert_eq!( +// *id, expected_id, +// "Main was expected to call {expected_id} but got {}", +// *id +// ); +// for (expected_input, input) in expected_inputs.iter().zip(inputs) { +// assert_eq!( +// expected_input, input, +// "Expected witness {expected_input:?} but got {input:?}" +// ); +// } +// for (expected_output, output) in expected_outputs.iter().zip(outputs) { +// assert_eq!( +// expected_output, output, +// "Expected witness {expected_output:?} but got {output:?}" +// ); +// } +// } +// _ => panic!("Expected only Call opcode"), +// } +// } + +// // Test that given multiple calls to the same brillig function we generate only one bytecode +// // and the appropriate Brillig call opcodes are generated +// #[test] +// fn multiple_brillig_calls_one_bytecode() { +// // acir(inline) fn main f0 { +// // b0(v0: Field, v1: Field): +// // v3 = call f1(v0, v1) +// // v4 = call f1(v0, v1) +// // v5 = call f1(v0, v1) +// // v6 = call f1(v0, v1) +// // return +// // } +// // brillig fn foo f1 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == u1 0 +// // return v0 +// // } +// // brillig fn foo f2 { +// // b0(v0: Field, v1: Field): +// // v2 = eq v0, v1 +// // constrain v2 == u1 0 +// // return v0 +// // } +// let foo_id = Id::test_new(0); +// let mut builder = FunctionBuilder::new("main".into(), foo_id); +// let main_v0 = builder.add_parameter(Type::field()); +// let main_v1 = builder.add_parameter(Type::field()); + +// let foo_id = Id::test_new(1); +// let foo = builder.import_function(foo_id); +// let bar_id = Id::test_new(2); +// let bar = builder.import_function(bar_id); + +// // Insert multiple calls to the same Brillig function +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions +// builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); +// builder.terminate_with_return(vec![]); + +// build_basic_foo_with_return(&mut builder, foo_id, true); +// build_basic_foo_with_return(&mut builder, bar_id, true); + +// let ssa = builder.finish(); +// let brillig = ssa.to_brillig(false); +// println!("{}", ssa); + +// let (acir_functions, brillig_functions) = ssa +// .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) +// .expect("Should compile manually written SSA into ACIR"); + +// assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); +// assert_eq!( +// brillig_functions.len(), +// 2, +// "Should only have generated a single Brillig function" +// ); + +// let main_acir = &acir_functions[0]; +// let main_opcodes = main_acir.opcodes(); +// assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); + +// // We should only have `BrilligCall` opcodes in `main` +// for (i, opcode) in main_opcodes.iter().enumerate() { +// match opcode { +// Opcode::BrilligCall { id, .. } => { +// let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; +// assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); +// } +// _ => panic!("Expected only Brillig call opcode"), +// } +// } +// } +// } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 3b9ce4220217..86aa631c3571 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -1,10 +1,9 @@ pub(crate) mod data_bus; -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, collections::BTreeMap, rc::Rc}; use acvm::FieldElement; use noirc_errors::Location; -use noirc_frontend::hir_def::types::Type as HirType; use crate::ssa::ir::{ basic_block::BasicBlockId, @@ -19,7 +18,7 @@ use super::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, function::{InlineType, RuntimeType}, - instruction::{ConstrainError, InstructionId, Intrinsic}, + instruction::{ConstrainError, ErrorType, ErrorTypeId, InstructionId, Intrinsic}, }, ssa_gen::Ssa, }; @@ -36,6 +35,7 @@ pub(crate) struct FunctionBuilder { current_block: BasicBlockId, finished_functions: Vec, call_stack: CallStack, + error_types: BTreeMap, } impl FunctionBuilder { @@ -51,6 +51,7 @@ impl FunctionBuilder { current_function: new_function, finished_functions: Vec::new(), call_stack: CallStack::new(), + error_types: Default::default(), } } @@ -100,7 +101,7 @@ impl FunctionBuilder { /// Consume the FunctionBuilder returning all the functions it has generated. pub(crate) fn finish(mut self) -> Ssa { self.finished_functions.push(self.current_function); - Ssa::new(self.finished_functions) + Ssa::new(self.finished_functions, self.error_types) } /// Add a parameter to the current function with the given parameter type. @@ -475,6 +476,10 @@ impl FunctionBuilder { } } } + + pub(crate) fn record_error_type(&mut self, id: ErrorTypeId, typ: ErrorType) { + self.error_types.insert(id, typ); + } } impl std::ops::Index for FunctionBuilder { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index c51d344937f6..9367ab831bb4 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -351,7 +351,7 @@ impl Instruction { ConstrainError::UserDefined(payload_values, typ) => { ConstrainError::UserDefined( payload_values.iter().map(|&value| f(value)).collect(), - typ.clone(), + *typ, ) } _ => error.clone(), @@ -602,14 +602,11 @@ pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), // These are errors issued by the user - UserDefined(Vec, UserDefinedErrorType), + UserDefined(Vec, ErrorTypeId), } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) struct UserDefinedErrorType { - pub(crate) typ: HirType, - pub(crate) id: usize, -} +pub(crate) type ErrorType = HirType; +pub(crate) type ErrorTypeId = Id; impl From for ConstrainError { fn from(value: String) -> Self { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 7808747a5365..e4ec1d8e4c51 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -204,8 +204,8 @@ fn display_constrain_error( ConstrainError::Intrinsic(assert_message_string) => { writeln!(f, "{assert_message_string:?}") } - ConstrainError::UserDefined(values, typ) => { - writeln!(f, "{}: {}", value_list(function, values), typ.typ) + ConstrainError::UserDefined(values, _) => { + writeln!(f, "{}", value_list(function, values)) } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index 3427236b2001..cfeb8751f25c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -121,7 +121,7 @@ impl DefunctionalizationContext { } _ => {} } - if let Some(mut new_instruction) = replacement_instruction { + if let Some(new_instruction) = replacement_instruction { func.dfg[instruction_id] = new_instruction; } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index a63c11f553d6..4cc698c891c5 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -1,5 +1,4 @@ use std::rc::Rc; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Mutex, RwLock}; use acvm::FieldElement; @@ -15,9 +14,9 @@ use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::dfg::DataFlowGraph; use crate::ssa::ir::function::{Function, RuntimeType}; use crate::ssa::ir::function::{FunctionId as IrFunctionId, InlineType}; -use crate::ssa::ir::instruction::BinaryOp; -use crate::ssa::ir::instruction::Instruction; -use crate::ssa::ir::map::{AtomicCounter, Id}; +use crate::ssa::ir::instruction::{BinaryOp, ErrorType}; +use crate::ssa::ir::instruction::{ErrorTypeId, Instruction}; +use crate::ssa::ir::map::AtomicCounter; use crate::ssa::ir::types::{NumericType, Type}; use crate::ssa::ir::value::ValueId; @@ -74,7 +73,7 @@ pub(super) struct SharedContext { /// Shared counter used to assign the ID of the next function function_counter: AtomicCounter, - error_id_counter: AtomicUsize, + error_id_counter: AtomicCounter, /// The entire monomorphized source program pub(super) program: Program, @@ -1237,8 +1236,9 @@ impl SharedContext { next_id } - pub(super) fn create_error_id(&self) -> usize { - self.error_id_counter.fetch_add(1, Ordering::Relaxed) + /// Used to create a unique error id for matching error types used in the program. + pub(super) fn create_error_id(&self) -> ErrorTypeId { + self.error_id_counter.next() } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index b0fe3f5d79f7..46a349574c88 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -30,7 +30,7 @@ use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{BinaryOp, ConstrainError, TerminatorInstruction, UserDefinedErrorType}, + instruction::{BinaryOp, ConstrainError, TerminatorInstruction}, types::Type, value::ValueId, }, @@ -707,14 +707,19 @@ impl<'a> FunctionContext<'a> { let Some(assert_message_payload) = assert_message else { return Ok(None) }; let (assert_message_expression, assert_message_typ) = assert_message_payload.as_ref(); - let values = self.codegen_expression(assert_message_expression)?.into_value_list(self); - Ok(Some(ConstrainError::UserDefined( - values, - UserDefinedErrorType { - typ: assert_message_typ.clone(), - id: self.shared_context.create_error_id(), - }, - ))) + let values: Vec> = + self.codegen_expression(assert_message_expression)?.into_value_list(self); + + let error_type_id = self.shared_context.create_error_id(); + + // Do not record string errors in the ABI + match assert_message_typ { + HirType::String(_) => {} + _ => { + self.builder.record_error_type(error_type_id, assert_message_typ.clone()); + } + }; + Ok(Some(ConstrainError::UserDefined(values, error_type_id))) } fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 291b694b9be8..13066d81d53e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -4,6 +4,7 @@ use iter_extended::btree_map; use crate::ssa::ir::{ function::{Function, FunctionId, RuntimeType}, + instruction::ErrorTypeId, map::AtomicCounter, }; use noirc_frontend::hir_def::types::Type as HirType; @@ -18,12 +19,16 @@ pub(crate) struct Ssa { /// This mapping is necessary to use the correct function pointer for an ACIR call, /// as the final program artifact will be a list of only entry point functions. pub(crate) entry_point_to_generated_index: BTreeMap, + pub(crate) error_id_to_type: BTreeMap, } impl Ssa { /// Create a new Ssa object from the given SSA functions. /// The first function in this vector is expected to be the main function. - pub(crate) fn new(functions: Vec) -> Self { + pub(crate) fn new( + functions: Vec, + error_types: BTreeMap, + ) -> Self { let main_id = functions.first().expect("Expected at least 1 SSA function").id(); let mut max_id = main_id; @@ -51,6 +56,7 @@ impl Ssa { main_id, next_id: AtomicCounter::starting_after(max_id), entry_point_to_generated_index, + error_id_to_type: error_types, } } diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 3c246d91e273..aa3eaad61420 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -2,7 +2,6 @@ use crate::foreign_calls::DebugForeignCallExecutor; use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; -use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::brillig_vm::MemoryValue; use acvm::pwg::{ ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM, @@ -350,9 +349,6 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let foreign_call_result = self.foreign_call_executor.execute(&foreign_call); match foreign_call_result { Ok(foreign_call_result) => { - let foreign_call_result = foreign_call_result - .get_brillig_output() - .unwrap_or(ForeignCallResult::default()); if let Some(mut solver) = self.brillig_solver.take() { solver.resolve_pending_foreign_call(foreign_call_result); self.brillig_solver = Some(solver); diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index f11ac22cd759..209439f5f92a 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -5,7 +5,7 @@ use acvm::{ }; use nargo::{ artifacts::debug::{DebugArtifact, DebugVars, StackFrame}, - ops::{DefaultForeignCallExecutor, ForeignCallExecutor, NargoForeignCallResult}, + ops::{DefaultForeignCallExecutor, ForeignCallExecutor}, }; use noirc_errors::debug_info::{DebugFnId, DebugVarId}; use noirc_printable_type::ForeignCallError; @@ -94,7 +94,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result { + ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match DebugForeignCall::lookup(foreign_call_name) { Some(DebugForeignCall::VarAssign) => { @@ -105,7 +105,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { foreign_call.inputs[1..].iter().flat_map(|x| x.fields()).collect(); self.debug_vars.assign_var(var_id, &values); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::VarDrop) => { let fcp_var_id = &foreign_call.inputs[0]; @@ -113,7 +113,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { let var_id = debug_var_id(var_id_value); self.debug_vars.drop_var(var_id); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::MemberAssign(arity)) => { if let Some(ForeignCallParam::Single(var_id_value)) = foreign_call.inputs.first() { @@ -141,7 +141,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { .collect(); self.debug_vars.assign_field(var_id, indexes, &values); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::DerefAssign) => { let fcp_var_id = &foreign_call.inputs[0]; @@ -150,7 +150,7 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { let var_id = debug_var_id(var_id_value); self.debug_vars.assign_deref(var_id, &fcp_value.fields()); } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::FnEnter) => { let fcp_fn_id = &foreign_call.inputs[0]; @@ -159,11 +159,11 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { }; let fn_id = debug_fn_id(fn_id_value); self.debug_vars.push_fn(fn_id); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(DebugForeignCall::FnExit) => { self.debug_vars.pop_fn(); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } None => self.executor.execute(foreign_call), } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 9bffaf7c51b8..2ba3859eb73c 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -76,7 +76,7 @@ impl NargoError { let decoded = prepare_for_display(fields, abi_type.clone()); Some(decoded.to_string()) } else { - None + Some(display_as_string(fields)) } } }, @@ -236,7 +236,7 @@ fn extract_message_from_error( if let Some(abi_type) = error_types.get(error_id) { format!("Assertion failed: {}", prepare_for_display(fields, abi_type.clone())) } else { - format!("Assertion failed {:?}", fields) + format!("Assertion failed {}", display_as_string(fields)) } } NargoError::ExecutionError(ExecutionError::SolvingError( @@ -253,6 +253,16 @@ fn extract_message_from_error( } } +pub fn display_as_string(fields: &[FieldElement]) -> String { + fields + .iter() + .map(|field| { + let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect() +} + /// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. pub fn try_to_diagnose_runtime_error( nargo_err: &NargoError, diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index 86a569f6713e..4a75212ba471 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -1,7 +1,6 @@ use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{OpcodeLocation, Program, ResolvedOpcodeLocation}; use acvm::acir::native_types::WitnessStack; -use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -9,7 +8,7 @@ use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use crate::errors::ExecutionError; use crate::NargoError; -use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; +use super::foreign_calls::ForeignCallExecutor; struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> { functions: &'a [Circuit], @@ -121,14 +120,7 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, } ACVMStatus::RequiresForeignCall(foreign_call) => { let foreign_call_result = self.foreign_call_executor.execute(&foreign_call)?; - match foreign_call_result { - NargoForeignCallResult::BrilligOutput(foreign_call_result) => { - acvm.resolve_pending_foreign_call(foreign_call_result); - } - NargoForeignCallResult::ResolvedAssertMessage(message) => { - unreachable!("Assert messages shouln't be resolved via foreign calls") - } - } + acvm.resolve_pending_foreign_call(foreign_call_result); } ACVMStatus::RequiresAcirCall(call_info) => { // Store the parent function index whose context we are currently executing diff --git a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs index 33767314a37d..90f6659ad284 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs @@ -10,69 +10,13 @@ pub trait ForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result; -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum NargoForeignCallResult { - BrilligOutput(ForeignCallResult), - ResolvedAssertMessage(String), -} - -impl NargoForeignCallResult { - pub fn get_assert_message(self) -> Option { - match self { - Self::ResolvedAssertMessage(msg) => Some(msg), - _ => None, - } - } - - pub fn get_brillig_output(self) -> Option { - match self { - Self::BrilligOutput(foreign_call_result) => Some(foreign_call_result), - _ => None, - } - } -} - -impl From for NargoForeignCallResult { - fn from(value: ForeignCallResult) -> Self { - Self::BrilligOutput(value) - } -} - -impl From for NargoForeignCallResult { - fn from(value: String) -> Self { - Self::ResolvedAssertMessage(value) - } -} - -impl From for NargoForeignCallResult { - fn from(value: FieldElement) -> Self { - let foreign_call_result: ForeignCallResult = value.into(); - foreign_call_result.into() - } -} - -impl From> for NargoForeignCallResult { - fn from(values: Vec) -> Self { - let foreign_call_result: ForeignCallResult = values.into(); - foreign_call_result.into() - } -} - -impl From> for NargoForeignCallResult { - fn from(values: Vec) -> Self { - let foreign_call_result: ForeignCallResult = values.into(); - foreign_call_result.into() - } + ) -> Result; } /// 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 pub enum ForeignCall { Print, - AssertMessage, CreateMock, SetMockParams, GetMockLastParams, @@ -91,7 +35,6 @@ impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { ForeignCall::Print => "print", - ForeignCall::AssertMessage => "assert_message", ForeignCall::CreateMock => "create_mock", ForeignCall::SetMockParams => "set_mock_params", ForeignCall::GetMockLastParams => "get_mock_last_params", @@ -104,7 +47,6 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { "print" => Some(ForeignCall::Print), - "assert_message" => Some(ForeignCall::AssertMessage), "create_mock" => Some(ForeignCall::CreateMock), "set_mock_params" => Some(ForeignCall::SetMockParams), "get_mock_last_params" => Some(ForeignCall::GetMockLastParams), @@ -223,13 +165,6 @@ impl DefaultForeignCallExecutor { Ok(()) } - fn execute_assert_message( - foreign_call_inputs: &[ForeignCallParam], - ) -> Result { - let display_string = Self::format_printable_value(foreign_call_inputs, true)?; - Ok(display_string.into()) - } - fn format_printable_value( foreign_call_inputs: &[ForeignCallParam], skip_newline: bool, @@ -246,16 +181,15 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result { + ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { Some(ForeignCall::Print) => { if self.show_output { Self::execute_print(&foreign_call.inputs)?; } - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } - Some(ForeignCall::AssertMessage) => Self::execute_assert_message(&foreign_call.inputs), Some(ForeignCall::CreateMock) => { let mock_oracle_name = Self::parse_string(&foreign_call.inputs[0]); assert!(ForeignCall::lookup(&mock_oracle_name).is_none()); @@ -271,7 +205,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .params = Some(params.to_vec()); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::GetMockLastParams) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -291,7 +225,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .result = ForeignCallResult { values: params.to_vec() }; - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::SetMockTimes) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -302,12 +236,12 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .times_left = Some(times); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } Some(ForeignCall::ClearMock) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; self.mocked_responses.retain(|response| response.id != id); - Ok(ForeignCallResult::default().into()) + Ok(ForeignCallResult::default()) } None => { let mock_response_position = self @@ -346,7 +280,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { let parsed_response: ForeignCallResult = response.result()?; - Ok(parsed_response.into()) + Ok(parsed_response) } (None, None) => panic!( "No mock for foreign call {}({:?})", @@ -423,7 +357,7 @@ mod tests { }; let result = executor.execute(&foreign_call); - assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }.into()); + assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }); server.close(); } diff --git a/noir/noir-repo/tooling/nargo/src/ops/mod.rs b/noir/noir-repo/tooling/nargo/src/ops/mod.rs index 2f5e6ebb7d43..cada2f0e9156 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/mod.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/mod.rs @@ -3,9 +3,7 @@ pub use self::compile::{ compile_workspace, report_errors, }; pub use self::execute::execute_program; -pub use self::foreign_calls::{ - DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor, NargoForeignCallResult, -}; +pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor}; pub use self::optimize::{optimize_contract, optimize_program}; pub use self::transform::{transform_contract, transform_program}; diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index 3f633f86bbc2..b7fe1ef8084a 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -14,7 +14,6 @@ noirc_frontend.workspace = true toml.workspace = true serde_json = "1.0" serde.workspace = true -serde_with.workspace = true thiserror.workspace = true num-bigint = "0.4" num-traits = "0.2" diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index e3ffcc74a27f..0c83da577d1e 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -4,10 +4,7 @@ #![warn(clippy::semicolon_if_nothing_returned)] use acvm::{ - acir::{ - circuit::ResolvedOpcodeLocation, - native_types::{Witness, WitnessMap}, - }, + acir::native_types::{Witness, WitnessMap}, FieldElement, }; use errors::AbiError; @@ -15,8 +12,6 @@ use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; use noirc_frontend::{hir::Context, Signedness, Type, TypeBinding, TypeVariableKind, Visibility}; use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use serde_with::DisplayFromStr; use std::ops::Range; use std::{collections::BTreeMap, str}; // This is the ABI used to bridge the different TOML formats for the initial @@ -246,7 +241,6 @@ pub struct AbiReturnType { pub visibility: AbiVisibility, } -#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. diff --git a/yarn-project/simulator/src/acvm/acvm.ts b/yarn-project/simulator/src/acvm/acvm.ts index d166b5d16c33..2ca82fa89b31 100644 --- a/yarn-project/simulator/src/acvm/acvm.ts +++ b/yarn-project/simulator/src/acvm/acvm.ts @@ -12,6 +12,7 @@ import { import { traverseCauseChain } from '../common/errors.js'; import { type ACVMWitness } from './acvm_types.js'; +import { fromACVMField } from './deserialize.js'; import { type ORACLE_NAMES } from './oracle/index.js'; /** @@ -157,3 +158,16 @@ export function extractCallStack( return callStack; } } + +export function extractAssertionMessage(error: Error | ExecutionError): string | undefined { + if (!('assertionPayload' in error) || !error.assertionPayload) { + return undefined; + } + const { assertionPayload } = error; + + if (typeof assertionPayload === 'string') { + return assertionPayload; + } else { + return String.fromCharCode(...assertionPayload.fields.map((field: string) => fromACVMField(field).toNumber())); + } +} diff --git a/yarn-project/simulator/src/avm/avm_machine_state.ts b/yarn-project/simulator/src/avm/avm_machine_state.ts index 8ebfa6b1e47d..4183e1ff9c7d 100644 --- a/yarn-project/simulator/src/avm/avm_machine_state.ts +++ b/yarn-project/simulator/src/avm/avm_machine_state.ts @@ -143,7 +143,7 @@ export class AvmMachineState { try { // Try to interpret the output as a text string. revertReason = new Error( - 'Reverted with output: ' + String.fromCharCode(...this.output.map(fr => fr.toNumber())), + 'Reverted with output: ' + String.fromCharCode(...this.output.slice(1).map(fr => fr.toNumber())), ); } catch (e) { revertReason = new Error('Reverted with non-string output'); diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 411d6c8fa84b..b630bf704430 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -114,7 +114,10 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(true); expect(results.revertReason?.message).toEqual("Reverted with output: Nullifier doesn't exist!"); - expect(results.output).toEqual([..."Nullifier doesn't exist!"].flatMap(c => new Fr(c.charCodeAt(0)))); + expect(results.output).toEqual([ + new Fr(0), + ...[..."Nullifier doesn't exist!"].flatMap(c => new Fr(c.charCodeAt(0))), + ]); }); describe.each([ diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts index 29c6626fa8d0..09fa7e0b5c1a 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts @@ -311,6 +311,7 @@ describe('External Calls', () => { it('Should return data and revert from the revert opcode', async () => { const returnData = [...'assert message'].flatMap(c => new Field(c.charCodeAt(0))); + returnData.unshift(new Field(0n)); // Prepend an error id context.machineState.memory.setSlice(0, returnData); diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 64cf9baae7d8..7176187699fb 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -4,8 +4,10 @@ import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; +import { error } from 'console'; + import { witnessMapToFields } from '../acvm/deserialize.js'; -import { Oracle, acvm, extractCallStack } from '../acvm/index.js'; +import { Oracle, acvm, extractAssertionMessage, extractCallStack } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { type ClientExecutionContext } from './client_execution_context.js'; import { type ExecutionResult } from './execution_result.js'; @@ -28,6 +30,10 @@ export async function executePrivateFunction( const acvmCallback = new Oracle(context); const acirExecutionResult = await acvm(await AcirSimulator.getSolver(), acir, initialWitness, acvmCallback).catch( (err: Error) => { + const assertionMessage = extractAssertionMessage(err); + if (assertionMessage) { + err.message = `Assertion failed: ${assertionMessage}`; + } throw new ExecutionError( err.message, { diff --git a/yarn-project/simulator/src/client/unconstrained_execution.ts b/yarn-project/simulator/src/client/unconstrained_execution.ts index d821ca9fea9c..30d76682e4a3 100644 --- a/yarn-project/simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/simulator/src/client/unconstrained_execution.ts @@ -5,7 +5,7 @@ import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { witnessMapToFields } from '../acvm/deserialize.js'; -import { Oracle, acvm, extractCallStack, toACVMWitness } from '../acvm/index.js'; +import { Oracle, acvm, extractAssertionMessage, extractCallStack, toACVMWitness } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { AcirSimulator } from './simulator.js'; import { type ViewDataOracle } from './view_data_oracle.js'; @@ -33,6 +33,10 @@ export async function executeUnconstrainedFunction( initialWitness, new Oracle(oracle), ).catch((err: Error) => { + const assertionMessage = extractAssertionMessage(err); + if (assertionMessage) { + err.message = `Assertion failed: ${assertionMessage}`; + } throw new ExecutionError( err.message, { diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 412ea1b973b8..76cd54e53a73 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -6,7 +6,7 @@ import { spawn } from 'child_process'; import fs from 'fs/promises'; import path from 'path'; -import { Oracle, acvm, extractCallStack, witnessMapToFields } from '../acvm/index.js'; +import { Oracle, acvm, extractAssertionMessage, extractCallStack, witnessMapToFields } from '../acvm/index.js'; import { AvmContext } from '../avm/avm_context.js'; import { AvmMachineState } from '../avm/avm_machine_state.js'; import { AvmSimulator } from '../avm/avm_simulator.js'; @@ -105,6 +105,10 @@ async function executePublicFunctionAcvm( }; } catch (err_) { const err = err_ as Error; + const assertionMessage = extractAssertionMessage(err); + if (assertionMessage) { + err.message = `Assertion failed: ${assertionMessage}`; + } const ee = new ExecutionError( err.message, { From d9e242793f7e005c78bb9da04eea0c6f0de18d68 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 13:06:53 +0000 Subject: [PATCH 03/25] update serialization --- .../dsl/acir_format/serde/acir.hpp | 185 ++++++++++++++++-- .../src/core/libraries/ConstantsGen.sol | 2 +- .../crates/types/src/constants.nr | 2 +- .../noir-repo/acvm-repo/acir/codegen/acir.cpp | 157 ++++++++++++++- noir/noir-repo/acvm-repo/acir/src/lib.rs | 3 +- yarn-project/circuits.js/src/constants.gen.ts | 2 +- .../simulator/src/client/private_execution.ts | 2 - 7 files changed, 327 insertions(+), 26 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index 5fd065434679..f4935144f59c 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -966,8 +966,7 @@ struct BrilligOpcode { }; struct Trap { - uint64_t revert_data_offset; - uint64_t revert_data_size; + Program::HeapArray revert_data; friend bool operator==(const Trap&, const Trap&); std::vector bincodeSerialize() const; @@ -1155,6 +1154,31 @@ struct Opcode { static Opcode bincodeDeserialize(std::vector); }; +struct AssertionPayload { + + struct StaticString { + std::string value; + + friend bool operator==(const StaticString&, const StaticString&); + std::vector bincodeSerialize() const; + static StaticString bincodeDeserialize(std::vector); + }; + + struct Expression { + std::tuple> value; + + friend bool operator==(const Expression&, const Expression&); + std::vector bincodeSerialize() const; + static Expression bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const AssertionPayload&, const AssertionPayload&); + std::vector bincodeSerialize() const; + static AssertionPayload bincodeDeserialize(std::vector); +}; + struct ExpressionWidth { struct Unbounded { @@ -1219,7 +1243,7 @@ struct Circuit { std::vector private_parameters; Program::PublicInputs public_parameters; Program::PublicInputs return_values; - std::vector> assert_messages; + std::vector> assert_messages; bool recursive; friend bool operator==(const Circuit&, const Circuit&); @@ -1248,6 +1272,150 @@ struct Program { namespace Program { +inline bool operator==(const AssertionPayload& lhs, const AssertionPayload& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector AssertionPayload::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline AssertionPayload AssertionPayload::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::AssertionPayload& obj, + Serializer& serializer) +{ + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::AssertionPayload serde::Deserializable::deserialize(Deserializer& deserializer) +{ + deserializer.increase_container_depth(); + Program::AssertionPayload obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + +inline bool operator==(const AssertionPayload::StaticString& lhs, const AssertionPayload::StaticString& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector AssertionPayload::StaticString::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline AssertionPayload::StaticString AssertionPayload::StaticString::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize( + const Program::AssertionPayload::StaticString& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::StaticString serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Program::AssertionPayload::StaticString obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + +inline bool operator==(const AssertionPayload::Expression& lhs, const AssertionPayload::Expression& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector AssertionPayload::Expression::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline AssertionPayload::Expression AssertionPayload::Expression::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize( + const Program::AssertionPayload::Expression& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::Expression serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Program::AssertionPayload::Expression obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + inline bool operator==(const BinaryFieldOp& lhs, const BinaryFieldOp& rhs) { if (!(lhs.value == rhs.value)) { @@ -6063,10 +6231,7 @@ namespace Program { inline bool operator==(const BrilligOpcode::Trap& lhs, const BrilligOpcode::Trap& rhs) { - if (!(lhs.revert_data_offset == rhs.revert_data_offset)) { - return false; - } - if (!(lhs.revert_data_size == rhs.revert_data_size)) { + if (!(lhs.revert_data == rhs.revert_data)) { return false; } return true; @@ -6096,8 +6261,7 @@ template void serde::Serializable::serialize(const Program::BrilligOpcode::Trap& obj, Serializer& serializer) { - serde::Serializable::serialize(obj.revert_data_offset, serializer); - serde::Serializable::serialize(obj.revert_data_size, serializer); + serde::Serializable::serialize(obj.revert_data, serializer); } template <> @@ -6106,8 +6270,7 @@ Program::BrilligOpcode::Trap serde::Deserializable Deserializer& deserializer) { Program::BrilligOpcode::Trap obj; - obj.revert_data_offset = serde::Deserializable::deserialize(deserializer); - obj.revert_data_size = serde::Deserializable::deserialize(deserializer); + obj.revert_data = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 6661fc67443d..96bed086a3e0 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -89,7 +89,7 @@ library Constants { uint256 internal constant DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE = 0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631; uint256 internal constant DEPLOYER_CONTRACT_ADDRESS = - 0x1b5ecf3d26907648cf737f4304759b8c5850478e839e72f8ce1f5791b286e8f2; + 0x2f245b4e6e38fa79207db99c2f9572f4ab8aaf137ab36194f99c0002f02193d6; uint256 internal constant AZTEC_ADDRESS_LENGTH = 1; uint256 internal constant DIMENSION_GAS_SETTINGS_LENGTH = 3; uint256 internal constant GAS_FEES_LENGTH = 3; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index e8e5f1cf2ee7..e6b6ae213201 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -126,7 +126,7 @@ global REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE = 0xe7af8166354 // CONTRACT INSTANCE CONSTANTS // sha224sum 'struct ContractInstanceDeployed' global DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE = 0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631; -global DEPLOYER_CONTRACT_ADDRESS = 0x1b5ecf3d26907648cf737f4304759b8c5850478e839e72f8ce1f5791b286e8f2; +global DEPLOYER_CONTRACT_ADDRESS = 0x2f245b4e6e38fa79207db99c2f9572f4ab8aaf137ab36194f99c0002f02193d6; // LENGTH OF STRUCTS SERIALIZED TO FIELDS global AZTEC_ADDRESS_LENGTH = 1; diff --git a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index 6c7bd347e5d0..155438f7ffb4 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -922,8 +922,7 @@ namespace Program { }; struct Trap { - uint64_t revert_data_offset; - uint64_t revert_data_size; + Program::HeapArray revert_data; friend bool operator==(const Trap&, const Trap&); std::vector bincodeSerialize() const; @@ -1093,6 +1092,31 @@ namespace Program { static Opcode bincodeDeserialize(std::vector); }; + struct AssertionPayload { + + struct StaticString { + std::string value; + + friend bool operator==(const StaticString&, const StaticString&); + std::vector bincodeSerialize() const; + static StaticString bincodeDeserialize(std::vector); + }; + + struct Expression { + std::tuple> value; + + friend bool operator==(const Expression&, const Expression&); + std::vector bincodeSerialize() const; + static Expression bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const AssertionPayload&, const AssertionPayload&); + std::vector bincodeSerialize() const; + static AssertionPayload bincodeDeserialize(std::vector); + }; + struct ExpressionWidth { struct Unbounded { @@ -1157,7 +1181,7 @@ namespace Program { std::vector private_parameters; Program::PublicInputs public_parameters; Program::PublicInputs return_values; - std::vector> assert_messages; + std::vector> assert_messages; bool recursive; friend bool operator==(const Circuit&, const Circuit&); @@ -1185,6 +1209,124 @@ namespace Program { } // end of namespace Program +namespace Program { + + inline bool operator==(const AssertionPayload &lhs, const AssertionPayload &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline AssertionPayload AssertionPayload::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::AssertionPayload &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::AssertionPayload serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::AssertionPayload obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + + inline bool operator==(const AssertionPayload::StaticString &lhs, const AssertionPayload::StaticString &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::StaticString::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline AssertionPayload::StaticString AssertionPayload::StaticString::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::AssertionPayload::StaticString &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::StaticString serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::StaticString obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + + inline bool operator==(const AssertionPayload::Expression &lhs, const AssertionPayload::Expression &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector AssertionPayload::Expression::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline AssertionPayload::Expression AssertionPayload::Expression::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::AssertionPayload::Expression &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::AssertionPayload::Expression serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::Expression obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const BinaryFieldOp &lhs, const BinaryFieldOp &rhs) { @@ -5017,8 +5159,7 @@ Program::BrilligOpcode::BlackBox serde::Deserializable template void serde::Serializable::serialize(const Program::BrilligOpcode::Trap &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.revert_data_offset, serializer); - serde::Serializable::serialize(obj.revert_data_size, serializer); + serde::Serializable::serialize(obj.revert_data, serializer); } template <> template Program::BrilligOpcode::Trap serde::Deserializable::deserialize(Deserializer &deserializer) { Program::BrilligOpcode::Trap obj; - obj.revert_data_offset = serde::Deserializable::deserialize(deserializer); - obj.revert_data_size = serde::Deserializable::deserialize(deserializer); + obj.revert_data = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/noir/noir-repo/acvm-repo/acir/src/lib.rs b/noir/noir-repo/acvm-repo/acir/src/lib.rs index d14159f34a16..e8dc868a84db 100644 --- a/noir/noir-repo/acvm-repo/acir/src/lib.rs +++ b/noir/noir-repo/acvm-repo/acir/src/lib.rs @@ -42,7 +42,7 @@ mod reflection { brillig::{BrilligInputs, BrilligOutputs}, directives::Directive, opcodes::BlackBoxFuncCall, - Circuit, ExpressionWidth, Opcode, OpcodeLocation, Program, + AssertionPayload, Circuit, ExpressionWidth, Opcode, OpcodeLocation, Program, }, native_types::{Witness, WitnessMap, WitnessStack}, }; @@ -74,6 +74,7 @@ mod reflection { tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); + tracer.trace_simple_type::().unwrap(); let registry = tracer.registry().unwrap(); diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 6441113ed89e..7ce62e781567 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -74,7 +74,7 @@ export const REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE = 0xe7af816635466f128568edb04c9fa024f6c87fb9010fdbffa68b3d99n; export const DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE = 0x85864497636cf755ae7bde03f267ce01a520981c21c3682aaf82a631n; -export const DEPLOYER_CONTRACT_ADDRESS = 0x1b5ecf3d26907648cf737f4304759b8c5850478e839e72f8ce1f5791b286e8f2n; +export const DEPLOYER_CONTRACT_ADDRESS = 0x2f245b4e6e38fa79207db99c2f9572f4ab8aaf137ab36194f99c0002f02193d6n; export const AZTEC_ADDRESS_LENGTH = 1; export const DIMENSION_GAS_SETTINGS_LENGTH = 3; export const GAS_FEES_LENGTH = 3; diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 7176187699fb..344119163d09 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -4,8 +4,6 @@ import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { error } from 'console'; - import { witnessMapToFields } from '../acvm/deserialize.js'; import { Oracle, acvm, extractAssertionMessage, extractCallStack } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; From 851b10c77be05441a924c48666eff6dddf5fd8bc Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 13:21:08 +0000 Subject: [PATCH 04/25] uncomment units --- noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 1304 +++++++++-------- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 808 +++++----- 2 files changed, 1075 insertions(+), 1037 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index 349aefec713e..1710728987b8 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use acir::{ - brillig::{BinaryFieldOp, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, + brillig::{BinaryFieldOp, HeapArray, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray}, circuit::{ brillig::{Brillig, BrilligInputs, BrilligOutputs}, opcodes::{BlockId, MemOp}, @@ -17,635 +17,673 @@ use brillig_vm::brillig::HeapValueType; // Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. -// #[test] -// fn inversion_brillig_oracle_equivalence() { -// // Opcodes below describe the following: -// // fn main(x : Field, y : pub Field) { -// // let z = x + y; -// // assert( 1/z == Oracle("inverse", x + y) ); -// // } -// // Also performs an unrelated equality check -// // just for the sake of testing multiple brillig opcodes. -// let fe_0 = FieldElement::zero(); -// let fe_1 = FieldElement::one(); -// let w_x = Witness(1); -// let w_y = Witness(2); -// let w_oracle = Witness(3); -// let w_z = Witness(4); -// let w_z_inverse = Witness(5); -// let w_x_plus_y = Witness(6); -// let w_equal_res = Witness(7); - -// let equal_opcode = BrilligOpcode::BinaryFieldOp { -// op: BinaryFieldOp::Equals, -// lhs: MemoryAddress::from(0), -// rhs: MemoryAddress::from(1), -// destination: MemoryAddress::from(2), -// }; - -// let brillig_data = Brillig { -// inputs: vec![ -// BrilligInputs::Single(Expression { -// // Input Register 0 -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], -// q_c: fe_0, -// }), -// BrilligInputs::Single(Expression::default()), // Input Register 1 -// ], -// // This tells the BrilligSolver which witnesses its output values correspond to -// outputs: vec![ -// BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input -// BrilligOutputs::Simple(w_oracle), // Output Register 1 -// BrilligOutputs::Simple(w_equal_res), // Output Register 2 -// ], -// bytecode: vec![ -// BrilligOpcode::CalldataCopy { -// destination_address: MemoryAddress(0), -// size: 2, -// offset: 0, -// }, -// equal_opcode, -// // Oracles are named 'foreign calls' in brillig -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], -// input_value_types: vec![HeapValueType::field()], -// }, -// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, -// ], -// predicate: None, -// }; - -// let opcodes = vec![ -// Opcode::Brillig(brillig_data), -// Opcode::AssertZero(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], -// q_c: fe_0, -// }), -// // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), -// Opcode::AssertZero(Expression { -// mul_terms: vec![(fe_1, w_z, w_z_inverse)], -// linear_combinations: vec![], -// q_c: -fe_1, -// }), -// Opcode::AssertZero(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], -// q_c: fe_0, -// }), -// ]; - -// let witness_assignments = BTreeMap::from([ -// (Witness(1), FieldElement::from(2u128)), -// (Witness(2), FieldElement::from(3u128)), -// ]) -// .into(); -// let unconstrained_functions = vec![]; -// let mut acvm = -// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); -// // use the partial witness generation solver with our acir program -// let solver_status = acvm.solve(); - -// assert!( -// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), -// "should require foreign call response" -// ); -// assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed"); - -// let foreign_call_wait_info: &ForeignCallWaitInfo = -// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); -// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - -// // As caller of VM, need to resolve foreign calls -// let foreign_call_result = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); -// // Alter Brillig oracle opcode with foreign call resolution -// acvm.resolve_pending_foreign_call(foreign_call_result.into()); - -// // After filling data request, continue solving -// let solver_status = acvm.solve(); -// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - -// // ACVM should be able to be finalized in `Solved` state. -// acvm.finalize(); -// } - -// #[test] -// fn double_inversion_brillig_oracle() { -// // Opcodes below describe the following: -// // fn main(x : Field, y : pub Field) { -// // let z = x + y; -// // let ij = i + j; -// // assert( 1/z == Oracle("inverse", x + y) ); -// // assert( 1/ij == Oracle("inverse", i + j) ); -// // } -// // Also performs an unrelated equality check -// // just for the sake of testing multiple brillig opcodes. -// let fe_0 = FieldElement::zero(); -// let fe_1 = FieldElement::one(); -// let w_x = Witness(1); -// let w_y = Witness(2); -// let w_oracle = Witness(3); -// let w_z = Witness(4); -// let w_z_inverse = Witness(5); -// let w_x_plus_y = Witness(6); -// let w_equal_res = Witness(7); -// let w_i = Witness(8); -// let w_j = Witness(9); -// let w_ij_oracle = Witness(10); -// let w_i_plus_j = Witness(11); - -// let equal_opcode = BrilligOpcode::BinaryFieldOp { -// op: BinaryFieldOp::Equals, -// lhs: MemoryAddress::from(0), -// rhs: MemoryAddress::from(1), -// destination: MemoryAddress::from(4), -// }; - -// let brillig_data = Brillig { -// inputs: vec![ -// BrilligInputs::Single(Expression { -// // Input Register 0 -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], -// q_c: fe_0, -// }), -// BrilligInputs::Single(Expression::default()), // Input Register 1 -// BrilligInputs::Single(Expression { -// // Input Register 2 -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], -// q_c: fe_0, -// }), -// ], -// outputs: vec![ -// BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input -// BrilligOutputs::Simple(w_oracle), // Output Register 1 -// BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input -// BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 -// BrilligOutputs::Simple(w_equal_res), // Output Register 4 -// ], -// bytecode: vec![ -// BrilligOpcode::CalldataCopy { -// destination_address: MemoryAddress(0), -// size: 3, -// offset: 0, -// }, -// equal_opcode, -// // Oracles are named 'foreign calls' in brillig -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], -// input_value_types: vec![HeapValueType::field()], -// }, -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], -// input_value_types: vec![HeapValueType::field()], -// }, -// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 }, -// ], -// predicate: None, -// }; - -// let opcodes = vec![ -// Opcode::Brillig(brillig_data), -// Opcode::AssertZero(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], -// q_c: fe_0, -// }), -// // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), -// Opcode::AssertZero(Expression { -// mul_terms: vec![(fe_1, w_z, w_z_inverse)], -// linear_combinations: vec![], -// q_c: -fe_1, -// }), -// Opcode::AssertZero(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], -// q_c: fe_0, -// }), -// ]; - -// let witness_assignments = BTreeMap::from([ -// (Witness(1), FieldElement::from(2u128)), -// (Witness(2), FieldElement::from(3u128)), -// (Witness(8), FieldElement::from(5u128)), -// (Witness(9), FieldElement::from(10u128)), -// ]) -// .into(); -// let unconstrained_functions = vec![]; -// let mut acvm = -// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - -// // use the partial witness generation solver with our acir program -// let solver_status = acvm.solve(); -// assert!( -// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), -// "should require foreign call response" -// ); -// assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); - -// let foreign_call_wait_info: &ForeignCallWaitInfo = -// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); -// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - -// let x_plus_y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); - -// // Resolve Brillig foreign call -// acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); - -// // After filling data request, continue solving -// let solver_status = acvm.solve(); -// assert!( -// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), -// "should require foreign call response" -// ); -// assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); - -// let foreign_call_wait_info = -// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); -// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - -// let i_plus_j_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); -// assert_ne!(x_plus_y_inverse, i_plus_j_inverse); - -// // Alter Brillig oracle opcode -// acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); - -// // After filling data request, continue solving -// let solver_status = acvm.solve(); -// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - -// // ACVM should be able to be finalized in `Solved` state. -// acvm.finalize(); -// } - -// #[test] -// fn oracle_dependent_execution() { -// // This test ensures that we properly track the list of opcodes which still need to be resolved -// // across any brillig foreign calls we may have to perform. -// // -// // Opcodes below describe the following: -// // fn main(x : Field, y : pub Field) { -// // assert(x == y); -// // let x_inv = Oracle("inverse", x); -// // let y_inv = Oracle("inverse", y); -// // -// // assert(x_inv == y_inv); -// // } -// // Also performs an unrelated equality check -// // just for the sake of testing multiple brillig opcodes. -// let fe_0 = FieldElement::zero(); -// let fe_1 = FieldElement::one(); -// let w_x = Witness(1); -// let w_y = Witness(2); -// let w_x_inv = Witness(3); -// let w_y_inv = Witness(4); - -// let brillig_data = Brillig { -// inputs: vec![ -// BrilligInputs::Single(w_x.into()), // Input Register 0 -// BrilligInputs::Single(Expression::default()), // Input Register 1 -// BrilligInputs::Single(w_y.into()), // Input Register 2, -// ], -// outputs: vec![ -// BrilligOutputs::Simple(w_x), // Output Register 0 - from input -// BrilligOutputs::Simple(w_y_inv), // Output Register 1 -// BrilligOutputs::Simple(w_y), // Output Register 2 - from input -// BrilligOutputs::Simple(w_y_inv), // Output Register 3 -// ], -// bytecode: vec![ -// BrilligOpcode::CalldataCopy { -// destination_address: MemoryAddress(0), -// size: 3, -// offset: 0, -// }, -// // Oracles are named 'foreign calls' in brillig -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], -// input_value_types: vec![HeapValueType::field()], -// }, -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], -// input_value_types: vec![HeapValueType::field()], -// }, -// BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 }, -// ], -// predicate: None, -// }; - -// // This equality check can be executed immediately before resolving any foreign calls. -// let equality_check = Expression { -// mul_terms: vec![], -// linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], -// q_c: fe_0, -// }; - -// // This equality check relies on the outputs of the Brillig call. -// // It then cannot be solved until the foreign calls are resolved. -// let inverse_equality_check = Expression { -// mul_terms: vec![], -// linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], -// q_c: fe_0, -// }; - -// let opcodes = vec![ -// Opcode::AssertZero(equality_check), -// Opcode::Brillig(brillig_data), -// Opcode::AssertZero(inverse_equality_check), -// ]; - -// let witness_assignments = -// BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); -// let unconstrained_functions = vec![]; -// let mut acvm = -// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); - -// // use the partial witness generation solver with our acir program -// let solver_status = acvm.solve(); -// assert!( -// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), -// "should require foreign call response" -// ); -// assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); - -// let foreign_call_wait_info: &ForeignCallWaitInfo = -// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); -// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - -// // Resolve Brillig foreign call -// let x_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); -// acvm.resolve_pending_foreign_call(x_inverse.into()); - -// // After filling data request, continue solving -// let solver_status = acvm.solve(); -// assert!( -// matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), -// "should require foreign call response" -// ); -// assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); - -// let foreign_call_wait_info: &ForeignCallWaitInfo = -// acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); -// assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - -// // Resolve Brillig foreign call -// let y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); -// acvm.resolve_pending_foreign_call(y_inverse.into()); - -// // We've resolved all the brillig foreign calls so we should be able to complete execution now. - -// // After filling data request, continue solving -// let solver_status = acvm.solve(); -// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - -// // ACVM should be able to be finalized in `Solved` state. -// acvm.finalize(); -// } - -// #[test] -// fn brillig_oracle_predicate() { -// let fe_0 = FieldElement::zero(); -// let fe_1 = FieldElement::one(); -// let w_x = Witness(1); -// let w_y = Witness(2); -// let w_oracle = Witness(3); -// let w_x_plus_y = Witness(4); -// let w_equal_res = Witness(5); -// let w_lt_res = Witness(6); - -// let equal_opcode = BrilligOpcode::BinaryFieldOp { -// op: BinaryFieldOp::Equals, -// lhs: MemoryAddress::from(0), -// rhs: MemoryAddress::from(1), -// destination: MemoryAddress::from(2), -// }; - -// let brillig_opcode = Opcode::Brillig(Brillig { -// inputs: vec![ -// BrilligInputs::Single(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], -// q_c: fe_0, -// }), -// BrilligInputs::Single(Expression::default()), -// ], -// outputs: vec![ -// BrilligOutputs::Simple(w_x_plus_y), -// BrilligOutputs::Simple(w_oracle), -// BrilligOutputs::Simple(w_equal_res), -// BrilligOutputs::Simple(w_lt_res), -// ], -// bytecode: vec![ -// BrilligOpcode::CalldataCopy { -// destination_address: MemoryAddress(0), -// size: 2, -// offset: 0, -// }, -// equal_opcode, -// // Oracles are named 'foreign calls' in brillig -// BrilligOpcode::ForeignCall { -// function: "invert".into(), -// destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], -// destination_value_types: vec![HeapValueType::field()], -// inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], -// input_value_types: vec![HeapValueType::field()], -// }, -// ], -// predicate: Some(Expression::default()), -// }); - -// let opcodes = vec![brillig_opcode]; - -// let witness_assignments = BTreeMap::from([ -// (Witness(1), FieldElement::from(2u128)), -// (Witness(2), FieldElement::from(3u128)), -// ]) -// .into(); -// let unconstrained_functions = vec![]; -// let mut acvm = -// ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); -// let solver_status = acvm.solve(); -// assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); - -// // ACVM should be able to be finalized in `Solved` state. -// acvm.finalize(); -// } - -// #[test] -// fn unsatisfied_opcode_resolved() { -// let a = Witness(0); -// let b = Witness(1); -// let c = Witness(2); -// let d = Witness(3); - -// // a = b + c + d; -// let opcode_a = Expression { -// mul_terms: vec![], -// linear_combinations: vec![ -// (FieldElement::one(), a), -// (-FieldElement::one(), b), -// (-FieldElement::one(), c), -// (-FieldElement::one(), d), -// ], -// q_c: FieldElement::zero(), -// }; - -// let mut values = WitnessMap::new(); -// values.insert(a, FieldElement::from(4_i128)); -// values.insert(b, FieldElement::from(2_i128)); -// values.insert(c, FieldElement::from(1_i128)); -// values.insert(d, FieldElement::from(2_i128)); - -// let opcodes = vec![Opcode::AssertZero(opcode_a)]; -// let unconstrained_functions = vec![]; -// let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); -// let solver_status = acvm.solve(); -// assert_eq!( -// solver_status, -// ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { -// opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), -// }), -// "The first opcode is not satisfiable, expected an error indicating this" -// ); -// } - -// #[test] -// fn unsatisfied_opcode_resolved_brillig() { -// let a = Witness(0); -// let b = Witness(1); -// let c = Witness(2); -// let d = Witness(3); - -// let fe_1 = FieldElement::one(); -// let fe_0 = FieldElement::zero(); -// let w_x = Witness(4); -// let w_y = Witness(5); -// let w_result = Witness(6); - -// let calldata_copy_opcode = -// BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; - -// let equal_opcode = BrilligOpcode::BinaryFieldOp { -// op: BinaryFieldOp::Equals, -// lhs: MemoryAddress::from(0), -// rhs: MemoryAddress::from(1), -// destination: MemoryAddress::from(2), -// }; -// // Jump pass the trap if the values are equal, else -// // jump to the trap -// let location_of_stop = 3; - -// let jmp_if_opcode = -// BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; - -// let trap_opcode = BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; -// let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; - -// let brillig_opcode = Opcode::Brillig(Brillig { -// inputs: vec![ -// BrilligInputs::Single(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_x)], -// q_c: fe_0, -// }), -// BrilligInputs::Single(Expression { -// mul_terms: vec![], -// linear_combinations: vec![(fe_1, w_y)], -// q_c: fe_0, -// }), -// ], -// outputs: vec![BrilligOutputs::Simple(w_result)], -// bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], -// predicate: Some(Expression::one()), -// }); - -// let opcode_a = Expression { -// mul_terms: vec![], -// linear_combinations: vec![ -// (FieldElement::one(), a), -// (-FieldElement::one(), b), -// (-FieldElement::one(), c), -// (-FieldElement::one(), d), -// ], -// q_c: FieldElement::zero(), -// }; - -// let mut values = WitnessMap::new(); -// values.insert(a, FieldElement::from(4_i128)); -// values.insert(b, FieldElement::from(2_i128)); -// values.insert(c, FieldElement::from(1_i128)); -// values.insert(d, FieldElement::from(2_i128)); -// values.insert(w_x, FieldElement::from(0_i128)); -// values.insert(w_y, FieldElement::from(1_i128)); -// values.insert(w_result, FieldElement::from(0_i128)); - -// let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; -// let unconstrained_functions = vec![]; -// let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); -// let solver_status = acvm.solve(); -// assert_eq!( -// solver_status, -// ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { -// message: None, -// call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] -// }), -// "The first opcode is not satisfiable, expected an error indicating this" -// ); -// } - -// #[test] -// fn memory_operations() { -// let initial_witness = WitnessMap::from(BTreeMap::from_iter([ -// (Witness(1), FieldElement::from(1u128)), -// (Witness(2), FieldElement::from(2u128)), -// (Witness(3), FieldElement::from(3u128)), -// (Witness(4), FieldElement::from(4u128)), -// (Witness(5), FieldElement::from(5u128)), -// (Witness(6), FieldElement::from(4u128)), -// ])); - -// let block_id = BlockId(0); - -// let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() }; - -// let read_op = Opcode::MemoryOp { -// block_id, -// op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)), -// predicate: None, -// }; - -// let expression = Opcode::AssertZero(Expression { -// mul_terms: Vec::new(), -// linear_combinations: vec![ -// (FieldElement::one(), Witness(7)), -// (-FieldElement::one(), Witness(8)), -// ], -// q_c: FieldElement::one(), -// }); - -// let opcodes = vec![init, read_op, expression]; -// let unconstrained_functions = vec![]; -// let mut acvm = -// ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions); -// let solver_status = acvm.solve(); -// assert_eq!(solver_status, ACVMStatus::Solved); -// let witness_map = acvm.finalize(); - -// assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); -// } +#[test] +fn inversion_brillig_oracle_equivalence() { + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // let z = x + y; + // assert( 1/z == Oracle("inverse", x + y) ); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_z = Witness(4); + let w_z_inverse = Witness(5); + let w_x_plus_y = Witness(6); + let w_equal_res = Witness(7); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }; + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + ], + // This tells the BrilligSolver which witnesses its output values correspond to + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_equal_res), // Output Register 2 + ], + bytecode: vec![ + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size: 2, + offset: 0, + }, + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 3 }, + ], + predicate: None, + }; + + let opcodes = vec![ + Opcode::Brillig(brillig_data), + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), + Opcode::AssertZero(Expression { + mul_terms: vec![(fe_1, w_z, w_z_inverse)], + linear_combinations: vec![], + q_c: -fe_1, + }), + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], + q_c: fe_0, + }), + ]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + ]) + .into(); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + Default::default(), + ); + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // As caller of VM, need to resolve foreign calls + let foreign_call_result = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + // Alter Brillig oracle opcode with foreign call resolution + acvm.resolve_pending_foreign_call(foreign_call_result.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn double_inversion_brillig_oracle() { + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // let z = x + y; + // let ij = i + j; + // assert( 1/z == Oracle("inverse", x + y) ); + // assert( 1/ij == Oracle("inverse", i + j) ); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_z = Witness(4); + let w_z_inverse = Witness(5); + let w_x_plus_y = Witness(6); + let w_equal_res = Witness(7); + let w_i = Witness(8); + let w_j = Witness(9); + let w_ij_oracle = Witness(10); + let w_i_plus_j = Witness(11); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(4), + }; + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(Expression { + // Input Register 2 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], + q_c: fe_0, + }), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input + BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 + BrilligOutputs::Simple(w_equal_res), // Output Register 4 + ], + bytecode: vec![ + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size: 3, + offset: 0, + }, + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 5 }, + ], + predicate: None, + }; + + let opcodes = vec![ + Opcode::Brillig(brillig_data), + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), + Opcode::AssertZero(Expression { + mul_terms: vec![(fe_1, w_z, w_z_inverse)], + linear_combinations: vec![], + q_c: -fe_1, + }), + Opcode::AssertZero(Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], + q_c: fe_0, + }), + ]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + (Witness(8), FieldElement::from(5u128)), + (Witness(9), FieldElement::from(10u128)), + ]) + .into(); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + Default::default(), + ); + + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + let x_plus_y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + + // Resolve Brillig foreign call + acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + + let foreign_call_wait_info = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + let i_plus_j_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + assert_ne!(x_plus_y_inverse, i_plus_j_inverse); + + // Alter Brillig oracle opcode + acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn oracle_dependent_execution() { + // This test ensures that we properly track the list of opcodes which still need to be resolved + // across any brillig foreign calls we may have to perform. + // + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // assert(x == y); + // let x_inv = Oracle("inverse", x); + // let y_inv = Oracle("inverse", y); + // + // assert(x_inv == y_inv); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_x_inv = Witness(3); + let w_y_inv = Witness(4); + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(w_x.into()), // Input Register 0 + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(w_y.into()), // Input Register 2, + ], + outputs: vec![ + BrilligOutputs::Simple(w_x), // Output Register 0 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 1 + BrilligOutputs::Simple(w_y), // Output Register 2 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 3 + ], + bytecode: vec![ + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size: 3, + offset: 0, + }, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(3))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(2))], + input_value_types: vec![HeapValueType::field()], + }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 4 }, + ], + predicate: None, + }; + + // This equality check can be executed immediately before resolving any foreign calls. + let equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }; + + // This equality check relies on the outputs of the Brillig call. + // It then cannot be solved until the foreign calls are resolved. + let inverse_equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], + q_c: fe_0, + }; + + let opcodes = vec![ + Opcode::AssertZero(equality_check), + Opcode::Brillig(brillig_data), + Opcode::AssertZero(inverse_equality_check), + ]; + + let witness_assignments = + BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + Default::default(), + ); + + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // Resolve Brillig foreign call + let x_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + acvm.resolve_pending_foreign_call(x_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // Resolve Brillig foreign call + let y_inverse = foreign_call_wait_info.inputs[0].unwrap_field().inverse(); + acvm.resolve_pending_foreign_call(y_inverse.into()); + + // We've resolved all the brillig foreign calls so we should be able to complete execution now. + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn brillig_oracle_predicate() { + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_x_plus_y = Witness(4); + let w_equal_res = Witness(5); + let w_lt_res = Witness(6); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }; + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), + BrilligOutputs::Simple(w_oracle), + BrilligOutputs::Simple(w_equal_res), + BrilligOutputs::Simple(w_lt_res), + ], + bytecode: vec![ + BrilligOpcode::CalldataCopy { + destination_address: MemoryAddress(0), + size: 2, + offset: 0, + }, + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(1))], + destination_value_types: vec![HeapValueType::field()], + inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::from(0))], + input_value_types: vec![HeapValueType::field()], + }, + ], + predicate: Some(Expression::default()), + }); + + let opcodes = vec![brillig_opcode]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + ]) + .into(); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + witness_assignments, + &unconstrained_functions, + Default::default(), + ); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn unsatisfied_opcode_resolved() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + // a = b + c + d; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut values = WitnessMap::new(); + values.insert(a, FieldElement::from(4_i128)); + values.insert(b, FieldElement::from(2_i128)); + values.insert(c, FieldElement::from(1_i128)); + values.insert(d, FieldElement::from(2_i128)); + + let opcodes = vec![Opcode::AssertZero(opcode_a)]; + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + values, + &unconstrained_functions, + Default::default(), + ); + let solver_status = acvm.solve(); + assert_eq!( + solver_status, + ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), + payload: None + }), + "The first opcode is not satisfiable, expected an error indicating this" + ); +} + +#[test] +fn unsatisfied_opcode_resolved_brillig() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + let fe_1 = FieldElement::one(); + let fe_0 = FieldElement::zero(); + let w_x = Witness(4); + let w_y = Witness(5); + let w_result = Witness(6); + + let calldata_copy_opcode = + BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), size: 2, offset: 0 }; + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: MemoryAddress::from(0), + rhs: MemoryAddress::from(1), + destination: MemoryAddress::from(2), + }; + // Jump pass the trap if the values are equal, else + // jump to the trap + let location_of_stop = 3; + + let jmp_if_opcode = + BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; + + let trap_opcode = BrilligOpcode::Trap { revert_data: HeapArray::default() }; + let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_y)], + q_c: fe_0, + }), + ], + outputs: vec![BrilligOutputs::Simple(w_result)], + bytecode: vec![calldata_copy_opcode, equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], + predicate: Some(Expression::one()), + }); + + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut values = WitnessMap::new(); + values.insert(a, FieldElement::from(4_i128)); + values.insert(b, FieldElement::from(2_i128)); + values.insert(c, FieldElement::from(1_i128)); + values.insert(d, FieldElement::from(2_i128)); + values.insert(w_x, FieldElement::from(0_i128)); + values.insert(w_y, FieldElement::from(1_i128)); + values.insert(w_result, FieldElement::from(0_i128)); + + let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + values, + &unconstrained_functions, + Default::default(), + ); + let solver_status = acvm.solve(); + assert_eq!( + solver_status, + ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { + payload: None, + call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] + }), + "The first opcode is not satisfiable, expected an error indicating this" + ); +} + +#[test] +fn memory_operations() { + let initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(2u128)), + (Witness(3), FieldElement::from(3u128)), + (Witness(4), FieldElement::from(4u128)), + (Witness(5), FieldElement::from(5u128)), + (Witness(6), FieldElement::from(4u128)), + ])); + + let block_id = BlockId(0); + + let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() }; + + let read_op = Opcode::MemoryOp { + block_id, + op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)), + predicate: None, + }; + + let expression = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(7)), + (-FieldElement::one(), Witness(8)), + ], + q_c: FieldElement::one(), + }); + + let opcodes = vec![init, read_op, expression]; + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new( + &StubbedBlackBoxSolver, + &opcodes, + initial_witness, + &unconstrained_functions, + Default::default(), + ); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved); + let witness_map = acvm.finalize(); + + assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index a2db801811cd..5c61ff04b39f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -2525,407 +2525,407 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { !types.iter().any(|typ| typ.contains_an_array()) } -// #[cfg(test)] -// mod test { -// use acvm::{ -// acir::{circuit::Opcode, native_types::Witness}, -// FieldElement, -// }; - -// use crate::{ -// brillig::Brillig, -// ssa::{ -// function_builder::FunctionBuilder, -// ir::{ -// function::{FunctionId, InlineType}, -// instruction::BinaryOp, -// map::Id, -// types::Type, -// }, -// }, -// }; - -// fn build_basic_foo_with_return( -// builder: &mut FunctionBuilder, -// foo_id: FunctionId, -// is_brillig_func: bool, -// ) { -// // fn foo f1 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == u1 0 -// // return v0 -// // } -// if is_brillig_func { -// builder.new_brillig_function("foo".into(), foo_id); -// } else { -// builder.new_function("foo".into(), foo_id, InlineType::Fold); -// } -// let foo_v0 = builder.add_parameter(Type::field()); -// let foo_v1 = builder.add_parameter(Type::field()); - -// let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); -// let zero = builder.numeric_constant(0u128, Type::unsigned(1)); -// builder.insert_constrain(foo_equality_check, zero, None); -// builder.terminate_with_return(vec![foo_v0]); -// } - -// #[test] -// fn basic_call_with_outputs_assert() { -// // acir(inline) fn main f0 { -// // b0(v0: Field, v1: Field): -// // v2 = call f1(v0, v1) -// // v3 = call f1(v0, v1) -// // constrain v2 == v3 -// // return -// // } -// // acir(fold) fn foo f1 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == u1 0 -// // return v0 -// // } -// let foo_id = Id::test_new(0); -// let mut builder = FunctionBuilder::new("main".into(), foo_id); -// let main_v0 = builder.add_parameter(Type::field()); -// let main_v1 = builder.add_parameter(Type::field()); - -// let foo_id = Id::test_new(1); -// let foo = builder.import_function(foo_id); -// let main_call1_results = -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// let main_call2_results = -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); -// builder.terminate_with_return(vec![]); - -// build_basic_foo_with_return(&mut builder, foo_id, false); - -// let ssa = builder.finish(); - -// let (acir_functions, _) = ssa -// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) -// .expect("Should compile manually written SSA into ACIR"); -// // Expected result: -// // main f0 -// // GeneratedAcir { -// // ... -// // opcodes: [ -// // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(2)], -// // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(3)], -// // EXPR [ (1, _2) (-1, _3) 0 ], -// // ], -// // return_witnesses: [], -// // input_witnesses: [ -// // Witness( -// // 0, -// // ), -// // Witness( -// // 1, -// // ), -// // ], -// // ... -// // } -// // foo f1 -// // GeneratedAcir { -// // ... -// // opcodes: [ -// // Same as opcodes as the expected result of `basic_call_codegen` -// // ], -// // return_witnesses: [ -// // Witness( -// // 0, -// // ), -// // ], -// // input_witnesses: [ -// // Witness( -// // 0, -// // ), -// // Witness( -// // 1, -// // ), -// // ], -// // ... -// // }, - -// let main_acir = &acir_functions[0]; -// let main_opcodes = main_acir.opcodes(); -// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); - -// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); -// check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); - -// if let Opcode::AssertZero(expr) = &main_opcodes[2] { -// assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); -// assert_eq!(expr.linear_combinations[0].1, Witness(2)); - -// assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); -// assert_eq!(expr.linear_combinations[1].1, Witness(3)); -// assert_eq!(expr.q_c, FieldElement::from(0u128)); -// } -// } - -// #[test] -// fn call_output_as_next_call_input() { -// // acir(inline) fn main f0 { -// // b0(v0: Field, v1: Field): -// // v3 = call f1(v0, v1) -// // v4 = call f1(v3, v1) -// // constrain v3 == v4 -// // return -// // } -// // acir(fold) fn foo f1 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == u1 0 -// // return v0 -// // } -// let foo_id = Id::test_new(0); -// let mut builder = FunctionBuilder::new("main".into(), foo_id); -// let main_v0 = builder.add_parameter(Type::field()); -// let main_v1 = builder.add_parameter(Type::field()); - -// let foo_id = Id::test_new(1); -// let foo = builder.import_function(foo_id); -// let main_call1_results = -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// let main_call2_results = builder -// .insert_call(foo, vec![main_call1_results[0], main_v1], vec![Type::field()]) -// .to_vec(); -// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); -// builder.terminate_with_return(vec![]); - -// build_basic_foo_with_return(&mut builder, foo_id, false); - -// let ssa = builder.finish(); - -// let (acir_functions, _) = ssa -// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) -// .expect("Should compile manually written SSA into ACIR"); -// // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` -// // opcodes will be different. The changes can discerned from the checks below. - -// let main_acir = &acir_functions[0]; -// let main_opcodes = main_acir.opcodes(); -// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - -// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); -// // The output of the first call should be the input of the second call -// check_call_opcode(&main_opcodes[1], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); -// } - -// #[test] -// fn basic_nested_call() { -// // SSA for the following Noir program: -// // fn main(x: Field, y: pub Field) { -// // let z = func_with_nested_foo_call(x, y); -// // let z2 = func_with_nested_foo_call(x, y); -// // assert(z == z2); -// // } -// // #[fold] -// // fn func_with_nested_foo_call(x: Field, y: Field) -> Field { -// // nested_call(x + 2, y) -// // } -// // #[fold] -// // fn foo(x: Field, y: Field) -> Field { -// // assert(x != y); -// // x -// // } -// // -// // SSA: -// // acir(inline) fn main f0 { -// // b0(v0: Field, v1: Field): -// // v3 = call f1(v0, v1) -// // v4 = call f1(v0, v1) -// // constrain v3 == v4 -// // return -// // } -// // acir(fold) fn func_with_nested_foo_call f1 { -// // b0(v0: Field, v1: Field): -// // v3 = add v0, Field 2 -// // v5 = call f2(v3, v1) -// // return v5 -// // } -// // acir(fold) fn foo f2 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == Field 0 -// // return v0 -// // } -// let foo_id = Id::test_new(0); -// let mut builder = FunctionBuilder::new("main".into(), foo_id); -// let main_v0 = builder.add_parameter(Type::field()); -// let main_v1 = builder.add_parameter(Type::field()); - -// let func_with_nested_foo_call_id = Id::test_new(1); -// let func_with_nested_foo_call = builder.import_function(func_with_nested_foo_call_id); -// let main_call1_results = builder -// .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) -// .to_vec(); -// let main_call2_results = builder -// .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) -// .to_vec(); -// builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); -// builder.terminate_with_return(vec![]); - -// builder.new_function( -// "func_with_nested_foo_call".into(), -// func_with_nested_foo_call_id, -// InlineType::Fold, -// ); -// let func_with_nested_call_v0 = builder.add_parameter(Type::field()); -// let func_with_nested_call_v1 = builder.add_parameter(Type::field()); - -// let two = builder.field_constant(2u128); -// let v0_plus_two = builder.insert_binary(func_with_nested_call_v0, BinaryOp::Add, two); - -// let foo_id = Id::test_new(2); -// let foo_call = builder.import_function(foo_id); -// let foo_call = builder -// .insert_call(foo_call, vec![v0_plus_two, func_with_nested_call_v1], vec![Type::field()]) -// .to_vec(); -// builder.terminate_with_return(vec![foo_call[0]]); - -// build_basic_foo_with_return(&mut builder, foo_id, false); - -// let ssa = builder.finish(); - -// let (acir_functions, _) = ssa -// .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) -// .expect("Should compile manually written SSA into ACIR"); - -// assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); - -// let main_acir = &acir_functions[0]; -// let main_opcodes = main_acir.opcodes(); -// assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); - -// // Both of these should call func_with_nested_foo_call f1 -// check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); -// // The output of the first call should be the input of the second call -// check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); - -// let func_with_nested_call_acir = &acir_functions[1]; -// let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); -// assert_eq!( -// func_with_nested_call_opcodes.len(), -// 2, -// "Should have an expression and a call to a nested `foo`" -// ); -// // Should call foo f2 -// check_call_opcode( -// &func_with_nested_call_opcodes[1], -// 2, -// vec![Witness(2), Witness(1)], -// vec![Witness(3)], -// ); -// } - -// fn check_call_opcode( -// opcode: &Opcode, -// expected_id: u32, -// expected_inputs: Vec, -// expected_outputs: Vec, -// ) { -// match opcode { -// Opcode::Call { id, inputs, outputs, .. } => { -// assert_eq!( -// *id, expected_id, -// "Main was expected to call {expected_id} but got {}", -// *id -// ); -// for (expected_input, input) in expected_inputs.iter().zip(inputs) { -// assert_eq!( -// expected_input, input, -// "Expected witness {expected_input:?} but got {input:?}" -// ); -// } -// for (expected_output, output) in expected_outputs.iter().zip(outputs) { -// assert_eq!( -// expected_output, output, -// "Expected witness {expected_output:?} but got {output:?}" -// ); -// } -// } -// _ => panic!("Expected only Call opcode"), -// } -// } - -// // Test that given multiple calls to the same brillig function we generate only one bytecode -// // and the appropriate Brillig call opcodes are generated -// #[test] -// fn multiple_brillig_calls_one_bytecode() { -// // acir(inline) fn main f0 { -// // b0(v0: Field, v1: Field): -// // v3 = call f1(v0, v1) -// // v4 = call f1(v0, v1) -// // v5 = call f1(v0, v1) -// // v6 = call f1(v0, v1) -// // return -// // } -// // brillig fn foo f1 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == u1 0 -// // return v0 -// // } -// // brillig fn foo f2 { -// // b0(v0: Field, v1: Field): -// // v2 = eq v0, v1 -// // constrain v2 == u1 0 -// // return v0 -// // } -// let foo_id = Id::test_new(0); -// let mut builder = FunctionBuilder::new("main".into(), foo_id); -// let main_v0 = builder.add_parameter(Type::field()); -// let main_v1 = builder.add_parameter(Type::field()); - -// let foo_id = Id::test_new(1); -// let foo = builder.import_function(foo_id); -// let bar_id = Id::test_new(2); -// let bar = builder.import_function(bar_id); - -// // Insert multiple calls to the same Brillig function -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions -// builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); -// builder.terminate_with_return(vec![]); - -// build_basic_foo_with_return(&mut builder, foo_id, true); -// build_basic_foo_with_return(&mut builder, bar_id, true); - -// let ssa = builder.finish(); -// let brillig = ssa.to_brillig(false); -// println!("{}", ssa); - -// let (acir_functions, brillig_functions) = ssa -// .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) -// .expect("Should compile manually written SSA into ACIR"); - -// assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); -// assert_eq!( -// brillig_functions.len(), -// 2, -// "Should only have generated a single Brillig function" -// ); - -// let main_acir = &acir_functions[0]; -// let main_opcodes = main_acir.opcodes(); -// assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); - -// // We should only have `BrilligCall` opcodes in `main` -// for (i, opcode) in main_opcodes.iter().enumerate() { -// match opcode { -// Opcode::BrilligCall { id, .. } => { -// let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; -// assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); -// } -// _ => panic!("Expected only Brillig call opcode"), -// } -// } -// } -// } +#[cfg(test)] +mod test { + use acvm::{ + acir::{circuit::Opcode, native_types::Witness}, + FieldElement, + }; + + use crate::{ + brillig::Brillig, + ssa::{ + function_builder::FunctionBuilder, + ir::{ + function::{FunctionId, InlineType}, + instruction::BinaryOp, + map::Id, + types::Type, + }, + }, + }; + + fn build_basic_foo_with_return( + builder: &mut FunctionBuilder, + foo_id: FunctionId, + is_brillig_func: bool, + ) { + // fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + if is_brillig_func { + builder.new_brillig_function("foo".into(), foo_id); + } else { + builder.new_function("foo".into(), foo_id, InlineType::Fold); + } + let foo_v0 = builder.add_parameter(Type::field()); + let foo_v1 = builder.add_parameter(Type::field()); + + let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); + let zero = builder.numeric_constant(0u128, Type::unsigned(1)); + builder.insert_constrain(foo_equality_check, zero, None); + builder.terminate_with_return(vec![foo_v0]); + } + + #[test] + fn basic_call_with_outputs_assert() { + // acir(inline) fn main f0 { + // b0(v0: Field, v1: Field): + // v2 = call f1(v0, v1) + // v3 = call f1(v0, v1) + // constrain v2 == v3 + // return + // } + // acir(fold) fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::field()); + let main_v1 = builder.add_parameter(Type::field()); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let main_call1_results = + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + let main_call2_results = + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, false); + + let ssa = builder.finish(); + + let (acir_functions, _, _) = ssa + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + // Expected result: + // main f0 + // GeneratedAcir { + // ... + // opcodes: [ + // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(2)], + // CALL func 1: inputs: [Witness(0), Witness(1)], outputs: [Witness(3)], + // EXPR [ (1, _2) (-1, _3) 0 ], + // ], + // return_witnesses: [], + // input_witnesses: [ + // Witness( + // 0, + // ), + // Witness( + // 1, + // ), + // ], + // ... + // } + // foo f1 + // GeneratedAcir { + // ... + // opcodes: [ + // Same as opcodes as the expected result of `basic_call_codegen` + // ], + // return_witnesses: [ + // Witness( + // 0, + // ), + // ], + // input_witnesses: [ + // Witness( + // 0, + // ), + // Witness( + // 1, + // ), + // ], + // ... + // }, + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo`"); + + check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + + if let Opcode::AssertZero(expr) = &main_opcodes[2] { + assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); + assert_eq!(expr.linear_combinations[0].1, Witness(2)); + + assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); + assert_eq!(expr.linear_combinations[1].1, Witness(3)); + assert_eq!(expr.q_c, FieldElement::from(0u128)); + } + } + + #[test] + fn call_output_as_next_call_input() { + // acir(inline) fn main f0 { + // b0(v0: Field, v1: Field): + // v3 = call f1(v0, v1) + // v4 = call f1(v3, v1) + // constrain v3 == v4 + // return + // } + // acir(fold) fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::field()); + let main_v1 = builder.add_parameter(Type::field()); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let main_call1_results = + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + let main_call2_results = builder + .insert_call(foo, vec![main_call1_results[0], main_v1], vec![Type::field()]) + .to_vec(); + builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, false); + + let ssa = builder.finish(); + + let (acir_functions, _, _) = ssa + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` + // opcodes will be different. The changes can discerned from the checks below. + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); + + check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + // The output of the first call should be the input of the second call + check_call_opcode(&main_opcodes[1], 1, vec![Witness(2), Witness(1)], vec![Witness(3)]); + } + + #[test] + fn basic_nested_call() { + // SSA for the following Noir program: + // fn main(x: Field, y: pub Field) { + // let z = func_with_nested_foo_call(x, y); + // let z2 = func_with_nested_foo_call(x, y); + // assert(z == z2); + // } + // #[fold] + // fn func_with_nested_foo_call(x: Field, y: Field) -> Field { + // nested_call(x + 2, y) + // } + // #[fold] + // fn foo(x: Field, y: Field) -> Field { + // assert(x != y); + // x + // } + // + // SSA: + // acir(inline) fn main f0 { + // b0(v0: Field, v1: Field): + // v3 = call f1(v0, v1) + // v4 = call f1(v0, v1) + // constrain v3 == v4 + // return + // } + // acir(fold) fn func_with_nested_foo_call f1 { + // b0(v0: Field, v1: Field): + // v3 = add v0, Field 2 + // v5 = call f2(v3, v1) + // return v5 + // } + // acir(fold) fn foo f2 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == Field 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::field()); + let main_v1 = builder.add_parameter(Type::field()); + + let func_with_nested_foo_call_id = Id::test_new(1); + let func_with_nested_foo_call = builder.import_function(func_with_nested_foo_call_id); + let main_call1_results = builder + .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) + .to_vec(); + let main_call2_results = builder + .insert_call(func_with_nested_foo_call, vec![main_v0, main_v1], vec![Type::field()]) + .to_vec(); + builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); + builder.terminate_with_return(vec![]); + + builder.new_function( + "func_with_nested_foo_call".into(), + func_with_nested_foo_call_id, + InlineType::Fold, + ); + let func_with_nested_call_v0 = builder.add_parameter(Type::field()); + let func_with_nested_call_v1 = builder.add_parameter(Type::field()); + + let two = builder.field_constant(2u128); + let v0_plus_two = builder.insert_binary(func_with_nested_call_v0, BinaryOp::Add, two); + + let foo_id = Id::test_new(2); + let foo_call = builder.import_function(foo_id); + let foo_call = builder + .insert_call(foo_call, vec![v0_plus_two, func_with_nested_call_v1], vec![Type::field()]) + .to_vec(); + builder.terminate_with_return(vec![foo_call[0]]); + + build_basic_foo_with_return(&mut builder, foo_id, false); + + let ssa = builder.finish(); + + let (acir_functions, _, _) = ssa + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + assert_eq!(main_opcodes.len(), 3, "Should have two calls to `foo` and an assert"); + + // Both of these should call func_with_nested_foo_call f1 + check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); + // The output of the first call should be the input of the second call + check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); + + let func_with_nested_call_acir = &acir_functions[1]; + let func_with_nested_call_opcodes = func_with_nested_call_acir.opcodes(); + assert_eq!( + func_with_nested_call_opcodes.len(), + 2, + "Should have an expression and a call to a nested `foo`" + ); + // Should call foo f2 + check_call_opcode( + &func_with_nested_call_opcodes[1], + 2, + vec![Witness(2), Witness(1)], + vec![Witness(3)], + ); + } + + fn check_call_opcode( + opcode: &Opcode, + expected_id: u32, + expected_inputs: Vec, + expected_outputs: Vec, + ) { + match opcode { + Opcode::Call { id, inputs, outputs, .. } => { + assert_eq!( + *id, expected_id, + "Main was expected to call {expected_id} but got {}", + *id + ); + for (expected_input, input) in expected_inputs.iter().zip(inputs) { + assert_eq!( + expected_input, input, + "Expected witness {expected_input:?} but got {input:?}" + ); + } + for (expected_output, output) in expected_outputs.iter().zip(outputs) { + assert_eq!( + expected_output, output, + "Expected witness {expected_output:?} but got {output:?}" + ); + } + } + _ => panic!("Expected only Call opcode"), + } + } + + // Test that given multiple calls to the same brillig function we generate only one bytecode + // and the appropriate Brillig call opcodes are generated + #[test] + fn multiple_brillig_calls_one_bytecode() { + // acir(inline) fn main f0 { + // b0(v0: Field, v1: Field): + // v3 = call f1(v0, v1) + // v4 = call f1(v0, v1) + // v5 = call f1(v0, v1) + // v6 = call f1(v0, v1) + // return + // } + // brillig fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // brillig fn foo f2 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::field()); + let main_v1 = builder.add_parameter(Type::field()); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let bar_id = Id::test_new(2); + let bar = builder.import_function(bar_id); + + // Insert multiple calls to the same Brillig function + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, true); + build_basic_foo_with_return(&mut builder, bar_id, true); + + let ssa = builder.finish(); + let brillig = ssa.to_brillig(false); + println!("{}", ssa); + + let (acir_functions, brillig_functions, _) = ssa + .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); + assert_eq!( + brillig_functions.len(), + 2, + "Should only have generated a single Brillig function" + ); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); + + // We should only have `BrilligCall` opcodes in `main` + for (i, opcode) in main_opcodes.iter().enumerate() { + match opcode { + Opcode::BrilligCall { id, .. } => { + let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; + assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); + } + _ => panic!("Expected only Brillig call opcode"), + } + } + } +} From 995cc348c4a6d8fa87c547575f078a51d7a665ec Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 13:59:41 +0000 Subject: [PATCH 05/25] various chores --- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 1 - .../compiler/noirc_evaluator/src/ssa.rs | 8 ++------ .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 17 ++++++++--------- noir/noir-repo/tooling/nargo/src/errors.rs | 1 - noir/noir-repo/tooling/nargo/src/ops/test.rs | 2 -- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 6080fa18924e..c5534987409c 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -498,7 +498,6 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { }; let result = solver.solve().map_err(|mut err| { - dbg!(&err); match &mut err { OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { // Some brillig errors have static strings as payloads, we can resolve them here diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index ae89eb4dbd2a..15eceaab1c4c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -30,8 +30,7 @@ use noirc_frontend::{ use tracing::{span, Level}; use self::{ - acir_gen::GeneratedAcir, - ir::instruction::{ErrorType, ErrorTypeId}, + acir_gen::{Artifacts, GeneratedAcir}, ssa_gen::Ssa, }; @@ -52,10 +51,7 @@ pub(crate) fn optimize_into_acir( print_brillig_trace: bool, force_brillig_output: bool, print_timings: bool, -) -> Result< - (Vec, Vec, BTreeMap), - RuntimeError, -> { +) -> Result { let abi_distinctness = program.return_distinctness; let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index cf5ce5631794..cae0a5572c3f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -226,16 +226,16 @@ impl AcirValue { } } +pub(crate) type Artifacts = + (Vec, Vec, BTreeMap); + impl Ssa { #[tracing::instrument(level = "trace", skip_all)] pub(crate) fn into_acir( self, brillig: &Brillig, abi_distinctness: Distinctness, - ) -> Result< - (Vec, Vec, BTreeMap), - RuntimeError, - > { + ) -> Result { let mut acirs = Vec::new(); // TODO: can we parallelise this? let mut shared_context = SharedContext::default(); @@ -2603,7 +2603,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _,_) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // Expected result: @@ -2699,7 +2699,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _,_) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` @@ -2790,7 +2790,7 @@ mod test { let ssa = builder.finish(); - let (acir_functions, _,_) = ssa + let (acir_functions, _, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); @@ -2900,9 +2900,8 @@ mod test { let ssa = builder.finish(); let brillig = ssa.to_brillig(false); - println!("{}", ssa); - let (acir_functions, brillig_functions,_) = ssa + let (acir_functions, brillig_functions, _) = ssa .into_acir(&brillig, noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 2ba3859eb73c..3328902d339d 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -221,7 +221,6 @@ fn extract_message_from_error( error_types: &BTreeMap, nargo_err: &NargoError, ) -> String { - dbg!(nargo_err); match nargo_err { NargoError::ExecutionError(ExecutionError::AssertionFailed( ResolvedAssertionPayload::String(message), diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index a76c6f57de56..5f26bfa0baf9 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -93,8 +93,6 @@ fn test_status_program_compile_pass( Err(err) => err, }; - dbg!(&abi.error_types); - // If we reach here, then the circuit execution failed. // // Check if the function should have passed From 54ddb149f0109314784dfdc908669c0bbd0a824f Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 14:20:49 +0000 Subject: [PATCH 06/25] chore: lock --- noir/noir-repo/yarn.lock | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index e9915882fac7..b45678f5d8bd 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,19 +221,18 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.35.1": - version: 0.35.1 - resolution: "@aztec/bb.js@npm:0.35.1" +"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": + version: 0.0.0-use.local + resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: dest/node/main.js - checksum: 8e3551f059523d9494af4721a9219e2c6e63c8ed1df447a2d0daa9f8526a794758ae708bd1d9c9b1fbfb89c56dc867d9f0b87250dbabfcde23ec02dabbb5a32a + bb.js: ./dest/node/main.js languageName: node - linkType: hard + linkType: soft "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4396,7 +4395,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.35.1 + "@aztec/bb.js": "portal:../../../../barretenberg/ts" "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3 From 70bfa2d2c11f3cb8c10d0f0e63cb63758e455f2f Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 14:43:03 +0000 Subject: [PATCH 07/25] chore: serde_as --- noir/noir-repo/Cargo.toml | 3 +-- noir/noir-repo/compiler/noirc_errors/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index db3abea156cc..18a9c369fcaa 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -109,13 +109,12 @@ chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default criterion = "0.5.0" # Note that using the "frame-pointer" feature breaks framegraphs on linux # https://github.com/tikv/pprof-rs/pull/172 -pprof = { version = "0.13", features = ["flamegraph","criterion"] } +pprof = { version = "0.13", features = ["flamegraph", "criterion"] } dirs = "4" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0" -serde_with = "3.2.0" smol_str = { version = "0.1.17", features = ["serde"] } thiserror = "1.0.21" toml = "0.7.2" diff --git a/noir/noir-repo/compiler/noirc_errors/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index 0e66419d6189..41b1cd0ff580 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -16,7 +16,7 @@ fm.workspace = true chumsky.workspace = true noirc_printable_type.workspace = true serde.workspace = true -serde_with.workspace = true +serde_with = "3.2.0" tracing.workspace = true flate2.workspace = true serde_json.workspace = true From b2986341cb46dba96ddf78133fbd0276fbbea5b7 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 15:38:07 +0000 Subject: [PATCH 08/25] chores --- noir/noir-repo/acvm-repo/acvm_js/src/execute.rs | 1 - noir/yarn.lock | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 noir/yarn.lock diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index a5b557a18fb1..fd139f7ac44c 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -275,7 +275,6 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { } _ => None, }; - // TODO add payload to JsExecutionError return Err(JsExecutionError::new( error.to_string(), diff --git a/noir/yarn.lock b/noir/yarn.lock deleted file mode 100644 index fb57ccd13afb..000000000000 --- a/noir/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From 6b9758cecd738a4a070a4bd4084f4d6b45c81d9b Mon Sep 17 00:00:00 2001 From: sirasistant Date: Tue, 23 Apr 2024 15:54:32 +0000 Subject: [PATCH 09/25] added test showcasing new errors --- .../should_fail_with_matches/src/main.nr | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr index d2b7d155a322..0dcff0d35b1a 100644 --- a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr @@ -17,3 +17,65 @@ fn test_should_fail_with_runtime_match() { fn test_should_fail_without_runtime_match() { assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); } + +#[test(should_fail_with = "PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +fn test_should_fail_with_struct(){ + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, hash); +} + + +#[test(should_fail_with = "A: 0x00 is not 1!")] +fn test_should_fail_with_basic_type_fmt_string() { + let a = 0; + let b = 1; + assert_eq(a, b, f"A: {a} is not 1!"); +} + +#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +fn test_should_fail_with_struct_fmt_string() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, f"Invalid hash: {hash}"); +} + +// Also test unconstrained versions + +#[test(should_fail_with = "Not equal")] +unconstrained fn unconstrained_test_should_fail_with_match() { + assert_eq(0, 1, "Not equal"); +} + +#[test(should_fail)] +unconstrained fn unconstrained_test_should_fail_without_match() { + assert_eq(0, 1); +} + +#[test(should_fail_with = "Not equal")] +unconstrained fn unconstrained_test_should_fail_with_runtime_match() { + assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0, "Not equal"); +} + +#[test(should_fail)] +unconstrained fn unconstrained_test_should_fail_without_runtime_match() { + assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); +} + +#[test(should_fail_with = "PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +unconstrained fn unconstrained_test_should_fail_with_struct(){ + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, hash); +} + + +#[test(should_fail_with = "A: 0x00 is not 1!")] +unconstrained fn unconstrained_test_should_fail_with_basic_type_fmt_string() { + let a = 0; + let b = 1; + assert_eq(a, b, f"A: {a} is not 1!"); +} + +#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +unconstrained fn unconstrained_test_should_fail_with_struct_fmt_string() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, f"Invalid hash: {hash}"); +} From c8d786a63065231d3c453515b27b7ba5c52e681c Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 08:38:12 +0000 Subject: [PATCH 10/25] refactor: use selectors instead of ids --- .../acvm-repo/acir/src/circuit/mod.rs | 2 +- .../acvm-repo/acvm/src/pwg/brillig.rs | 25 ++++++++++++--- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 31 ++++++++++++++++-- .../acvm-repo/acvm_js/src/execute.rs | 29 +++++++++++++---- .../acvm_js/src/js_execution_error.rs | 12 +++---- .../compiler/noirc_driver/src/abi_gen.rs | 2 +- .../src/brillig/brillig_gen/brillig_block.rs | 2 +- .../brillig_ir/codegen_control_flow.rs | 5 +-- .../compiler/noirc_evaluator/src/ssa.rs | 6 ++-- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 8 ++--- .../src/ssa/function_builder/mod.rs | 8 ++--- .../noirc_evaluator/src/ssa/ir/instruction.rs | 32 ++++++++++++++++--- .../src/ssa/ssa_gen/context.rs | 12 ++----- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 6 ++-- .../src/ssa/ssa_gen/program.rs | 8 ++--- noir/noir-repo/tooling/nargo/src/errors.rs | 16 +++++----- noir/noir-repo/tooling/noirc_abi/src/lib.rs | 2 +- yarn-project/simulator/src/acvm/acvm.ts | 14 -------- .../src/avm/opcodes/external_calls.test.ts | 2 +- .../simulator/src/client/private_execution.ts | 6 +--- .../src/client/unconstrained_execution.ts | 6 +--- yarn-project/simulator/src/public/executor.ts | 6 +--- 22 files changed, 142 insertions(+), 98 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 6c294353c6df..8be415b3922b 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -77,7 +77,7 @@ pub struct Circuit { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AssertionPayload { StaticString(String), - Expression(/* type_id */ usize, Vec), + Expression(/* error_selector */ u64, Vec), } #[derive(Debug, Copy, Clone)] diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 87c6f8e43a66..543b30ed277b 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -208,14 +208,29 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { .iter() .map(|memory_value| memory_value.to_field()) .collect(); - let revert_data_type_id = fields + let error_selector = fields .remove(0) .try_to_u64() - .expect("Error id doesn't fit in a u64") - .try_into() - .expect("Error id doesn't fit in a usize"); + .expect("Error selector doesn't fit in a u64"); - Some(ResolvedAssertionPayload::Raw(revert_data_type_id, fields)) + match error_selector { + 0 => { + // If the error selector is 0, it means the error is a string + let string = fields + .iter() + .map(|field| { + let as_u8 = + field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect(); + Some(ResolvedAssertionPayload::String(string)) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + Some(ResolvedAssertionPayload::Raw(error_selector, fields)) + } + } } } }; diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index c5534987409c..5734a07bef50 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -117,7 +117,7 @@ impl std::fmt::Display for ErrorLocation { #[derive(Clone, PartialEq, Eq, Debug)] pub enum ResolvedAssertionPayload { String(String), - Raw(/*type_id:*/ usize, Vec), + Raw(/*error_selector:*/ u64, Vec), } #[derive(Clone, PartialEq, Eq, Debug, Error)] @@ -402,14 +402,39 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { AssertionPayload::StaticString(string) => { Some(ResolvedAssertionPayload::String(string.clone())) } - AssertionPayload::Expression(type_id, expression) => { + AssertionPayload::Expression( + error_selector, + expression, + ) => { let elements: Result, _> = expression .iter() .map(|expr| get_value(expr, &self.witness_map)) .collect(); elements .map(|fields| { - ResolvedAssertionPayload::Raw(*type_id, fields) + match error_selector { + 0 => { + // If the error selector is 0, it means the error is a string + let string = fields + .iter() + .map(|field| { + let as_u8 = field + .try_to_u64() + .unwrap_or_default() + as u8; + as_u8 as char + }) + .collect(); + ResolvedAssertionPayload::String(string) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + ResolvedAssertionPayload::Raw( + *error_selector, + fields, + ) + } + } }) .ok() } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index fd139f7ac44c..525d1e458978 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -1,6 +1,7 @@ use std::{future::Future, pin::Pin}; use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::pwg::ResolvedAssertionPayload; use acvm::BlackBoxFunctionSolver; use acvm::{ acir::circuit::{Circuit, Program}, @@ -254,6 +255,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { unreachable!("Execution should not stop while in `InProgress` state.") } ACVMStatus::Failure(error) => { + // Fetch call stack let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), @@ -268,18 +270,31 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { } _ => None, }; - let assertion_payload = match &error { - OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } - | OpcodeResolutionError::BrilligFunctionFailed { payload, .. } => { - payload.clone() + // If the failed opcode has an assertion message, integrate it into the error message for backwards compatibility. + // Otherwise, pass the raw assertion payload as is. + let (message, raw_assertion_payload) = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + payload: Some(payload), + .. } - _ => None, + | OpcodeResolutionError::BrilligFunctionFailed { + payload: Some(payload), + .. + } => match payload { + ResolvedAssertionPayload::Raw(selector, fields) => { + (error.to_string(), Some((*selector, fields.clone()))) + } + ResolvedAssertionPayload::String(message) => { + (format!("Assertion failed: {}", message), None) + } + }, + _ => (error.to_string(), None), }; return Err(JsExecutionError::new( - error.to_string(), + message, call_stack, - assertion_payload, + raw_assertion_payload, ) .into()); } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs index 73bb86845fcd..0f79ba513f56 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs @@ -1,4 +1,4 @@ -use acvm::{acir::circuit::OpcodeLocation, pwg::ResolvedAssertionPayload}; +use acvm::{acir::circuit::OpcodeLocation, FieldElement}; use js_sys::{Array, Error, JsString, Map, Object, Reflect}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; @@ -10,10 +10,9 @@ export type RawAssertionPayload = { typeId: number; fields: string[]; }; -export type ResolvedAssertionPayload = string | RawAssertionPayload; export type ExecutionError = Error & { callStack?: string[]; - assertionPayload?: ResolvedAssertionPayload; + rawAssertionPayload?: RawAssertionPayload; }; "#; @@ -36,7 +35,7 @@ impl JsExecutionError { pub fn new( message: String, call_stack: Option>, - assertion_payload: Option, + assertion_payload: Option<(u64, Vec)>, ) -> Self { let mut error = JsExecutionError::constructor(JsString::from(message)); let js_call_stack = match call_stack { @@ -50,8 +49,7 @@ impl JsExecutionError { None => JsValue::UNDEFINED, }; let assertion_payload = match assertion_payload { - Some(ResolvedAssertionPayload::String(string)) => JsValue::from(string), - Some(ResolvedAssertionPayload::Raw(type_id, fields)) => { + Some((type_id, fields)) => { let raw_payload_map = Map::new(); raw_payload_map .set(&JsValue::from_str("typeId"), &JsValue::from(type_id.to_string())); @@ -67,7 +65,7 @@ impl JsExecutionError { }; error.set_property("callStack", js_call_stack); - error.set_property("assertionPayload", assertion_payload); + error.set_property("rawAssertionPayload", assertion_payload); error } diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 0b9571e9b122..0c27c5d6971c 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -20,7 +20,7 @@ pub(super) fn gen_abi( input_witnesses: Vec, return_witnesses: Vec, return_visibility: Visibility, - error_types: BTreeMap, + error_types: BTreeMap, ) -> Abi { let (parameters, return_type) = compute_function_abi(context, func_id); let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 0dec057815b0..74edb2b506d5 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -270,7 +270,7 @@ impl<'block> BrilligBlock<'block> { condition, payload_values, payload_as_params, - typ_id.to_usize(), + typ_id.to_u64(), ); } Some(ConstrainError::Intrinsic(message)) => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index d73c93075904..fd4c7b73244f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -148,7 +148,7 @@ impl BrilligContext { condition: SingleAddrVariable, revert_data_items: Vec, revert_data_types: Vec, - revert_data_type: usize, + revert_data_type: u64, ) { assert!(condition.bit_size == 1); @@ -162,7 +162,8 @@ impl BrilligContext { let current_revert_data_pointer = ctx.allocate_register(); ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); - let revert_data_id = ctx.make_usize_constant_instruction(revert_data_type.into()); + let revert_data_id = + ctx.make_usize_constant_instruction((revert_data_type as u128).into()); ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); for (revert_variable, revert_param) in diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 15eceaab1c4c..aa3beb71507b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -105,13 +105,13 @@ pub struct SsaProgramArtifact { pub main_input_witnesses: Vec, pub main_return_witnesses: Vec, pub names: Vec, - pub error_types: BTreeMap, + pub error_types: BTreeMap, } impl SsaProgramArtifact { fn new( unconstrained_functions: Vec, - error_types: BTreeMap, + error_types: BTreeMap, ) -> Self { let program = AcirProgram { functions: Vec::default(), unconstrained_functions }; Self { @@ -171,7 +171,7 @@ pub fn create_program( let error_types = error_types .into_iter() - .map(|(error_typ_id, error_typ)| (error_typ_id.to_usize(), error_typ)) + .map(|(error_typ_id, error_typ)| (error_typ_id.to_u64(), error_typ)) .collect(); let mut program_artifact = SsaProgramArtifact::new(generated_brillig, error_types); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index cae0a5572c3f..dc32a6da4c4f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -8,7 +8,7 @@ use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; -use super::ir::instruction::{ConstrainError, ErrorType, ErrorTypeId}; +use super::ir::instruction::{ConstrainError, ErrorSelector, ErrorType}; use super::{ ir::{ dfg::DataFlowGraph, @@ -227,7 +227,7 @@ impl AcirValue { } pub(crate) type Artifacts = - (Vec, Vec, BTreeMap); + (Vec, Vec, BTreeMap); impl Ssa { #[tracing::instrument(level = "trace", skip_all)] @@ -277,7 +277,7 @@ impl Ssa { } Distinctness::DuplicationAllowed => {} } - Ok((acirs, brillig, self.error_id_to_type)) + Ok((acirs, brillig, self.error_selector_to_type)) } } @@ -549,7 +549,7 @@ impl<'a> Context<'a> { self.acir_context.var_to_expression(var) })?; - Some(AssertionPayload::Expression(id.to_usize(), expressions)) + Some(AssertionPayload::Expression(id.to_u64(), expressions)) } } } else { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 86aa631c3571..232047c6dc9a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -18,7 +18,7 @@ use super::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, function::{InlineType, RuntimeType}, - instruction::{ConstrainError, ErrorType, ErrorTypeId, InstructionId, Intrinsic}, + instruction::{ConstrainError, ErrorSelector, ErrorType, InstructionId, Intrinsic}, }, ssa_gen::Ssa, }; @@ -35,7 +35,7 @@ pub(crate) struct FunctionBuilder { current_block: BasicBlockId, finished_functions: Vec, call_stack: CallStack, - error_types: BTreeMap, + error_types: BTreeMap, } impl FunctionBuilder { @@ -477,8 +477,8 @@ impl FunctionBuilder { } } - pub(crate) fn record_error_type(&mut self, id: ErrorTypeId, typ: ErrorType) { - self.error_types.insert(id, typ); + pub(crate) fn record_error_type(&mut self, selector: ErrorSelector, typ: ErrorType) { + self.error_types.insert(selector, typ); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 9367ab831bb4..366a774c77e3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,4 +1,7 @@ +use std::hash::{Hash, Hasher}; + use acvm::{acir::BlackBoxFunc, FieldElement}; +use fxhash::FxHasher; use iter_extended::vecmap; use noirc_frontend::hir_def::types::Type as HirType; @@ -597,17 +600,38 @@ impl Instruction { } } +pub(crate) type ErrorType = HirType; + +#[derive(Debug, Copy, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] +pub(crate) struct ErrorSelector(u64); + +impl ErrorSelector { + pub(crate) fn new(typ: &ErrorType) -> Self { + match typ { + ErrorType::String(_) => Self(0), + _ => { + let mut hasher = FxHasher::default(); + typ.hash(&mut hasher); + let hash = hasher.finish(); + assert!(hash != 0, "ICE: Error type {} collides with the string error type", typ); + Self(hash) + } + } + } + + pub(crate) fn to_u64(self) -> u64 { + self.0 + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), // These are errors issued by the user - UserDefined(Vec, ErrorTypeId), + UserDefined(Vec, ErrorSelector), } -pub(crate) type ErrorType = HirType; -pub(crate) type ErrorTypeId = Id; - impl From for ConstrainError { fn from(value: String) -> Self { ConstrainError::Intrinsic(value) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 0b6b81fb74e0..ceb7c7e48d2a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -14,8 +14,8 @@ use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::dfg::DataFlowGraph; use crate::ssa::ir::function::{Function, RuntimeType}; use crate::ssa::ir::function::{FunctionId as IrFunctionId, InlineType}; -use crate::ssa::ir::instruction::{BinaryOp, ErrorType}; -use crate::ssa::ir::instruction::{ErrorTypeId, Instruction}; +use crate::ssa::ir::instruction::BinaryOp; +use crate::ssa::ir::instruction::Instruction; use crate::ssa::ir::map::AtomicCounter; use crate::ssa::ir::types::{NumericType, Type}; use crate::ssa::ir::value::ValueId; @@ -73,8 +73,6 @@ pub(super) struct SharedContext { /// Shared counter used to assign the ID of the next function function_counter: AtomicCounter, - error_id_counter: AtomicCounter, - /// The entire monomorphized source program pub(super) program: Program, } @@ -1203,7 +1201,6 @@ impl SharedContext { functions: Default::default(), function_queue: Default::default(), function_counter: Default::default(), - error_id_counter: Default::default(), program, } } @@ -1235,11 +1232,6 @@ impl SharedContext { next_id } - - /// Used to create a unique error id for matching error types used in the program. - pub(super) fn create_error_id(&self) -> ErrorTypeId { - self.error_id_counter.next() - } } /// Used to remember the results of each step of extracting a value from an ast::LValue diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 2e4b7a56bf6e..bf407d4820f2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -24,6 +24,7 @@ use self::{ value::{Tree, Values}, }; +use super::ir::instruction::ErrorSelector; use super::{ function_builder::data_bus::DataBus, ir::{ @@ -705,10 +706,9 @@ impl<'a> FunctionContext<'a> { let Some(assert_message_payload) = assert_message else { return Ok(None) }; let (assert_message_expression, assert_message_typ) = assert_message_payload.as_ref(); - let values: Vec> = - self.codegen_expression(assert_message_expression)?.into_value_list(self); + let values = self.codegen_expression(assert_message_expression)?.into_value_list(self); - let error_type_id = self.shared_context.create_error_id(); + let error_type_id = ErrorSelector::new(assert_message_typ); // Do not record string errors in the ABI match assert_message_typ { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 13066d81d53e..03d4ac05bd9b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -4,7 +4,7 @@ use iter_extended::btree_map; use crate::ssa::ir::{ function::{Function, FunctionId, RuntimeType}, - instruction::ErrorTypeId, + instruction::ErrorSelector, map::AtomicCounter, }; use noirc_frontend::hir_def::types::Type as HirType; @@ -19,7 +19,7 @@ pub(crate) struct Ssa { /// This mapping is necessary to use the correct function pointer for an ACIR call, /// as the final program artifact will be a list of only entry point functions. pub(crate) entry_point_to_generated_index: BTreeMap, - pub(crate) error_id_to_type: BTreeMap, + pub(crate) error_selector_to_type: BTreeMap, } impl Ssa { @@ -27,7 +27,7 @@ impl Ssa { /// The first function in this vector is expected to be the main function. pub(crate) fn new( functions: Vec, - error_types: BTreeMap, + error_types: BTreeMap, ) -> Self { let main_id = functions.first().expect("Expected at least 1 SSA function").id(); let mut max_id = main_id; @@ -56,7 +56,7 @@ impl Ssa { main_id, next_id: AtomicCounter::starting_after(max_id), entry_point_to_generated_index, - error_id_to_type: error_types, + error_selector_to_type: error_types, } } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 3328902d339d..6844fdf1ed77 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -61,7 +61,7 @@ impl NargoError { /// in tests to expected failure messages pub fn user_defined_failure_message( &self, - error_types: BTreeMap, + error_types: BTreeMap, ) -> Option { let execution_error = match self { NargoError::ExecutionError(error) => error, @@ -71,12 +71,12 @@ impl NargoError { match execution_error { ExecutionError::AssertionFailed(payload, _) => match payload { ResolvedAssertionPayload::String(message) => Some(message.to_string()), - ResolvedAssertionPayload::Raw(error_id, fields) => { - if let Some(abi_type) = error_types.get(error_id) { + ResolvedAssertionPayload::Raw(error_selector, fields) => { + if let Some(abi_type) = error_types.get(error_selector) { let decoded = prepare_for_display(fields, abi_type.clone()); Some(decoded.to_string()) } else { - Some(display_as_string(fields)) + None } } }, @@ -218,7 +218,7 @@ fn prepare_for_display(fields: &[FieldElement], abi_typ: AbiType) -> PrintableVa } fn extract_message_from_error( - error_types: &BTreeMap, + error_types: &BTreeMap, nargo_err: &NargoError, ) -> String { match nargo_err { @@ -229,13 +229,13 @@ fn extract_message_from_error( format!("Assertion failed: '{message}'") } NargoError::ExecutionError(ExecutionError::AssertionFailed( - ResolvedAssertionPayload::Raw(error_id, fields), + ResolvedAssertionPayload::Raw(error_selector, fields), .., )) => { - if let Some(abi_type) = error_types.get(error_id) { + if let Some(abi_type) = error_types.get(error_selector) { format!("Assertion failed: {}", prepare_for_display(fields, abi_type.clone())) } else { - format!("Assertion failed {}", display_as_string(fields)) + "Assertion failed".to_string() } } NargoError::ExecutionError(ExecutionError::SolvingError( diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 7fbe1aa4d9a9..5ac527507f61 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -251,7 +251,7 @@ pub struct Abi { pub param_witnesses: BTreeMap>>, pub return_type: Option, pub return_witnesses: Vec, - pub error_types: BTreeMap, + pub error_types: BTreeMap, } impl Abi { diff --git a/yarn-project/simulator/src/acvm/acvm.ts b/yarn-project/simulator/src/acvm/acvm.ts index 2ca82fa89b31..d166b5d16c33 100644 --- a/yarn-project/simulator/src/acvm/acvm.ts +++ b/yarn-project/simulator/src/acvm/acvm.ts @@ -12,7 +12,6 @@ import { import { traverseCauseChain } from '../common/errors.js'; import { type ACVMWitness } from './acvm_types.js'; -import { fromACVMField } from './deserialize.js'; import { type ORACLE_NAMES } from './oracle/index.js'; /** @@ -158,16 +157,3 @@ export function extractCallStack( return callStack; } } - -export function extractAssertionMessage(error: Error | ExecutionError): string | undefined { - if (!('assertionPayload' in error) || !error.assertionPayload) { - return undefined; - } - const { assertionPayload } = error; - - if (typeof assertionPayload === 'string') { - return assertionPayload; - } else { - return String.fromCharCode(...assertionPayload.fields.map((field: string) => fromACVMField(field).toNumber())); - } -} diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts index 09fa7e0b5c1a..2c76fc38f832 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts @@ -311,7 +311,7 @@ describe('External Calls', () => { it('Should return data and revert from the revert opcode', async () => { const returnData = [...'assert message'].flatMap(c => new Field(c.charCodeAt(0))); - returnData.unshift(new Field(0n)); // Prepend an error id + returnData.unshift(new Field(0n)); // Prepend an error selector context.machineState.memory.setSlice(0, returnData); diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 344119163d09..64cf9baae7d8 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -5,7 +5,7 @@ import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { witnessMapToFields } from '../acvm/deserialize.js'; -import { Oracle, acvm, extractAssertionMessage, extractCallStack } from '../acvm/index.js'; +import { Oracle, acvm, extractCallStack } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { type ClientExecutionContext } from './client_execution_context.js'; import { type ExecutionResult } from './execution_result.js'; @@ -28,10 +28,6 @@ export async function executePrivateFunction( const acvmCallback = new Oracle(context); const acirExecutionResult = await acvm(await AcirSimulator.getSolver(), acir, initialWitness, acvmCallback).catch( (err: Error) => { - const assertionMessage = extractAssertionMessage(err); - if (assertionMessage) { - err.message = `Assertion failed: ${assertionMessage}`; - } throw new ExecutionError( err.message, { diff --git a/yarn-project/simulator/src/client/unconstrained_execution.ts b/yarn-project/simulator/src/client/unconstrained_execution.ts index 30d76682e4a3..d821ca9fea9c 100644 --- a/yarn-project/simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/simulator/src/client/unconstrained_execution.ts @@ -5,7 +5,7 @@ import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { witnessMapToFields } from '../acvm/deserialize.js'; -import { Oracle, acvm, extractAssertionMessage, extractCallStack, toACVMWitness } from '../acvm/index.js'; +import { Oracle, acvm, extractCallStack, toACVMWitness } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { AcirSimulator } from './simulator.js'; import { type ViewDataOracle } from './view_data_oracle.js'; @@ -33,10 +33,6 @@ export async function executeUnconstrainedFunction( initialWitness, new Oracle(oracle), ).catch((err: Error) => { - const assertionMessage = extractAssertionMessage(err); - if (assertionMessage) { - err.message = `Assertion failed: ${assertionMessage}`; - } throw new ExecutionError( err.message, { diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 2b9b7c1a8b09..57710e7c5a85 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -6,7 +6,7 @@ import { spawn } from 'child_process'; import fs from 'fs/promises'; import path from 'path'; -import { Oracle, acvm, extractAssertionMessage, extractCallStack, witnessMapToFields } from '../acvm/index.js'; +import { Oracle, acvm, extractCallStack, witnessMapToFields } from '../acvm/index.js'; import { AvmContext } from '../avm/avm_context.js'; import { AvmMachineState } from '../avm/avm_machine_state.js'; import { AvmSimulator } from '../avm/avm_simulator.js'; @@ -105,10 +105,6 @@ async function executePublicFunctionAcvm( }; } catch (err_) { const err = err_ as Error; - const assertionMessage = extractAssertionMessage(err); - if (assertionMessage) { - err.message = `Assertion failed: ${assertionMessage}`; - } const ee = new ExecutionError( err.message, { From e32220970a3fb565cf968441bce29f61aceda44f Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 09:12:48 +0000 Subject: [PATCH 11/25] feat: try to extract static assertion messages in acir gen --- .../acvm-repo/acir/src/circuit/mod.rs | 2 + .../acvm-repo/acvm/src/pwg/brillig.rs | 4 +- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 5 +- .../compiler/noirc_evaluator/src/ssa.rs | 2 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 6 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 6 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 67 +++++++++++++++---- .../noirc_evaluator/src/ssa/ir/instruction.rs | 7 +- 8 files changed, 75 insertions(+), 24 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 8be415b3922b..e90b713373dc 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -74,6 +74,8 @@ pub struct Circuit { pub recursive: bool, } +pub const STRING_ERROR_SELECTOR: u64 = 0; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AssertionPayload { StaticString(String), diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 543b30ed277b..5ad45d8e0282 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -5,7 +5,7 @@ use acir::{ circuit::{ brillig::{Brillig, BrilligInputs, BrilligOutputs}, opcodes::BlockId, - OpcodeLocation, + OpcodeLocation, STRING_ERROR_SELECTOR, }, native_types::WitnessMap, FieldElement, @@ -214,7 +214,7 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { .expect("Error selector doesn't fit in a u64"); match error_selector { - 0 => { + STRING_ERROR_SELECTOR => { // If the error selector is 0, it means the error is a string let string = fields .iter() diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 5734a07bef50..9e2b86fb3b7e 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -6,6 +6,7 @@ use acir::{ brillig::ForeignCallResult, circuit::{ brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, Opcode, OpcodeLocation, + STRING_ERROR_SELECTOR, }, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, @@ -412,8 +413,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { .collect(); elements .map(|fields| { - match error_selector { - 0 => { + match *error_selector { + STRING_ERROR_SELECTOR => { // If the error selector is 0, it means the error is a string let string = fields .iter() diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index aa3beb71507b..92461e1a1b12 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -217,7 +217,7 @@ fn convert_generated_acir_into_circuit( return_witnesses, locations, input_witnesses, - assert_messages, + assertion_payloads: assert_messages, warnings, name, .. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 9b044ecfafcd..af91b1ca3eba 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -510,7 +510,9 @@ impl AcirContext { self.acir_ir.assert_is_zero(diff_expr); if let Some(payload) = assert_message { - self.acir_ir.assert_messages.insert(self.acir_ir.last_acir_opcode_location(), payload); + self.acir_ir + .assertion_payloads + .insert(self.acir_ir.last_acir_opcode_location(), payload); } self.mark_variables_equivalent(lhs, rhs)?; @@ -981,7 +983,7 @@ impl AcirContext { let witness = self.var_to_witness(witness_var)?; self.acir_ir.range_constraint(witness, *bit_size)?; if let Some(message) = message { - self.acir_ir.assert_messages.insert( + self.acir_ir.assertion_payloads.insert( self.acir_ir.last_acir_opcode_location(), AssertionPayload::StaticString(message.clone()), ); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 9a1c999983e4..f26709387df9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -54,7 +54,7 @@ pub(crate) struct GeneratedAcir { pub(crate) call_stack: CallStack, /// Correspondence between an opcode index and the error message associated with it. - pub(crate) assert_messages: BTreeMap, + pub(crate) assertion_payloads: BTreeMap, pub(crate) warnings: Vec, @@ -610,7 +610,7 @@ impl GeneratedAcir { ); } for (brillig_index, message) in generated_brillig.assert_messages { - self.assert_messages.insert( + self.assertion_payloads.insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index }, AssertionPayload::StaticString(message.clone()), ); @@ -638,7 +638,7 @@ impl GeneratedAcir { ); } for (brillig_index, message) in generated_brillig.assert_messages.iter() { - self.assert_messages.insert( + self.assertion_payloads.insert( OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index: *brillig_index, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index dc32a6da4c4f..332ee5332657 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -30,7 +30,7 @@ use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::AssertionPayload; +use acvm::acir::circuit::{AssertionPayload, STRING_ERROR_SELECTOR}; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -539,17 +539,26 @@ impl<'a> Context<'a> { ConstrainError::Intrinsic(string) => { Some(AssertionPayload::StaticString(string.clone())) } - ConstrainError::UserDefined(values, id) => { - let acir_vars: Vec<(AcirVar, AcirType)> = values - .iter() - .flat_map(|value| self.convert_value(*value, dfg).flatten()) - .collect(); - - let expressions = try_vecmap(acir_vars, |(var, _)| { - self.acir_context.var_to_expression(var) - })?; - - Some(AssertionPayload::Expression(id.to_u64(), expressions)) + ConstrainError::UserDefined(values, error_selector) => { + if let Some(constant_string) = + self.try_to_extract_constant_string(*error_selector, values, dfg) + { + Some(AssertionPayload::StaticString(constant_string)) + } else { + let acir_vars: Vec<(AcirVar, AcirType)> = values + .iter() + .flat_map(|value| self.convert_value(*value, dfg).flatten()) + .collect(); + + let expressions = try_vecmap(acir_vars, |(var, _)| { + self.acir_context.var_to_expression(var) + })?; + + Some(AssertionPayload::Expression( + error_selector.to_u64(), + expressions, + )) + } } } } else { @@ -2513,6 +2522,40 @@ impl<'a> Context<'a> { } } } + + /// Tries to extract a constant string from an error payload. + fn try_to_extract_constant_string( + &self, + error_selector: ErrorSelector, + values: &[ValueId], + dfg: &DataFlowGraph, + ) -> Option { + (error_selector.to_u64() == STRING_ERROR_SELECTOR) + .then_some(()) + .and_then(|()| { + let fields: Option> = values + .iter() + .map(|value_id| { + let value = &dfg[*value_id]; + if let Value::NumericConstant { constant, .. } = value { + Some(constant) + } else { + None + } + }) + .collect(); + fields + }) + .map(|fields| { + fields + .iter() + .map(|field| { + let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect() + }) + } } // We can omit the element size array for arrays which don't contain arrays or slices. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 366a774c77e3..0d2c894b3350 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,6 +1,9 @@ use std::hash::{Hash, Hasher}; -use acvm::{acir::BlackBoxFunc, FieldElement}; +use acvm::{ + acir::{circuit::STRING_ERROR_SELECTOR, BlackBoxFunc}, + FieldElement, +}; use fxhash::FxHasher; use iter_extended::vecmap; use noirc_frontend::hir_def::types::Type as HirType; @@ -608,7 +611,7 @@ pub(crate) struct ErrorSelector(u64); impl ErrorSelector { pub(crate) fn new(typ: &ErrorType) -> Self { match typ { - ErrorType::String(_) => Self(0), + ErrorType::String(_) => Self(STRING_ERROR_SELECTOR), _ => { let mut hasher = FxHasher::default(); typ.hash(&mut hasher); From f012bca4992dde63dc8d95f729d784ba7193ccc0 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 10:25:46 +0000 Subject: [PATCH 12/25] feat: allow dynamic arrays in dynamic assertions --- .../dsl/acir_format/serde/acir.hpp | 197 ++++++++++++++++-- .../noir-repo/acvm-repo/acir/codegen/acir.cpp | 169 +++++++++++++-- .../acvm-repo/acir/src/circuit/mod.rs | 10 +- noir/noir-repo/acvm-repo/acir/src/lib.rs | 4 +- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 112 +++++----- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 24 ++- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 13 +- 7 files changed, 441 insertions(+), 88 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index f4935144f59c..fdd6b1218b60 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -1154,6 +1154,31 @@ struct Opcode { static Opcode bincodeDeserialize(std::vector); }; +struct ExpressionOrMemory { + + struct Expression { + Program::Expression value; + + friend bool operator==(const Expression&, const Expression&); + std::vector bincodeSerialize() const; + static Expression bincodeDeserialize(std::vector); + }; + + struct Memory { + Program::BlockId value; + + friend bool operator==(const Memory&, const Memory&); + std::vector bincodeSerialize() const; + static Memory bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const ExpressionOrMemory&, const ExpressionOrMemory&); + std::vector bincodeSerialize() const; + static ExpressionOrMemory bincodeDeserialize(std::vector); +}; + struct AssertionPayload { struct StaticString { @@ -1164,15 +1189,15 @@ struct AssertionPayload { static StaticString bincodeDeserialize(std::vector); }; - struct Expression { - std::tuple> value; + struct Raw { + std::tuple> value; - friend bool operator==(const Expression&, const Expression&); + friend bool operator==(const Raw&, const Raw&); std::vector bincodeSerialize() const; - static Expression bincodeDeserialize(std::vector); + static Raw bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const AssertionPayload&, const AssertionPayload&); std::vector bincodeSerialize() const; @@ -1369,7 +1394,7 @@ Program::AssertionPayload::StaticString serde::Deserializable AssertionPayload::Expression::bincodeSerialize() const +inline std::vector AssertionPayload::Raw::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } -inline AssertionPayload::Expression AssertionPayload::Expression::bincodeDeserialize(std::vector input) +inline AssertionPayload::Raw AssertionPayload::Raw::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw_or_abort("Some input bytes were not read"); } @@ -1398,18 +1423,18 @@ inline AssertionPayload::Expression AssertionPayload::Expression::bincodeDeseria template <> template -void serde::Serializable::serialize( - const Program::AssertionPayload::Expression& obj, Serializer& serializer) +void serde::Serializable::serialize(const Program::AssertionPayload::Raw& obj, + Serializer& serializer) { serde::Serializable::serialize(obj.value, serializer); } template <> template -Program::AssertionPayload::Expression serde::Deserializable::deserialize( +Program::AssertionPayload::Raw serde::Deserializable::deserialize( Deserializer& deserializer) { - Program::AssertionPayload::Expression obj; + Program::AssertionPayload::Raw obj; obj.value = serde::Deserializable::deserialize(deserializer); return obj; } @@ -6720,6 +6745,150 @@ Program::Expression serde::Deserializable::deserialize(Dese namespace Program { +inline bool operator==(const ExpressionOrMemory& lhs, const ExpressionOrMemory& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector ExpressionOrMemory::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline ExpressionOrMemory ExpressionOrMemory::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ExpressionOrMemory& obj, + Serializer& serializer) +{ + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::ExpressionOrMemory serde::Deserializable::deserialize(Deserializer& deserializer) +{ + deserializer.increase_container_depth(); + Program::ExpressionOrMemory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + +inline bool operator==(const ExpressionOrMemory::Expression& lhs, const ExpressionOrMemory::Expression& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector ExpressionOrMemory::Expression::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline ExpressionOrMemory::Expression ExpressionOrMemory::Expression::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize( + const Program::ExpressionOrMemory::Expression& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Expression serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Program::ExpressionOrMemory::Expression obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + +inline bool operator==(const ExpressionOrMemory::Memory& lhs, const ExpressionOrMemory::Memory& rhs) +{ + if (!(lhs.value == rhs.value)) { + return false; + } + return true; +} + +inline std::vector ExpressionOrMemory::Memory::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline ExpressionOrMemory::Memory ExpressionOrMemory::Memory::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ExpressionOrMemory::Memory& obj, + Serializer& serializer) +{ + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Memory serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Program::ExpressionOrMemory::Memory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + inline bool operator==(const ExpressionWidth& lhs, const ExpressionWidth& rhs) { if (!(lhs.value == rhs.value)) { diff --git a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index 155438f7ffb4..958f1cab4386 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -1092,6 +1092,31 @@ namespace Program { static Opcode bincodeDeserialize(std::vector); }; + struct ExpressionOrMemory { + + struct Expression { + Program::Expression value; + + friend bool operator==(const Expression&, const Expression&); + std::vector bincodeSerialize() const; + static Expression bincodeDeserialize(std::vector); + }; + + struct Memory { + Program::BlockId value; + + friend bool operator==(const Memory&, const Memory&); + std::vector bincodeSerialize() const; + static Memory bincodeDeserialize(std::vector); + }; + + std::variant value; + + friend bool operator==(const ExpressionOrMemory&, const ExpressionOrMemory&); + std::vector bincodeSerialize() const; + static ExpressionOrMemory bincodeDeserialize(std::vector); + }; + struct AssertionPayload { struct StaticString { @@ -1102,15 +1127,15 @@ namespace Program { static StaticString bincodeDeserialize(std::vector); }; - struct Expression { - std::tuple> value; + struct Raw { + std::tuple> value; - friend bool operator==(const Expression&, const Expression&); + friend bool operator==(const Raw&, const Raw&); std::vector bincodeSerialize() const; - static Expression bincodeDeserialize(std::vector); + static Raw bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const AssertionPayload&, const AssertionPayload&); std::vector bincodeSerialize() const; @@ -1291,20 +1316,20 @@ Program::AssertionPayload::StaticString serde::Deserializable AssertionPayload::Expression::bincodeSerialize() const { + inline std::vector AssertionPayload::Raw::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline AssertionPayload::Expression AssertionPayload::Expression::bincodeDeserialize(std::vector input) { + inline AssertionPayload::Raw AssertionPayload::Raw::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -1315,14 +1340,14 @@ namespace Program { template <> template -void serde::Serializable::serialize(const Program::AssertionPayload::Expression &obj, Serializer &serializer) { +void serde::Serializable::serialize(const Program::AssertionPayload::Raw &obj, Serializer &serializer) { serde::Serializable::serialize(obj.value, serializer); } template <> template -Program::AssertionPayload::Expression serde::Deserializable::deserialize(Deserializer &deserializer) { - Program::AssertionPayload::Expression obj; +Program::AssertionPayload::Raw serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::Raw obj; obj.value = serde::Deserializable::deserialize(deserializer); return obj; } @@ -5550,6 +5575,124 @@ Program::Expression serde::Deserializable::deserialize(Dese return obj; } +namespace Program { + + inline bool operator==(const ExpressionOrMemory &lhs, const ExpressionOrMemory &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory ExpressionOrMemory::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ExpressionOrMemory &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::ExpressionOrMemory serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::ExpressionOrMemory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Program { + + inline bool operator==(const ExpressionOrMemory::Expression &lhs, const ExpressionOrMemory::Expression &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::Expression::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory::Expression ExpressionOrMemory::Expression::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ExpressionOrMemory::Expression &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Expression serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ExpressionOrMemory::Expression obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Program { + + inline bool operator==(const ExpressionOrMemory::Memory &lhs, const ExpressionOrMemory::Memory &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector ExpressionOrMemory::Memory::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline ExpressionOrMemory::Memory ExpressionOrMemory::Memory::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::ExpressionOrMemory::Memory &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.value, serializer); +} + +template <> +template +Program::ExpressionOrMemory::Memory serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::ExpressionOrMemory::Memory obj; + obj.value = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const ExpressionWidth &lhs, const ExpressionWidth &rhs) { diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index e90b713373dc..91ea9c043892 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -15,7 +15,7 @@ use serde::{de::Error as DeserializationError, Deserialize, Deserializer, Serial use std::collections::BTreeSet; -use self::brillig::BrilligBytecode; +use self::{brillig::BrilligBytecode, opcodes::BlockId}; /// Specifies the maximum width of the expressions which will be constrained. /// @@ -76,10 +76,16 @@ pub struct Circuit { pub const STRING_ERROR_SELECTOR: u64 = 0; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExpressionOrMemory { + Expression(Expression), + Memory(BlockId), +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AssertionPayload { StaticString(String), - Expression(/* error_selector */ u64, Vec), + Raw(/* error_selector */ u64, Vec), } #[derive(Debug, Copy, Clone)] diff --git a/noir/noir-repo/acvm-repo/acir/src/lib.rs b/noir/noir-repo/acvm-repo/acir/src/lib.rs index e8dc868a84db..24f27aae06fa 100644 --- a/noir/noir-repo/acvm-repo/acir/src/lib.rs +++ b/noir/noir-repo/acvm-repo/acir/src/lib.rs @@ -42,7 +42,8 @@ mod reflection { brillig::{BrilligInputs, BrilligOutputs}, directives::Directive, opcodes::BlackBoxFuncCall, - AssertionPayload, Circuit, ExpressionWidth, Opcode, OpcodeLocation, Program, + AssertionPayload, Circuit, ExpressionOrMemory, ExpressionWidth, Opcode, OpcodeLocation, + Program, }, native_types::{Witness, WitnessMap, WitnessStack}, }; @@ -75,6 +76,7 @@ mod reflection { tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); tracer.trace_simple_type::().unwrap(); + tracer.trace_simple_type::().unwrap(); let registry = tracer.registry().unwrap(); diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 9e2b86fb3b7e..25f8e509cc8f 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, circuit::{ - brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, Opcode, OpcodeLocation, - STRING_ERROR_SELECTOR, + brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, ExpressionOrMemory, Opcode, + OpcodeLocation, STRING_ERROR_SELECTOR, }, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, @@ -396,54 +396,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } => { let location = OpcodeLocation::Acir(self.instruction_pointer()); *opcode_index = ErrorLocation::Resolved(location); - *assertion_payload = - self.assertion_payloads.iter().find_map(|(loc, payload)| { - if location == *loc { - match payload { - AssertionPayload::StaticString(string) => { - Some(ResolvedAssertionPayload::String(string.clone())) - } - AssertionPayload::Expression( - error_selector, - expression, - ) => { - let elements: Result, _> = expression - .iter() - .map(|expr| get_value(expr, &self.witness_map)) - .collect(); - elements - .map(|fields| { - match *error_selector { - STRING_ERROR_SELECTOR => { - // If the error selector is 0, it means the error is a string - let string = fields - .iter() - .map(|field| { - let as_u8 = field - .try_to_u64() - .unwrap_or_default() - as u8; - as_u8 as char - }) - .collect(); - ResolvedAssertionPayload::String(string) - } - _ => { - // If the error selector is not 0, it means the error is a custom error - ResolvedAssertionPayload::Raw( - *error_selector, - fields, - ) - } - } - }) - .ok() - } - } - } else { - None - } - }); + *assertion_payload = self.extract_assertion_payload(location); } // All other errors are thrown normally. _ => (), @@ -453,6 +406,65 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } } + fn extract_assertion_payload( + &self, + location: OpcodeLocation, + ) -> Option { + let found_assertion_payload = self.assertion_payloads.iter().find_map(|(loc, payload)| { + if location == *loc { + Some(payload) + } else { + None + } + }); + let Some(found_assertion_payload) = found_assertion_payload else { + return None; + }; + match found_assertion_payload { + AssertionPayload::StaticString(string) => { + Some(ResolvedAssertionPayload::String(string.clone())) + } + AssertionPayload::Raw(error_selector, expression) => { + let mut fields = vec![]; + for expr in expression { + match expr { + ExpressionOrMemory::Expression(expr) => { + let value = get_value(expr, &self.witness_map).ok()?; + fields.push(value); + } + ExpressionOrMemory::Memory(block_id) => { + let memory_block = self.block_solvers.get(block_id)?; + fields.extend((0..memory_block.block_len).map(|memory_index| { + *memory_block + .block_value + .get(&memory_index) + .expect("All memory is initialized on creation") + })); + } + } + } + + Some(match *error_selector { + STRING_ERROR_SELECTOR => { + // If the error selector is 0, it means the error is a string + let string = fields + .iter() + .map(|field| { + let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect(); + ResolvedAssertionPayload::String(string) + } + _ => { + // If the error selector is not 0, it means the error is a custom error + ResolvedAssertionPayload::Raw(*error_selector, fields) + } + }) + } + } + } + fn solve_brillig_opcode( &mut self, ) -> Result, OpcodeResolutionError> { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 5f7eb2abc9eb..2d546bc7d86e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -9,7 +9,7 @@ use crate::ssa::ir::types::Type as SsaType; use crate::ssa::ir::{instruction::Endian, types::NumericType}; use acvm::acir::circuit::brillig::{BrilligInputs, BrilligOutputs}; use acvm::acir::circuit::opcodes::{BlockId, MemOp}; -use acvm::acir::circuit::{AssertionPayload, Opcode}; +use acvm::acir::circuit::{AssertionPayload, ExpressionOrMemory, Opcode}; use acvm::blackbox_solver; use acvm::brillig_vm::{MemoryValue, VMStatus, VM}; use acvm::{ @@ -521,6 +521,28 @@ impl AcirContext { Ok(()) } + pub(crate) fn vars_to_expressions_or_memory( + &self, + values: &[AcirValue], + ) -> Result, RuntimeError> { + let mut result = Vec::with_capacity(values.len()); + for value in values { + match value { + AcirValue::Var(var, _) => { + result.push(ExpressionOrMemory::Expression(self.var_to_expression(*var)?)); + } + AcirValue::Array(vars) => { + let vars_as_vec: Vec<_> = vars.iter().cloned().collect(); + result.extend(self.vars_to_expressions_or_memory(&vars_as_vec)?); + } + AcirValue::DynamicArray(AcirDynamicArray { block_id, .. }) => { + result.push(ExpressionOrMemory::Memory(*block_id)); + } + } + } + Ok(result) + } + /// Adds a new Variable to context whose value will /// be constrained to be the division of `lhs` and `rhs` pub(crate) fn div_var( diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 0fbdafb7f24d..fb2fd8a27900 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -625,18 +625,17 @@ impl<'a> Context<'a> { { Some(AssertionPayload::StaticString(constant_string)) } else { - let acir_vars: Vec<(AcirVar, AcirType)> = values + let acir_vars: Vec<_> = values .iter() - .flat_map(|value| self.convert_value(*value, dfg).flatten()) + .map(|value| self.convert_value(*value, dfg)) .collect(); - let expressions = try_vecmap(acir_vars, |(var, _)| { - self.acir_context.var_to_expression(var) - })?; + let expressions_or_memory = + self.acir_context.vars_to_expressions_or_memory(&acir_vars)?; - Some(AssertionPayload::Expression( + Some(AssertionPayload::Raw( error_selector.to_u64(), - expressions, + expressions_or_memory, )) } } From e8eb08503ce16faafaf75f9ece7a5215906eec24 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 11:00:30 +0000 Subject: [PATCH 13/25] various refactors --- .../brillig_ir/codegen_control_flow.rs | 4 ++ .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 45 +++------------- .../noirc_evaluator/src/ssa/ir/printer.rs | 51 +++++++++++++++++-- .../src/ssa/ssa_gen/context.rs | 2 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 4 +- noir/noir-repo/tooling/nargo/src/errors.rs | 10 ---- 6 files changed, 60 insertions(+), 56 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index fd4c7b73244f..01d740b3f4a8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -165,6 +165,7 @@ impl BrilligContext { let revert_data_id = ctx.make_usize_constant_instruction((revert_data_type as u128).into()); ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); + ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); for (revert_variable, revert_param) in revert_data_items.into_iter().zip(revert_data_types.into_iter()) @@ -198,6 +199,9 @@ impl BrilligContext { ); } ctx.trap_instruction(revert_data); + ctx.deallocate_register(revert_data.pointer); + ctx.deallocate_register(current_revert_data_pointer); + ctx.deallocate_single_addr(revert_data_id); }); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index fb2fd8a27900..936294620044 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -10,6 +10,7 @@ use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; use super::ir::instruction::{ConstrainError, ErrorSelector, ErrorType}; +use super::ir::printer::try_to_extract_string_from_error_payload; use super::{ ir::{ dfg::DataFlowGraph, @@ -31,7 +32,7 @@ use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::acir::circuit::{AssertionPayload, OpcodeLocation, STRING_ERROR_SELECTOR}; +use acvm::acir::circuit::{AssertionPayload, OpcodeLocation}; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -620,9 +621,11 @@ impl<'a> Context<'a> { Some(AssertionPayload::StaticString(string.clone())) } ConstrainError::UserDefined(values, error_selector) => { - if let Some(constant_string) = - self.try_to_extract_constant_string(*error_selector, values, dfg) - { + if let Some(constant_string) = try_to_extract_string_from_error_payload( + *error_selector, + values, + dfg, + ) { Some(AssertionPayload::StaticString(constant_string)) } else { let acir_vars: Vec<_> = values @@ -2603,40 +2606,6 @@ impl<'a> Context<'a> { } } } - - /// Tries to extract a constant string from an error payload. - fn try_to_extract_constant_string( - &self, - error_selector: ErrorSelector, - values: &[ValueId], - dfg: &DataFlowGraph, - ) -> Option { - (error_selector.to_u64() == STRING_ERROR_SELECTOR) - .then_some(()) - .and_then(|()| { - let fields: Option> = values - .iter() - .map(|value_id| { - let value = &dfg[*value_id]; - if let Value::NumericConstant { constant, .. } = value { - Some(constant) - } else { - None - } - }) - .collect(); - fields - }) - .map(|fields| { - fields - .iter() - .map(|field| { - let as_u8 = field.try_to_u64().unwrap_or_default() as u8; - as_u8 as char - }) - .collect() - }) - } } // We can omit the element size array for arrays which don't contain arrays or slices. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index e4ec1d8e4c51..fb3bf4823904 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -4,13 +4,17 @@ use std::{ fmt::{Formatter, Result}, }; +use acvm::acir::circuit::STRING_ERROR_SELECTOR; use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, + dfg::DataFlowGraph, function::Function, - instruction::{ConstrainError, Instruction, InstructionId, TerminatorInstruction}, - value::ValueId, + instruction::{ + ConstrainError, ErrorSelector, Instruction, InstructionId, TerminatorInstruction, + }, + value::{Value, ValueId}, }; /// Helper function for Function's Display impl to pretty-print the function with the given formatter. @@ -60,7 +64,6 @@ pub(crate) fn display_block( /// Specialize displaying value ids so that if they refer to a numeric /// constant or a function we print those directly. fn value(function: &Function, id: ValueId) -> String { - use super::value::Value; let id = function.dfg.resolve(id); match &function.dfg[id] { Value::NumericConstant { constant, typ } => { @@ -195,6 +198,39 @@ fn display_instruction_inner( } } +/// Tries to extract a constant string from an error payload. +pub(crate) fn try_to_extract_string_from_error_payload( + error_selector: ErrorSelector, + values: &[ValueId], + dfg: &DataFlowGraph, +) -> Option { + (error_selector.to_u64() == STRING_ERROR_SELECTOR) + .then_some(()) + .and_then(|()| { + let fields: Option> = values + .iter() + .map(|value_id| { + let value = &dfg[*value_id]; + if let Value::NumericConstant { constant, .. } = value { + Some(constant) + } else { + None + } + }) + .collect(); + fields + }) + .map(|fields| { + fields + .iter() + .map(|field| { + let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + as_u8 as char + }) + .collect() + }) +} + fn display_constrain_error( function: &Function, error: &ConstrainError, @@ -204,8 +240,13 @@ fn display_constrain_error( ConstrainError::Intrinsic(assert_message_string) => { writeln!(f, "{assert_message_string:?}") } - ConstrainError::UserDefined(values, _) => { - writeln!(f, "{}", value_list(function, values)) + ConstrainError::UserDefined(values, selector) => { + writeln!( + f, + "{}", + try_to_extract_string_from_error_payload(*selector, values, &function.dfg) + .unwrap_or(value_list(function, values)) + ) } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index ceb7c7e48d2a..e591a3d478c4 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -38,7 +38,7 @@ pub(super) struct FunctionContext<'a> { definitions: HashMap, pub(super) builder: FunctionBuilder, - pub(super) shared_context: &'a SharedContext, + shared_context: &'a SharedContext, /// Contains any loops we're currently in the middle of translating. /// These are ordered such that an inner loop is at the end of the vector and diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index bf407d4820f2..1a318e4c083f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -688,9 +688,9 @@ impl<'a> FunctionContext<'a> { // Set the location here for any errors that may occur when we codegen the assert message self.builder.set_location(location); - let assert_message = self.codegen_constrain_error(assert_payload)?; + let assert_payload = self.codegen_constrain_error(assert_payload)?; - self.builder.insert_constrain(expr, true_literal, assert_message); + self.builder.insert_constrain(expr, true_literal, assert_payload); Ok(Self::unit_value()) } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 6844fdf1ed77..ffbd0e78425b 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -252,16 +252,6 @@ fn extract_message_from_error( } } -pub fn display_as_string(fields: &[FieldElement]) -> String { - fields - .iter() - .map(|field| { - let as_u8 = field.try_to_u64().unwrap_or_default() as u8; - as_u8 as char - }) - .collect() -} - /// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. pub fn try_to_diagnose_runtime_error( nargo_err: &NargoError, From 89084190470d5b94f1b4ce719ea7079a66662adc Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 11:17:27 +0000 Subject: [PATCH 14/25] fix constant string extraction --- .../noirc_evaluator/src/ssa/ir/printer.rs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index fb3bf4823904..a6537a826e28 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -159,7 +159,6 @@ fn display_instruction_inner( Instruction::Constrain(lhs, rhs, error) => { write!(f, "constrain {} == {}", show(*lhs), show(*rhs))?; if let Some(error) = error { - write!(f, " ")?; display_constrain_error(function, error, f) } else { writeln!(f) @@ -204,9 +203,12 @@ pub(crate) fn try_to_extract_string_from_error_payload( values: &[ValueId], dfg: &DataFlowGraph, ) -> Option { - (error_selector.to_u64() == STRING_ERROR_SELECTOR) + ((error_selector.to_u64() == STRING_ERROR_SELECTOR) && (values.len() == 1)) .then_some(()) .and_then(|()| { + let Value::Array { array: values, .. } = &dfg[values[0]] else { + return None; + }; let fields: Option> = values .iter() .map(|value_id| { @@ -218,6 +220,7 @@ pub(crate) fn try_to_extract_string_from_error_payload( } }) .collect(); + fields }) .map(|fields| { @@ -238,15 +241,16 @@ fn display_constrain_error( ) -> Result { match error { ConstrainError::Intrinsic(assert_message_string) => { - writeln!(f, "{assert_message_string:?}") + writeln!(f, " '{assert_message_string:?}'") } ConstrainError::UserDefined(values, selector) => { - writeln!( - f, - "{}", + if let Some(constant_string) = try_to_extract_string_from_error_payload(*selector, values, &function.dfg) - .unwrap_or(value_list(function, values)) - ) + { + writeln!(f, " '{}'", constant_string) + } else { + writeln!(f, ", data {}", value_list(function, values)) + } } } } From 52d6bda35bbf7d1800ec6a0a174ba0813c83e085 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 12:21:23 +0000 Subject: [PATCH 15/25] test: fix abi type --- noir/noir-repo/tooling/noir_js_types/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/noir/noir-repo/tooling/noir_js_types/src/types.ts b/noir/noir-repo/tooling/noir_js_types/src/types.ts index 456e5a57f40c..83afeeca6ea6 100644 --- a/noir/noir-repo/tooling/noir_js_types/src/types.ts +++ b/noir/noir-repo/tooling/noir_js_types/src/types.ts @@ -27,6 +27,7 @@ export type Abi = { param_witnesses: Record; return_type: { abi_type: AbiType; visibility: Visibility } | null; return_witnesses: number[]; + error_types: Record; }; export interface VerifierBackend { From 6b9b55984fbbcff02af4d77486fa0078c4b99ea9 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 24 Apr 2024 13:03:07 +0000 Subject: [PATCH 16/25] chore: fix ts --- .../test/public_input_deflattening.test.ts | 1 + noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts | 1 + .../tooling/noirc_abi_wasm/test/shared/array_as_field.ts | 1 + .../tooling/noirc_abi_wasm/test/shared/field_as_array.ts | 1 + noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts | 1 + .../tooling/noirc_abi_wasm/test/shared/uint_overflow.ts | 1 + 6 files changed, 6 insertions(+) diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts index 079a1ad268b8..cfd43eff2506 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts @@ -55,6 +55,7 @@ const abi: Abi = { visibility: 'public', }, return_witnesses: [2, 13, 13], + error_types: {}, }; it('flattens a witness map in order of its witness indices', async () => { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts index cb80c6710baf..f4ab8175700c 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts @@ -12,6 +12,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 2 }], bar: [{ start: 2, end: 4 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts index 0cc0035fa68b..3698b913c667 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts index 6ae709459de8..4e3e2fd12a8c 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts index 6614f8f278e9..ee666e40e87c 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts @@ -54,6 +54,7 @@ export const abi: Abi = { }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts index c6e066e2bcd4..82a3e3998cae 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts @@ -11,6 +11,7 @@ export const abi: Abi = { param_witnesses: { foo: [{ start: 1, end: 2 }] }, return_type: null, return_witnesses: [], + error_types: {}, }; export const inputs: InputMap = { From 910b964cf956204efecdd6d8150381c6c2d748a3 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Thu, 25 Apr 2024 09:41:23 +0000 Subject: [PATCH 17/25] chore: address PR comments --- .../dsl/acir_format/serde/acir.hpp | 26 +++--- .../noir-repo/acvm-repo/acir/codegen/acir.cpp | 24 ++--- .../acvm-repo/acir/src/circuit/mod.rs | 2 +- .../acvm-repo/acvm/src/pwg/brillig.rs | 30 ++++--- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 87 ++++++++++--------- .../acvm-repo/acvm_js/src/execute.rs | 2 +- .../src/brillig/brillig_gen/brillig_block.rs | 4 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 4 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 8 +- .../noirc_evaluator/src/ssa/ir/printer.rs | 2 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 2 +- noir/noir-repo/noir_stdlib/src/internal.nr | 4 - noir/noir-repo/noir_stdlib/src/lib.nr | 1 - 13 files changed, 100 insertions(+), 96 deletions(-) delete mode 100644 noir/noir-repo/noir_stdlib/src/internal.nr diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index fdd6b1218b60..3e3d8d62eda3 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -1189,15 +1189,15 @@ struct AssertionPayload { static StaticString bincodeDeserialize(std::vector); }; - struct Raw { + struct Dynamic { std::tuple> value; - friend bool operator==(const Raw&, const Raw&); + friend bool operator==(const Dynamic&, const Dynamic&); std::vector bincodeSerialize() const; - static Raw bincodeDeserialize(std::vector); + static Dynamic bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const AssertionPayload&, const AssertionPayload&); std::vector bincodeSerialize() const; @@ -1394,7 +1394,7 @@ Program::AssertionPayload::StaticString serde::Deserializable AssertionPayload::Raw::bincodeSerialize() const +inline std::vector AssertionPayload::Dynamic::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } -inline AssertionPayload::Raw AssertionPayload::Raw::bincodeDeserialize(std::vector input) +inline AssertionPayload::Dynamic AssertionPayload::Dynamic::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw_or_abort("Some input bytes were not read"); } @@ -1423,18 +1423,18 @@ inline AssertionPayload::Raw AssertionPayload::Raw::bincodeDeserialize(std::vect template <> template -void serde::Serializable::serialize(const Program::AssertionPayload::Raw& obj, - Serializer& serializer) +void serde::Serializable::serialize(const Program::AssertionPayload::Dynamic& obj, + Serializer& serializer) { serde::Serializable::serialize(obj.value, serializer); } template <> template -Program::AssertionPayload::Raw serde::Deserializable::deserialize( +Program::AssertionPayload::Dynamic serde::Deserializable::deserialize( Deserializer& deserializer) { - Program::AssertionPayload::Raw obj; + Program::AssertionPayload::Dynamic obj; obj.value = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index 958f1cab4386..888c1b353ea6 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -1127,15 +1127,15 @@ namespace Program { static StaticString bincodeDeserialize(std::vector); }; - struct Raw { + struct Dynamic { std::tuple> value; - friend bool operator==(const Raw&, const Raw&); + friend bool operator==(const Dynamic&, const Dynamic&); std::vector bincodeSerialize() const; - static Raw bincodeDeserialize(std::vector); + static Dynamic bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const AssertionPayload&, const AssertionPayload&); std::vector bincodeSerialize() const; @@ -1316,20 +1316,20 @@ Program::AssertionPayload::StaticString serde::Deserializable AssertionPayload::Raw::bincodeSerialize() const { + inline std::vector AssertionPayload::Dynamic::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline AssertionPayload::Raw AssertionPayload::Raw::bincodeDeserialize(std::vector input) { + inline AssertionPayload::Dynamic AssertionPayload::Dynamic::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -1340,14 +1340,14 @@ namespace Program { template <> template -void serde::Serializable::serialize(const Program::AssertionPayload::Raw &obj, Serializer &serializer) { +void serde::Serializable::serialize(const Program::AssertionPayload::Dynamic &obj, Serializer &serializer) { serde::Serializable::serialize(obj.value, serializer); } template <> template -Program::AssertionPayload::Raw serde::Deserializable::deserialize(Deserializer &deserializer) { - Program::AssertionPayload::Raw obj; +Program::AssertionPayload::Dynamic serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::AssertionPayload::Dynamic obj; obj.value = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 91ea9c043892..9f18f7204fe0 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -85,7 +85,7 @@ pub enum ExpressionOrMemory { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AssertionPayload { StaticString(String), - Raw(/* error_selector */ u64, Vec), + Dynamic(/* error_selector */ u64, Vec), } #[derive(Debug, Copy, Clone)] diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 5ad45d8e0282..4aefc41ee94d 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -203,24 +203,23 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { None } else { let memory = self.vm.get_memory(); - let mut fields: Vec<_> = memory + let mut revert_values_iter = memory [revert_data_offset..(revert_data_offset + revert_data_size)] - .iter() - .map(|memory_value| memory_value.to_field()) - .collect(); - let error_selector = fields - .remove(0) - .try_to_u64() - .expect("Error selector doesn't fit in a u64"); + .iter(); + let error_selector = revert_values_iter + .next() + .expect("Incorrect revert data size") + .try_into() + .expect("Error selector is not u64"); match error_selector { STRING_ERROR_SELECTOR => { // If the error selector is 0, it means the error is a string - let string = fields - .iter() - .map(|field| { - let as_u8 = - field.try_to_u64().unwrap_or_default() as u8; + let string = revert_values_iter + .map(|memory_value| { + let as_u8: u8 = memory_value + .try_into() + .expect("String item is not u8"); as_u8 as char }) .collect(); @@ -228,7 +227,10 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { } _ => { // If the error selector is not 0, it means the error is a custom error - Some(ResolvedAssertionPayload::Raw(error_selector, fields)) + Some(ResolvedAssertionPayload::Raw( + error_selector, + revert_values_iter.map(|value| value.to_field()).collect(), + )) } } } diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 25f8e509cc8f..690f1f8b92aa 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -410,21 +410,22 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { &self, location: OpcodeLocation, ) -> Option { - let found_assertion_payload = self.assertion_payloads.iter().find_map(|(loc, payload)| { - if location == *loc { - Some(payload) - } else { - None - } - }); - let Some(found_assertion_payload) = found_assertion_payload else { + let Some(found_assertion_payload) = + self.assertion_payloads.iter().find_map(|(loc, payload)| { + if location == *loc { + Some(payload) + } else { + None + } + }) + else { return None; }; match found_assertion_payload { AssertionPayload::StaticString(string) => { Some(ResolvedAssertionPayload::String(string.clone())) } - AssertionPayload::Raw(error_selector, expression) => { + AssertionPayload::Dynamic(error_selector, expression) => { let mut fields = vec![]; for expr in expression { match expr { @@ -450,7 +451,11 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let string = fields .iter() .map(|field| { - let as_u8 = field.try_to_u64().unwrap_or_default() as u8; + let as_u8: u8 = field + .try_to_u64() + .expect("String character doesn't fit in u64") + .try_into() + .expect("String character doesn't fit in u8"); as_u8 as char }) .collect(); @@ -516,9 +521,9 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { unreachable!("Not executing a Brillig opcode"); }; - let witness = &mut self.witness_map; - if is_predicate_false(witness, predicate)? { - return BrilligSolver::::zero_out_brillig_outputs(witness, outputs).map(|_| None); + if is_predicate_false(&self.witness_map, predicate)? { + return BrilligSolver::::zero_out_brillig_outputs(&mut self.witness_map, outputs) + .map(|_| None); } // If we're resuming execution after resolving a foreign call then @@ -526,7 +531,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let mut solver: BrilligSolver<'_, B> = match self.brillig_solver.take() { Some(solver) => solver, None => BrilligSolver::new_call( - witness, + &self.witness_map, &self.block_solvers, inputs, &self.unconstrained_functions[*id as usize].bytecode, @@ -535,32 +540,9 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { )?, }; - let result = solver.solve().map_err(|mut err| { - match &mut err { - OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { - // Some brillig errors have static strings as payloads, we can resolve them here - let last_location = - call_stack.last().expect("Call stacks should have at least one item"); - let assertion_descriptor = - self.assertion_payloads.iter().find_map(|(loc, payload)| { - if loc == last_location { - Some(payload) - } else { - None - } - }); - - if let Some(AssertionPayload::StaticString(string)) = assertion_descriptor { - *payload = Some(ResolvedAssertionPayload::String(string.clone())); - } - - err - } - _ => err, - } - }); + let result = solver.solve().map_err(|err| self.map_brillig_error(err))?; - match result? { + match result { BrilligSolverStatus::ForeignCallWait(foreign_call) => { // Cache the current state of the solver self.brillig_solver = Some(solver); @@ -571,12 +553,37 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } BrilligSolverStatus::Finished => { // Write execution outputs - solver.finalize(witness, outputs)?; + solver.finalize(&mut self.witness_map, outputs)?; Ok(None) } } } + fn map_brillig_error(&self, mut err: OpcodeResolutionError) -> OpcodeResolutionError { + match &mut err { + OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => { + // Some brillig errors have static strings as payloads, we can resolve them here + let last_location = + call_stack.last().expect("Call stacks should have at least one item"); + let assertion_descriptor = + self.assertion_payloads.iter().find_map(|(loc, payload)| { + if loc == last_location { + Some(payload) + } else { + None + } + }); + + if let Some(AssertionPayload::StaticString(string)) = assertion_descriptor { + *payload = Some(ResolvedAssertionPayload::String(string.clone())); + } + + err + } + _ => err, + } + } + pub fn step_into_brillig_opcode(&mut self) -> StepResult<'a, B> { let Opcode::Brillig(brillig) = &self.opcodes[self.instruction_pointer] else { return StepResult::Status(self.solve_opcode()); diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 525d1e458978..0870c3f13d1d 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -168,7 +168,7 @@ async fn execute_program_with_native_type_return( let program: Program = Program::deserialize_program(&program) .map_err(|_| JsExecutionError::new( "Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), - None , + None, None))?; execute_program_with_native_program_and_return( diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 74edb2b506d5..93431cb45dc7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -259,7 +259,7 @@ impl<'block> BrilligBlock<'block> { condition, ); match assert_message { - Some(ConstrainError::UserDefined(values, typ_id)) => { + Some(ConstrainError::UserDefined(selector, values)) => { let payload_values = vecmap(values, |value| self.convert_ssa_value(*value, dfg)); let payload_as_params = vecmap(values, |value| { @@ -270,7 +270,7 @@ impl<'block> BrilligBlock<'block> { condition, payload_values, payload_as_params, - typ_id.to_u64(), + selector.to_u64(), ); } Some(ConstrainError::Intrinsic(message)) => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 936294620044..80dcb5db3288 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -620,7 +620,7 @@ impl<'a> Context<'a> { ConstrainError::Intrinsic(string) => { Some(AssertionPayload::StaticString(string.clone())) } - ConstrainError::UserDefined(values, error_selector) => { + ConstrainError::UserDefined(error_selector, values) => { if let Some(constant_string) = try_to_extract_string_from_error_payload( *error_selector, values, @@ -636,7 +636,7 @@ impl<'a> Context<'a> { let expressions_or_memory = self.acir_context.vars_to_expressions_or_memory(&acir_vars)?; - Some(AssertionPayload::Raw( + Some(AssertionPayload::Dynamic( error_selector.to_u64(), expressions_or_memory, )) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 0d2c894b3350..582e00b6be2f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -354,10 +354,10 @@ impl Instruction { let lhs = f(*lhs); let rhs = f(*rhs); let assert_message = assert_message.as_ref().map(|error| match error { - ConstrainError::UserDefined(payload_values, typ) => { + ConstrainError::UserDefined(selector, payload_values) => { ConstrainError::UserDefined( + *selector, payload_values.iter().map(|&value| f(value)).collect(), - *typ, ) } _ => error.clone(), @@ -419,7 +419,7 @@ impl Instruction { Instruction::Constrain(lhs, rhs, assert_error) => { f(*lhs); f(*rhs); - if let Some(ConstrainError::UserDefined(values, _)) = assert_error.as_ref() { + if let Some(ConstrainError::UserDefined(_, values)) = assert_error.as_ref() { values.iter().for_each(|&val| { f(val); }); @@ -632,7 +632,7 @@ pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen Intrinsic(String), // These are errors issued by the user - UserDefined(Vec, ErrorSelector), + UserDefined(ErrorSelector, Vec), } impl From for ConstrainError { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index a6537a826e28..0accdaf3462e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -243,7 +243,7 @@ fn display_constrain_error( ConstrainError::Intrinsic(assert_message_string) => { writeln!(f, " '{assert_message_string:?}'") } - ConstrainError::UserDefined(values, selector) => { + ConstrainError::UserDefined(selector, values) => { if let Some(constant_string) = try_to_extract_string_from_error_payload(*selector, values, &function.dfg) { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 1a318e4c083f..8de09d4309bc 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -717,7 +717,7 @@ impl<'a> FunctionContext<'a> { self.builder.record_error_type(error_type_id, assert_message_typ.clone()); } }; - Ok(Some(ConstrainError::UserDefined(values, error_type_id))) + Ok(Some(ConstrainError::UserDefined(error_type_id, values))) } fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { diff --git a/noir/noir-repo/noir_stdlib/src/internal.nr b/noir/noir-repo/noir_stdlib/src/internal.nr deleted file mode 100644 index 22440fe60ede..000000000000 --- a/noir/noir-repo/noir_stdlib/src/internal.nr +++ /dev/null @@ -1,4 +0,0 @@ -// This file contains functions which should only be used in calls injected by the Noir compiler. -// These functions should not be called manually in user code. -// -// Changes to this file will not be considered breaking. diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index 90c04472066f..ebde4b888588 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -26,7 +26,6 @@ mod default; mod prelude; mod uint128; mod bigint; -mod internal; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident From cf251fec62dcb3abb370b90ccfb10d8dacb7eaf0 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Thu, 25 Apr 2024 09:57:29 +0000 Subject: [PATCH 18/25] address PR comments --- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 13 ++----------- .../noirc_evaluator/src/brillig/brillig_ir.rs | 4 +--- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index 642ab44cac75..bd1562a4101f 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -410,17 +410,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { &self, location: OpcodeLocation, ) -> Option { - let Some(found_assertion_payload) = - self.assertion_payloads.iter().find_map(|(loc, payload)| { - if location == *loc { - Some(payload) - } else { - None - } - }) - else { - return None; - }; + let (_, found_assertion_payload) = + self.assertion_payloads.iter().find(|(loc, _)| location == *loc)?; match found_assertion_payload { AssertionPayload::StaticString(string) => { Some(ResolvedAssertionPayload::String(string.clone())) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 2ef56eaa1ef1..ded41e02bdae 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -270,9 +270,7 @@ pub(crate) mod tests { // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 8 }); - context.push_opcode(BrilligOpcode::Trap { - revert_data: HeapArray { pointer: MemoryAddress(0), size: 0 }, - }); + context.push_opcode(BrilligOpcode::Trap { revert_data: HeapArray::default() }); context.stop_instruction(); From 9593a3c2c1d09b36eb18a82cadb6a3b697307b9c Mon Sep 17 00:00:00 2001 From: sirasistant Date: Thu, 25 Apr 2024 12:06:39 +0000 Subject: [PATCH 19/25] address pr comments --- .../acvm-repo/acir/src/circuit/mod.rs | 11 ++++++ .../acvm-repo/acvm/src/pwg/brillig.rs | 4 +-- noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs | 8 +---- noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 35 ++++++------------- .../acvm-repo/acvm_js/src/execute.rs | 2 +- .../brillig_ir/codegen_control_flow.rs | 4 +-- .../src/ssa/function_builder/mod.rs | 2 +- noir/noir-repo/tooling/nargo/src/errors.rs | 6 ++-- noir/noir-repo/tooling/nargo/src/ops/test.rs | 2 +- .../tooling/noir_js/src/witness_generation.ts | 6 ---- 10 files changed, 32 insertions(+), 48 deletions(-) diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 9f18f7204fe0..2cdb59f1b2ac 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -4,6 +4,7 @@ pub mod directives; pub mod opcodes; use crate::native_types::{Expression, Witness}; +use acir_field::FieldElement; pub use opcodes::Opcode; use thiserror::Error; @@ -74,6 +75,10 @@ pub struct Circuit { pub recursive: bool, } +/// This selector indicates that the payload is a string. +/// This is used to parse any error with a string payload directly, +/// to avoid users having to parse the error externally to the ACVM. +/// Only non-string errors need to be parsed externally to the ACVM using the circuit ABI. pub const STRING_ERROR_SELECTOR: u64 = 0; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -88,6 +93,12 @@ pub enum AssertionPayload { Dynamic(/* error_selector */ u64, Vec), } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ResolvedAssertionPayload { + String(String), + Raw(/*error_selector:*/ u64, Vec), +} + #[derive(Debug, Copy, Clone)] /// The opcode location for a call to a separate ACIR circuit /// This includes the function index of the caller within a [program][Program] diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 7d29204656af..1186c37e3eb3 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -5,7 +5,7 @@ use acir::{ circuit::{ brillig::{Brillig, BrilligInputs, BrilligOutputs}, opcodes::BlockId, - OpcodeLocation, STRING_ERROR_SELECTOR, + OpcodeLocation, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, }, native_types::WitnessMap, FieldElement, @@ -15,7 +15,7 @@ use brillig_vm::{FailureReason, MemoryValue, VMStatus, VM}; use crate::{pwg::OpcodeNotSolvable, OpcodeResolutionError}; -use super::{get_value, insert_value, memory_op::MemoryOpSolver, ResolvedAssertionPayload}; +use super::{get_value, insert_value, memory_op::MemoryOpSolver}; #[derive(Debug)] pub enum BrilligSolverStatus { diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs index bd1562a4101f..4faaefa72205 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/mod.rs @@ -6,7 +6,7 @@ use acir::{ brillig::ForeignCallResult, circuit::{ brillig::BrilligBytecode, opcodes::BlockId, AssertionPayload, ExpressionOrMemory, Opcode, - OpcodeLocation, STRING_ERROR_SELECTOR, + OpcodeLocation, ResolvedAssertionPayload, STRING_ERROR_SELECTOR, }, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, @@ -115,12 +115,6 @@ impl std::fmt::Display for ErrorLocation { } } -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum ResolvedAssertionPayload { - String(String), - Raw(/*error_selector:*/ u64, Vec), -} - #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum OpcodeResolutionError { #[error("Cannot solve opcode: {0}")] diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index 1710728987b8..3e59f102cacd 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -110,7 +110,7 @@ fn inversion_brillig_oracle_equivalence() { &opcodes, witness_assignments, &unconstrained_functions, - Default::default(), + &[], ); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -253,7 +253,7 @@ fn double_inversion_brillig_oracle() { &opcodes, witness_assignments, &unconstrained_functions, - Default::default(), + &[], ); // use the partial witness generation solver with our acir program @@ -388,7 +388,7 @@ fn oracle_dependent_execution() { &opcodes, witness_assignments, &unconstrained_functions, - Default::default(), + &[], ); // use the partial witness generation solver with our acir program @@ -498,7 +498,7 @@ fn brillig_oracle_predicate() { &opcodes, witness_assignments, &unconstrained_functions, - Default::default(), + &[], ); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); @@ -534,13 +534,8 @@ fn unsatisfied_opcode_resolved() { let opcodes = vec![Opcode::AssertZero(opcode_a)]; let unconstrained_functions = vec![]; - let mut acvm = ACVM::new( - &StubbedBlackBoxSolver, - &opcodes, - values, - &unconstrained_functions, - Default::default(), - ); + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!( solver_status, @@ -624,13 +619,8 @@ fn unsatisfied_opcode_resolved_brillig() { let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; let unconstrained_functions = vec![]; - let mut acvm = ACVM::new( - &StubbedBlackBoxSolver, - &opcodes, - values, - &unconstrained_functions, - Default::default(), - ); + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!( solver_status, @@ -674,13 +664,8 @@ fn memory_operations() { let opcodes = vec![init, read_op, expression]; let unconstrained_functions = vec![]; - let mut acvm = ACVM::new( - &StubbedBlackBoxSolver, - &opcodes, - initial_witness, - &unconstrained_functions, - Default::default(), - ); + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions, &[]); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved); let witness_map = acvm.finalize(); diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 0870c3f13d1d..99d4b4ccb74b 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -1,7 +1,7 @@ use std::{future::Future, pin::Pin}; use acvm::acir::circuit::brillig::BrilligBytecode; -use acvm::pwg::ResolvedAssertionPayload; +use acvm::acir::circuit::ResolvedAssertionPayload; use acvm::BlackBoxFunctionSolver; use acvm::{ acir::circuit::{Circuit, Program}, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index 01d740b3f4a8..d91096463386 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -148,7 +148,7 @@ impl BrilligContext { condition: SingleAddrVariable, revert_data_items: Vec, revert_data_types: Vec, - revert_data_type: u64, + error_selector: u64, ) { assert!(condition.bit_size == 1); @@ -163,7 +163,7 @@ impl BrilligContext { let current_revert_data_pointer = ctx.allocate_register(); ctx.mov_instruction(current_revert_data_pointer, revert_data.pointer); let revert_data_id = - ctx.make_usize_constant_instruction((revert_data_type as u128).into()); + ctx.make_usize_constant_instruction((error_selector as u128).into()); ctx.store_instruction(current_revert_data_pointer, revert_data_id.address); ctx.codegen_usize_op_in_place(current_revert_data_pointer, BrilligBinaryOp::Add, 1); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 232047c6dc9a..e149769f786d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -51,7 +51,7 @@ impl FunctionBuilder { current_function: new_function, finished_functions: Vec::new(), call_stack: CallStack::new(), - error_types: Default::default(), + error_types: BTreeMap::default(), } } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index ffbd0e78425b..51408d45218f 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; use acvm::{ - acir::circuit::{OpcodeLocation, ResolvedOpcodeLocation}, - pwg::{ErrorLocation, OpcodeResolutionError, ResolvedAssertionPayload}, + acir::circuit::{OpcodeLocation, ResolvedAssertionPayload, ResolvedOpcodeLocation}, + pwg::{ErrorLocation, OpcodeResolutionError}, FieldElement, }; use noirc_abi::{Abi, AbiType, Sign}; @@ -61,7 +61,7 @@ impl NargoError { /// in tests to expected failure messages pub fn user_defined_failure_message( &self, - error_types: BTreeMap, + error_types: &BTreeMap, ) -> Option { let execution_error = match self { NargoError::ExecutionError(error) => error, diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 5f26bfa0baf9..e86f085efabb 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -107,7 +107,7 @@ fn test_status_program_compile_pass( check_expected_failure_message( test_function, - circuit_execution_err.user_defined_failure_message(abi.error_types).map(|s| s.to_string()), + circuit_execution_err.user_defined_failure_message(&abi.error_types).map(|s| s.to_string()), diagnostic, ) } diff --git a/noir/noir-repo/tooling/noir_js/src/witness_generation.ts b/noir/noir-repo/tooling/noir_js/src/witness_generation.ts index cef1d817d9b6..1f2334220615 100644 --- a/noir/noir-repo/tooling/noir_js/src/witness_generation.ts +++ b/noir/noir-repo/tooling/noir_js/src/witness_generation.ts @@ -26,12 +26,6 @@ const defaultForeignCallHandler: ForeignCallHandler = async (name: string, args: // // If a user needs to print values then they should provide a custom foreign call handler. return []; - } else if (name == 'assert_message') { - // By default we do not do anything for `assert_message` foreign calls due to a need for formatting, - // however we provide an empty response in order to not halt execution. - // - // If a user needs to use dynamic assertion messages then they should provide a custom foreign call handler. - return []; } throw Error(`Unexpected oracle during execution: ${name}(${args.join(', ')})`); }; From d3a67d6687093ea2d28d117f91155e036a45a3dc Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 08:08:38 +0000 Subject: [PATCH 20/25] address some PR comments --- .../compiler/noirc_evaluator/src/ssa/ir/printer.rs | 13 ++----------- noir/noir-repo/tooling/nargo/src/errors.rs | 9 +++------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 0accdaf3462e..bbff3cf8acb9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -209,17 +209,8 @@ pub(crate) fn try_to_extract_string_from_error_payload( let Value::Array { array: values, .. } = &dfg[values[0]] else { return None; }; - let fields: Option> = values - .iter() - .map(|value_id| { - let value = &dfg[*value_id]; - if let Value::NumericConstant { constant, .. } = value { - Some(constant) - } else { - None - } - }) - .collect(); + let fields: Option> = + values.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect(); fields }) diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 51408d45218f..43f309dc6e02 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -72,12 +72,9 @@ impl NargoError { ExecutionError::AssertionFailed(payload, _) => match payload { ResolvedAssertionPayload::String(message) => Some(message.to_string()), ResolvedAssertionPayload::Raw(error_selector, fields) => { - if let Some(abi_type) = error_types.get(error_selector) { - let decoded = prepare_for_display(fields, abi_type.clone()); - Some(decoded.to_string()) - } else { - None - } + let abi_type = error_types.get(error_selector)?; + let decoded = prepare_for_display(fields, abi_type.clone()); + Some(decoded.to_string()) } }, ExecutionError::SolvingError(error, _) => match error { From d53eac431e419e2060e147cbf112b190f05c5d80 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 08:40:24 +0000 Subject: [PATCH 21/25] refactor: remove fmtstring from ABI type --- .../compiler/noirc_driver/src/abi_gen.rs | 4 +- noir/noir-repo/tooling/nargo/src/errors.rs | 52 ++++++++++--------- noir/noir-repo/tooling/nargo/src/ops/test.rs | 2 +- noir/noir-repo/tooling/noirc_abi/src/lib.rs | 47 +++++++++-------- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 0c27c5d6971c..b1e52dc309db 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use acvm::acir::native_types::Witness; use iter_extended::{btree_map, vecmap}; -use noirc_abi::{Abi, AbiParameter, AbiReturnType, AbiType, AbiValue}; +use noirc_abi::{Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue}; use noirc_frontend::ast::Visibility; use noirc_frontend::{ hir::Context, @@ -28,7 +28,7 @@ pub(super) fn gen_abi( .map(|typ| AbiReturnType { abi_type: typ, visibility: return_visibility.into() }); let error_types = error_types .into_iter() - .map(|(loc, typ)| (loc, AbiType::from_type(context, &typ))) + .map(|(selector, typ)| (selector, AbiErrorType::from_type(context, &typ))) .collect(); Abi { parameters, return_type, param_witnesses, return_witnesses, error_types } } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 43f309dc6e02..fa3d9d6520ca 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -5,7 +5,7 @@ use acvm::{ pwg::{ErrorLocation, OpcodeResolutionError}, FieldElement, }; -use noirc_abi::{Abi, AbiType, Sign}; +use noirc_abi::{Abi, AbiErrorType, AbiType, Sign}; use noirc_errors::{ debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic, }; @@ -61,7 +61,7 @@ impl NargoError { /// in tests to expected failure messages pub fn user_defined_failure_message( &self, - error_types: &BTreeMap, + error_types: &BTreeMap, ) -> Option { let execution_error = match self { NargoError::ExecutionError(error) => error, @@ -188,34 +188,36 @@ fn map_non_fmt_to_printable_type(abi_typ: AbiType) -> PrintableType { PrintableType::UnsignedInteger { width } } AbiType::Integer { sign: Sign::Signed, width } => PrintableType::SignedInteger { width }, - AbiType::FmtString { .. } => unreachable!("Nested fmt strings are unsupported"), } } -fn prepare_for_display(fields: &[FieldElement], abi_typ: AbiType) -> PrintableValueDisplay { - if let AbiType::FmtString { length, item_types } = abi_typ { - let mut fields_iter = fields.iter().copied(); - let PrintableValue::String(string) = - decode_value(&mut fields_iter, &PrintableType::String { length }) - else { - unreachable!("Got non-string from string decoding"); - }; - let _length_of_items = fields_iter.next(); - let items = item_types.into_iter().map(|abi_type| { - let printable_typ = map_non_fmt_to_printable_type(abi_type); - let decoded = decode_value(&mut fields_iter, &printable_typ); - (decoded, printable_typ) - }); - PrintableValueDisplay::FmtString(string, items.collect()) - } else { - let printable_type = map_non_fmt_to_printable_type(abi_typ); - let decoded = decode_value(&mut fields.iter().copied(), &printable_type); - PrintableValueDisplay::Plain(decoded, printable_type) +fn prepare_for_display(fields: &[FieldElement], error_type: AbiErrorType) -> PrintableValueDisplay { + match error_type { + AbiErrorType::FmtString { length, item_types } => { + let mut fields_iter = fields.iter().copied(); + let PrintableValue::String(string) = + decode_value(&mut fields_iter, &PrintableType::String { length }) + else { + unreachable!("Got non-string from string decoding"); + }; + let _length_of_items = fields_iter.next(); + let items = item_types.into_iter().map(|abi_type| { + let printable_typ = map_non_fmt_to_printable_type(abi_type); + let decoded = decode_value(&mut fields_iter, &printable_typ); + (decoded, printable_typ) + }); + PrintableValueDisplay::FmtString(string, items.collect()) + } + AbiErrorType::Custom(abi_typ) => { + let printable_type = map_non_fmt_to_printable_type(abi_typ); + let decoded = decode_value(&mut fields.iter().copied(), &printable_type); + PrintableValueDisplay::Plain(decoded, printable_type) + } } } fn extract_message_from_error( - error_types: &BTreeMap, + error_types: &BTreeMap, nargo_err: &NargoError, ) -> String { match nargo_err { @@ -229,8 +231,8 @@ fn extract_message_from_error( ResolvedAssertionPayload::Raw(error_selector, fields), .., )) => { - if let Some(abi_type) = error_types.get(error_selector) { - format!("Assertion failed: {}", prepare_for_display(fields, abi_type.clone())) + if let Some(error_type) = error_types.get(error_selector) { + format!("Assertion failed: {}", prepare_for_display(fields, error_type.clone())) } else { "Assertion failed".to_string() } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index e86f085efabb..86dd8cd7cd54 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -107,7 +107,7 @@ fn test_status_program_compile_pass( check_expected_failure_message( test_function, - circuit_execution_err.user_defined_failure_message(&abi.error_types).map(|s| s.to_string()), + circuit_execution_err.user_defined_failure_message(&abi.error_types), diagnostic, ) } diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 5ac527507f61..a34bade75a24 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -67,10 +67,6 @@ pub enum AbiType { String { length: u64, }, - FmtString { - length: u64, - item_types: Vec, - }, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -176,14 +172,6 @@ impl AbiType { let fields = vecmap(fields, |typ| Self::from_type(context, typ)); Self::Tuple { fields } } - Type::FmtString(len, item_types) => { - let length = len.evaluate_to_u64().expect("Cannot evaluate fmt length"); - let Type::Tuple(item_types) = item_types.as_ref() else { - unreachable!("FmtString items must be a tuple") - }; - let item_types = vecmap(item_types, |typ| Self::from_type(context, typ)); - Self::FmtString { length, item_types } - } Type::Error | Type::Unit @@ -194,6 +182,7 @@ impl AbiType { | Type::Forall(..) | Type::Code | Type::Slice(_) + | Type::FmtString(_, _) | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), } @@ -211,12 +200,6 @@ impl AbiType { fields.iter().fold(0, |acc, field_typ| acc + field_typ.field_count()) } AbiType::String { length } => *length as u32, - AbiType::FmtString { length, item_types } => { - let items_size = item_types.iter().fold(0, |acc, item| acc + item.field_count()); - let string_size = *length as u32; - // Fmt strings include the number of items to be encoded as an extra field - string_size + 1 + items_size - } } } } @@ -251,7 +234,7 @@ pub struct Abi { pub param_witnesses: BTreeMap>>, pub return_type: Option, pub return_witnesses: Vec, - pub error_types: BTreeMap, + pub error_types: BTreeMap, } impl Abi { @@ -510,9 +493,6 @@ fn decode_value( InputValue::Vec(tuple_elements) } - AbiType::FmtString { .. } => { - unreachable!("FmtString is not supported in decoding") - } }; Ok(value) @@ -571,6 +551,29 @@ fn range_to_vec(ranges: &[Range]) -> Vec { result } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "error_kind", rename_all = "lowercase")] +pub enum AbiErrorType { + FmtString { length: u64, item_types: Vec }, + Custom(AbiType), +} +impl AbiErrorType { + pub fn from_type(context: &Context, typ: &Type) -> Self { + match typ { + Type::FmtString(len, item_types) => { + let length = len.evaluate_to_u64().expect("Cannot evaluate fmt length"); + let Type::Tuple(item_types) = item_types.as_ref() else { + unreachable!("FmtString items must be a tuple") + }; + let item_types = + item_types.iter().map(|typ| AbiType::from_type(context, typ)).collect(); + Self::FmtString { length, item_types } + } + _ => Self::Custom(AbiType::from_type(context, typ)), + } + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap; From da23011d1578ebeaea5067401b2166345560c740 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 08:43:03 +0000 Subject: [PATCH 22/25] test: added explicit error struct in test --- .../should_fail_with_matches/src/main.nr | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr index 0dcff0d35b1a..56ee5cfa586c 100644 --- a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr @@ -18,12 +18,15 @@ fn test_should_fail_without_runtime_match() { assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); } -#[test(should_fail_with = "PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] -fn test_should_fail_with_struct(){ - let hash = dep::std::hash::pedersen_commitment([27]); - assert_eq(hash.x, 0, hash); +struct InvalidPointError { + point: dep::std::hash::PedersenPoint, } +#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +fn test_should_fail_with_struct() { + let hash = dep::std::hash::pedersen_commitment([27]); + assert_eq(hash.x, 0, InvalidPointError { point: hash }); +} #[test(should_fail_with = "A: 0x00 is not 1!")] fn test_should_fail_with_basic_type_fmt_string() { @@ -60,13 +63,12 @@ unconstrained fn unconstrained_test_should_fail_without_runtime_match() { assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); } -#[test(should_fail_with = "PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] -unconstrained fn unconstrained_test_should_fail_with_struct(){ +#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +unconstrained fn unconstrained_test_should_fail_with_struct() { let hash = dep::std::hash::pedersen_commitment([27]); - assert_eq(hash.x, 0, hash); + assert_eq(hash.x, 0, InvalidPointError { point: hash }); } - #[test(should_fail_with = "A: 0x00 is not 1!")] unconstrained fn unconstrained_test_should_fail_with_basic_type_fmt_string() { let a = 0; From b1e5a32f2757a81dfd836faead6d436e2b6ccf8d Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 09:08:15 +0000 Subject: [PATCH 23/25] chore: fix type --- noir/noir-repo/tooling/noir_js_types/src/types.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/noir/noir-repo/tooling/noir_js_types/src/types.ts b/noir/noir-repo/tooling/noir_js_types/src/types.ts index 83afeeca6ea6..664a7c2a4579 100644 --- a/noir/noir-repo/tooling/noir_js_types/src/types.ts +++ b/noir/noir-repo/tooling/noir_js_types/src/types.ts @@ -19,6 +19,14 @@ export type AbiParameter = { visibility: Visibility; }; +export type AbiErrorType = + | { + error_kind: 'fmtstring'; + length: number; + item_types: AbiType[]; + } + | ({ error_kind: 'custom' } & AbiType); + // Map from witness index to hex string value of witness. export type WitnessMap = Map; @@ -27,7 +35,7 @@ export type Abi = { param_witnesses: Record; return_type: { abi_type: AbiType; visibility: Visibility } | null; return_witnesses: number[]; - error_types: Record; + error_types: Record; }; export interface VerifierBackend { From 45cbb2e951305e572be47c5badb49db3289763ab Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 09:14:03 +0000 Subject: [PATCH 24/25] refactor --- noir/noir-repo/tooling/noirc_abi/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index a34bade75a24..38701150ae95 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -172,7 +172,6 @@ impl AbiType { let fields = vecmap(fields, |typ| Self::from_type(context, typ)); Self::Tuple { fields } } - Type::Error | Type::Unit | Type::Constant(_) @@ -182,8 +181,8 @@ impl AbiType { | Type::Forall(..) | Type::Code | Type::Slice(_) - | Type::FmtString(_, _) | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), + Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), } } @@ -610,7 +609,7 @@ mod test { visibility: AbiVisibility::Public, }), return_witnesses: vec![Witness(3)], - error_types: Default::default(), + error_types: BTreeMap::default(), }; // Note we omit return value from inputs From 2c9053aedec4541fad6f9f7edd7bac521578cc76 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Fri, 26 Apr 2024 10:55:54 +0000 Subject: [PATCH 25/25] chore: move conversion to printable type to a From impl --- avm-transpiler/Cargo.lock | 1 + noir/noir-repo/Cargo.lock | 1 + .../acvm_js/src/js_execution_error.rs | 6 +-- noir/noir-repo/tooling/nargo/src/errors.rs | 37 ++----------------- noir/noir-repo/tooling/noirc_abi/Cargo.toml | 1 + noir/noir-repo/tooling/noirc_abi/src/lib.rs | 35 +++++++++++++++++- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index e145398e26e1..e922fb18971c 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -1202,6 +1202,7 @@ dependencies = [ "acvm", "iter-extended", "noirc_frontend", + "noirc_printable_type", "num-bigint", "num-traits", "serde", diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 8a8ccfdbf8a9..377e3339d7ef 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -3072,6 +3072,7 @@ dependencies = [ "acvm", "iter-extended", "noirc_frontend", + "noirc_printable_type", "num-bigint", "num-traits", "serde", diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs index 0f79ba513f56..ae0e0aaa2366 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/js_execution_error.rs @@ -7,7 +7,7 @@ use crate::js_witness_map::field_element_to_js_string; #[wasm_bindgen(typescript_custom_section)] const EXECUTION_ERROR: &'static str = r#" export type RawAssertionPayload = { - typeId: number; + selector: number; fields: string[]; }; export type ExecutionError = Error & { @@ -49,10 +49,10 @@ impl JsExecutionError { None => JsValue::UNDEFINED, }; let assertion_payload = match assertion_payload { - Some((type_id, fields)) => { + Some((selector, fields)) => { let raw_payload_map = Map::new(); raw_payload_map - .set(&JsValue::from_str("typeId"), &JsValue::from(type_id.to_string())); + .set(&JsValue::from_str("selector"), &JsValue::from(selector.to_string())); let js_fields = Array::new(); for field in fields { js_fields.push(&field_element_to_js_string(&field)); diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index fa3d9d6520ca..4b0c29a9b1b9 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -5,7 +5,7 @@ use acvm::{ pwg::{ErrorLocation, OpcodeResolutionError}, FieldElement, }; -use noirc_abi::{Abi, AbiErrorType, AbiType, Sign}; +use noirc_abi::{Abi, AbiErrorType}; use noirc_errors::{ debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic, }; @@ -160,37 +160,6 @@ fn extract_locations_from_error( ) } -fn map_non_fmt_to_printable_type(abi_typ: AbiType) -> PrintableType { - match abi_typ { - AbiType::Field => PrintableType::Field, - AbiType::String { length } => PrintableType::String { length }, - AbiType::Tuple { fields } => { - let fields = - fields.iter().map(|field| map_non_fmt_to_printable_type(field.clone())).collect(); - PrintableType::Tuple { types: fields } - } - AbiType::Array { length, typ } => { - let typ = map_non_fmt_to_printable_type(*typ); - PrintableType::Array { length: Some(length), typ: Box::new(typ) } - } - AbiType::Boolean => PrintableType::Boolean, - AbiType::Struct { path, fields } => { - let fields = fields - .iter() - .map(|(name, field)| (name.clone(), map_non_fmt_to_printable_type(field.clone()))) - .collect(); - PrintableType::Struct { - name: path.split("::").last().unwrap_or_default().to_string(), - fields, - } - } - AbiType::Integer { sign: Sign::Unsigned, width } => { - PrintableType::UnsignedInteger { width } - } - AbiType::Integer { sign: Sign::Signed, width } => PrintableType::SignedInteger { width }, - } -} - fn prepare_for_display(fields: &[FieldElement], error_type: AbiErrorType) -> PrintableValueDisplay { match error_type { AbiErrorType::FmtString { length, item_types } => { @@ -202,14 +171,14 @@ fn prepare_for_display(fields: &[FieldElement], error_type: AbiErrorType) -> Pri }; let _length_of_items = fields_iter.next(); let items = item_types.into_iter().map(|abi_type| { - let printable_typ = map_non_fmt_to_printable_type(abi_type); + let printable_typ = (&abi_type).into(); let decoded = decode_value(&mut fields_iter, &printable_typ); (decoded, printable_typ) }); PrintableValueDisplay::FmtString(string, items.collect()) } AbiErrorType::Custom(abi_typ) => { - let printable_type = map_non_fmt_to_printable_type(abi_typ); + let printable_type = (&abi_typ).into(); let decoded = decode_value(&mut fields.iter().copied(), &printable_type); PrintableValueDisplay::Plain(decoded, printable_type) } diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index 3258ea04c40d..040f8a0dc79a 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true acvm.workspace = true iter-extended.workspace = true noirc_frontend.workspace = true +noirc_printable_type.workspace = true toml.workspace = true serde_json = "1.0" serde.workspace = true diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 38701150ae95..48c88833bdf0 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -12,8 +12,9 @@ use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; use noirc_frontend::ast::{Signedness, Visibility}; use noirc_frontend::{hir::Context, Type, TypeBinding, TypeVariableKind}; +use noirc_printable_type::PrintableType; use serde::{Deserialize, Serialize}; -use std::ops::Range; +use std::{borrow::Borrow, ops::Range}; use std::{collections::BTreeMap, str}; // This is the ABI used to bridge the different TOML formats for the initial // witness, the partial witness generator and the interpreter. @@ -203,6 +204,38 @@ impl AbiType { } } +impl From<&AbiType> for PrintableType { + fn from(value: &AbiType) -> Self { + match value { + AbiType::Field => PrintableType::Field, + AbiType::String { length } => PrintableType::String { length: *length }, + AbiType::Tuple { fields } => { + let fields = fields.iter().map(|field| field.into()).collect(); + PrintableType::Tuple { types: fields } + } + AbiType::Array { length, typ } => { + let borrowed: &AbiType = typ.borrow(); + PrintableType::Array { length: Some(*length), typ: Box::new(borrowed.into()) } + } + AbiType::Boolean => PrintableType::Boolean, + AbiType::Struct { path, fields } => { + let fields = + fields.iter().map(|(name, field)| (name.clone(), field.into())).collect(); + PrintableType::Struct { + name: path.split("::").last().unwrap_or_default().to_string(), + fields, + } + } + AbiType::Integer { sign: Sign::Unsigned, width } => { + PrintableType::UnsignedInteger { width: *width } + } + AbiType::Integer { sign: Sign::Signed, width } => { + PrintableType::SignedInteger { width: *width } + } + } + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] /// An argument or return value of the circuit's `main` function. pub struct AbiParameter {