diff --git a/Cargo.lock b/Cargo.lock index 93f1d25fc76..1585d81d6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2931,6 +2931,7 @@ dependencies = [ "noirc_errors", "noirc_evaluator", "noirc_frontend", + "noirc_macros", "rust-embed", "serde", "tracing", @@ -2993,6 +2994,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "noirc_macros" +version = "0.23.0" +dependencies = [ + "iter-extended", + "noirc_frontend", +] + [[package]] name = "noirc_printable_type" version = "0.23.0" diff --git a/Cargo.toml b/Cargo.toml index 5dfff3dbb5d..2ba97e906b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [workspace] members = [ + # Macros crates for metaprogramming "aztec_macros", + "noirc_macros", + # Compiler crates "compiler/noirc_evaluator", "compiler/noirc_frontend", "compiler/noirc_errors", diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index b248b30b1d9..dcb09d429eb 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -38,6 +38,10 @@ pub struct Circuit { // 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, String)>, } diff --git a/acvm-repo/brillig/src/foreign_call.rs b/acvm-repo/brillig/src/foreign_call.rs index 1359d7d604d..3f124a9a0a7 100644 --- a/acvm-repo/brillig/src/foreign_call.rs +++ b/acvm-repo/brillig/src/foreign_call.rs @@ -37,7 +37,7 @@ impl ForeignCallParam { } /// Represents the full output of a [foreign call][crate::Opcode::ForeignCall]. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)] pub struct ForeignCallResult { /// Resolved output values of the foreign call. pub values: Vec, diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 0c258ab34d6..38a562e65a1 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -242,7 +242,7 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { self.registers.set(*value_index, *value); } _ => unreachable!( - "Function result size does not match brillig bytecode (expected 1 result)" + "Function result size does not match brillig bytecode. Expected 1 result but got {output:?}" ), }, RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { diff --git a/compiler/noirc_driver/Cargo.toml b/compiler/noirc_driver/Cargo.toml index eb9650e8aec..d9b240101d8 100644 --- a/compiler/noirc_driver/Cargo.toml +++ b/compiler/noirc_driver/Cargo.toml @@ -25,3 +25,4 @@ rust-embed.workspace = true tracing.workspace = true aztec_macros = { path = "../../aztec_macros" } +noirc_macros = { path = "../../noirc_macros" } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 6fd69f8b576..25d4229048e 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -76,7 +76,7 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub only_acir: bool, - /// Disables the builtin macros being used in the compiler + /// Disables the builtin Aztec macros being used in the compiler #[arg(long, hide = true)] pub disable_macros: bool, @@ -191,9 +191,12 @@ pub fn check_crate( disable_macros: bool, ) -> CompilationResult<()> { let macros: Vec<&dyn MacroProcessor> = if disable_macros { - vec![] + vec![&noirc_macros::AssertMessageMacro as &dyn MacroProcessor] } else { - vec![&aztec_macros::AztecMacro as &dyn MacroProcessor] + vec![ + &aztec_macros::AztecMacro as &dyn MacroProcessor, + &noirc_macros::AssertMessageMacro as &dyn MacroProcessor, + ] }; let mut errors = vec![]; @@ -244,6 +247,7 @@ pub fn compile_main( let compiled_program = compile_no_check(context, options, main, cached_program, options.force_compile) .map_err(FileDiagnostic::from)?; + let compilation_warnings = vecmap(compiled_program.warnings.clone(), FileDiagnostic::from); if options.deny_warnings && !compilation_warnings.is_empty() { return Err(compilation_warnings); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index b084042981b..632add74e6d 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -3,6 +3,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; +use crate::ssa::ir::instruction::ConstrainError; use crate::ssa::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -265,7 +266,30 @@ impl<'block> BrilligBlock<'block> { condition, ); - self.brillig_context.constrain_instruction(condition, assert_message.clone()); + let assert_message = if let Some(error) = assert_message { + match error.as_ref() { + ConstrainError::Static(string) => Some(string.clone()), + ConstrainError::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. + None + } + } + } else { + None + }; + + self.brillig_context.constrain_instruction(condition, assert_message); self.brillig_context.deallocate_register(condition); } Instruction::Allocate => { @@ -369,7 +393,8 @@ impl<'block> BrilligBlock<'block> { } } Value::Function(func_id) => { - self.convert_ssa_function_call(*func_id, arguments, dfg, instruction_id); + let result_ids = dfg.instruction_results(instruction_id); + self.convert_ssa_function_call(*func_id, arguments, dfg, result_ids); } Value::Intrinsic(Intrinsic::BlackBox(bb_func)) => { // Slices are represented as a tuple of (length, slice contents). @@ -638,7 +663,7 @@ impl<'block> BrilligBlock<'block> { func_id: FunctionId, arguments: &[ValueId], dfg: &DataFlowGraph, - instruction_id: InstructionId, + result_ids: &[ValueId], ) { // Convert the arguments to registers casting those to the types of the receiving function let argument_registers: Vec = arguments @@ -646,8 +671,6 @@ impl<'block> BrilligBlock<'block> { .flat_map(|argument_id| self.convert_ssa_value(*argument_id, dfg).extract_registers()) .collect(); - let result_ids = dfg.instruction_results(instruction_id); - // Create label for the function that will be called let label_of_function_to_call = FunctionContext::function_id_to_function_label(func_id); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 120c5bf25df..5d067737930 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -7,6 +7,7 @@ use std::fmt::Debug; use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; +use super::ir::instruction::ConstrainError; use super::{ ir::{ dfg::DataFlowGraph, @@ -413,15 +414,94 @@ impl Context { let lhs = self.convert_numeric_value(*lhs, dfg)?; let rhs = self.convert_numeric_value(*rhs, dfg)?; - self.acir_context.assert_eq_var(lhs, rhs, assert_message.clone())?; + let assert_message = if let Some(error) = assert_message { + match error.as_ref() { + ConstrainError::Static(string) => Some(string.clone()), + ConstrainError::Dynamic(call_instruction) => { + self.convert_ssa_call(call_instruction, dfg, ssa, brillig, &[])?; + None + } + } + } else { + None + }; + + self.acir_context.assert_eq_var(lhs, rhs, assert_message)?; } Instruction::Cast(value_id, _) => { let acir_var = self.convert_numeric_value(*value_id, dfg)?; self.define_result_var(dfg, instruction_id, acir_var); } - Instruction::Call { func, arguments } => { + Instruction::Call { .. } => { let result_ids = dfg.instruction_results(instruction_id); - match &dfg[*func] { + warnings.extend(self.convert_ssa_call( + instruction, + dfg, + ssa, + brillig, + result_ids, + )?); + } + Instruction::Not(value_id) => { + let (acir_var, typ) = match self.convert_value(*value_id, dfg) { + AcirValue::Var(acir_var, typ) => (acir_var, typ), + _ => unreachable!("NOT is only applied to numerics"), + }; + let result_acir_var = self.acir_context.not_var(acir_var, typ)?; + self.define_result_var(dfg, instruction_id, result_acir_var); + } + Instruction::Truncate { value, bit_size, max_bit_size } => { + let result_acir_var = + self.convert_ssa_truncate(*value, *bit_size, *max_bit_size, dfg)?; + self.define_result_var(dfg, instruction_id, result_acir_var); + } + Instruction::EnableSideEffects { condition } => { + let acir_var = self.convert_numeric_value(*condition, dfg)?; + self.current_side_effects_enabled_var = acir_var; + } + Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { + self.handle_array_operation(instruction_id, dfg, last_array_uses)?; + } + Instruction::Allocate => { + unreachable!("Expected all allocate instructions to be removed before acir_gen") + } + Instruction::Store { .. } => { + unreachable!("Expected all store instructions to be removed before acir_gen") + } + Instruction::Load { .. } => { + unreachable!("Expected all load instructions to be removed before acir_gen") + } + Instruction::IncrementRc { .. } => { + // Do nothing. Only Brillig needs to worry about reference counted arrays + } + Instruction::RangeCheck { value, max_bit_size, assert_message } => { + let acir_var = self.convert_numeric_value(*value, dfg)?; + self.acir_context.range_constrain_var( + acir_var, + &NumericType::Unsigned { bit_size: *max_bit_size }, + assert_message.clone(), + )?; + } + } + + self.acir_context.set_call_stack(CallStack::new()); + Ok(warnings) + } + + fn convert_ssa_call( + &mut self, + instruction: &Instruction, + dfg: &DataFlowGraph, + ssa: &Ssa, + brillig: &Brillig, + result_ids: &[ValueId], + ) -> Result, RuntimeError> { + let mut warnings = Vec::new(); + + match instruction { + Instruction::Call { func, arguments } => { + let function_value = &dfg[*func]; + match function_value { Value::Function(id) => { let func = &ssa.functions[id]; match func.runtime() { @@ -495,51 +575,11 @@ impl Context { Value::ForeignFunction(_) => unreachable!( "All `oracle` methods should be wrapped in an unconstrained fn" ), - _ => unreachable!("expected calling a function"), + _ => unreachable!("expected calling a function but got {function_value:?}"), } } - Instruction::Not(value_id) => { - let (acir_var, typ) = match self.convert_value(*value_id, dfg) { - AcirValue::Var(acir_var, typ) => (acir_var, typ), - _ => unreachable!("NOT is only applied to numerics"), - }; - let result_acir_var = self.acir_context.not_var(acir_var, typ)?; - self.define_result_var(dfg, instruction_id, result_acir_var); - } - Instruction::Truncate { value, bit_size, max_bit_size } => { - let result_acir_var = - self.convert_ssa_truncate(*value, *bit_size, *max_bit_size, dfg)?; - self.define_result_var(dfg, instruction_id, result_acir_var); - } - Instruction::EnableSideEffects { condition } => { - let acir_var = self.convert_numeric_value(*condition, dfg)?; - self.current_side_effects_enabled_var = acir_var; - } - Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { - self.handle_array_operation(instruction_id, dfg, last_array_uses)?; - } - Instruction::Allocate => { - unreachable!("Expected all allocate instructions to be removed before acir_gen") - } - Instruction::Store { .. } => { - unreachable!("Expected all store instructions to be removed before acir_gen") - } - Instruction::Load { .. } => { - unreachable!("Expected all load instructions to be removed before acir_gen") - } - Instruction::IncrementRc { .. } => { - // Do nothing. Only Brillig needs to worry about reference counted arrays - } - Instruction::RangeCheck { value, max_bit_size, assert_message } => { - let acir_var = self.convert_numeric_value(*value, dfg)?; - self.acir_context.range_constrain_var( - acir_var, - &NumericType::Unsigned { bit_size: *max_bit_size }, - assert_message.clone(), - )?; - } + _ => unreachable!("expected calling a call instruction"), } - self.acir_context.set_call_stack(CallStack::new()); Ok(warnings) } diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 44be423be10..1cac0aa67ed 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -18,7 +18,7 @@ use super::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, function::RuntimeType, - instruction::{Endian, InstructionId, Intrinsic}, + instruction::{ConstrainError, Endian, InstructionId, Intrinsic}, types::NumericType, }, ssa_gen::Ssa, @@ -250,7 +250,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/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 331a02a6974..59f88fe99eb 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -157,7 +157,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 }, @@ -326,7 +326,17 @@ impl Instruction { max_bit_size: *max_bit_size, }, Instruction::Constrain(lhs, rhs, assert_message) => { - Instruction::Constrain(f(*lhs), f(*rhs), assert_message.clone()) + // 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::Dynamic(call_instr) => { + let new_instr = call_instr.map_values(f); + Box::new(ConstrainError::Dynamic(new_instr)) + } + _ => error.clone(), + }); + Instruction::Constrain(lhs, rhs, assert_message) } Instruction::Call { func, arguments } => Instruction::Call { func: f(*func), @@ -376,9 +386,14 @@ impl Instruction { | Instruction::Load { address: value } => { f(*value); } - Instruction::Constrain(lhs, rhs, _) => { + Instruction::Constrain(lhs, rhs, assert_error) => { f(*lhs); f(*rhs); + if let Some(error) = assert_error.as_ref() { + if let ConstrainError::Dynamic(call_instr) = error.as_ref() { + call_instr.for_each_value(f); + } + } } Instruction::Store { address, value } => { @@ -441,7 +456,7 @@ impl Instruction { } } Instruction::Constrain(lhs, rhs, msg) => { - let constraints = decompose_constrain(*lhs, *rhs, msg.clone(), dfg); + let constraints = decompose_constrain(*lhs, *rhs, msg, dfg); if constraints.is_empty() { Remove } else { @@ -551,6 +566,28 @@ impl Instruction { } } +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub(crate) enum ConstrainError { + // These are errors which have been hardcoded during SSA gen + 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), +} + +impl From for ConstrainError { + fn from(value: String) -> Self { + ConstrainError::Static(value) + } +} + +impl From for Box { + fn from(value: String) -> Self { + Box::new(value.into()) + } +} + /// The possible return values for Instruction::return_types pub(crate) enum InstructionResultType { /// The result type of this instruction matches that of this operand diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs index 7fb0970c834..0ae0f8f2892 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs @@ -1,13 +1,13 @@ use acvm::FieldElement; -use super::{Binary, BinaryOp, DataFlowGraph, Instruction, Type, Value, ValueId}; +use super::{Binary, BinaryOp, ConstrainError, DataFlowGraph, Instruction, Type, Value, ValueId}; /// Try to decompose this constrain instruction. This constraint will be broken down such that it instead constrains /// all the values which are used to compute the values which were being constrained. pub(super) fn decompose_constrain( lhs: ValueId, rhs: ValueId, - msg: Option, + msg: &Option>, dfg: &mut DataFlowGraph, ) -> Vec { let lhs = dfg.resolve(lhs); @@ -39,7 +39,7 @@ pub(super) fn decompose_constrain( // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it // will likely be removed through dead instruction elimination. - vec![Instruction::Constrain(lhs, rhs, msg)] + vec![Instruction::Constrain(lhs, rhs, msg.clone())] } Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Mul }) @@ -64,7 +64,7 @@ pub(super) fn decompose_constrain( let one = dfg.make_constant(one, Type::bool()); [ - decompose_constrain(lhs, one, msg.clone(), dfg), + decompose_constrain(lhs, one, msg, dfg), decompose_constrain(rhs, one, msg, dfg), ] .concat() @@ -92,7 +92,7 @@ pub(super) fn decompose_constrain( let zero = dfg.make_constant(zero, dfg.type_of_value(lhs)); [ - decompose_constrain(lhs, zero, msg.clone(), dfg), + decompose_constrain(lhs, zero, msg, dfg), decompose_constrain(rhs, zero, msg, dfg), ] .concat() @@ -116,11 +116,11 @@ pub(super) fn decompose_constrain( decompose_constrain(value, reversed_constant, msg, dfg) } - _ => vec![Instruction::Constrain(lhs, rhs, msg)], + _ => vec![Instruction::Constrain(lhs, rhs, msg.clone())], } } - _ => vec![Instruction::Constrain(lhs, rhs, msg)], + _ => vec![Instruction::Constrain(lhs, rhs, msg.clone())], } } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 2899b987c1d..9bd43fab1ff 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -9,7 +9,7 @@ use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, function::Function, - instruction::{Instruction, InstructionId, TerminatorInstruction}, + instruction::{ConstrainError, Instruction, InstructionId, TerminatorInstruction}, value::ValueId, }; @@ -133,9 +133,17 @@ pub(crate) fn display_instruction( write!(f, "{} = ", value_list(function, results))?; } + display_instruction_inner(function, &function.dfg[instruction], f) +} + +fn display_instruction_inner( + function: &Function, + instruction: &Instruction, + f: &mut Formatter, +) -> Result { let show = |id| value(function, id); - match &function.dfg[instruction] { + match instruction { Instruction::Binary(binary) => { writeln!(f, "{} {}, {}", binary.operator, show(binary.lhs), show(binary.rhs)) } @@ -145,10 +153,15 @@ pub(crate) fn display_instruction( let value = show(*value); writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}",) } - Instruction::Constrain(lhs, rhs, message) => match message { - Some(message) => writeln!(f, "constrain {} == {} '{message}'", show(*lhs), show(*rhs)), - None => writeln!(f, "constrain {} == {}", show(*lhs), show(*rhs)), - }, + 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) + } + } Instruction::Call { func, arguments } => { writeln!(f, "call {}({})", show(*func), value_list(function, arguments)) } @@ -180,3 +193,18 @@ pub(crate) fn display_instruction( } } } + +fn display_constrain_error( + function: &Function, + error: &ConstrainError, + f: &mut Formatter, +) -> Result { + match error { + ConstrainError::Static(assert_message_string) => { + writeln!(f, "{assert_message_string:?}") + } + ConstrainError::Dynamic(assert_message_call) => { + display_instruction_inner(function, assert_message_call, f) + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs index 8a903cbd87b..b737c51d145 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs @@ -109,11 +109,11 @@ mod test { let v1 = builder.insert_binary(v0, BinaryOp::Add, one); let v2 = builder.insert_binary(v1, BinaryOp::Add, one); - builder.insert_constrain(v0, one, Some("With message".to_string())); + builder.insert_constrain(v0, one, Some("With message".to_string().into())); builder.insert_constrain(v2, three, None); builder.insert_constrain(v0, one, None); builder.insert_constrain(v1, two, None); - builder.insert_constrain(v1, two, Some("With message".to_string())); + builder.insert_constrain(v1, two, Some("With message".to_string().into())); builder.terminate_with_return(vec![]); let ssa = builder.finish(); @@ -137,11 +137,11 @@ mod test { assert_eq!(block.instructions().len(), 7); let expected_instructions = vec![ - Instruction::Constrain(v0, one, Some("With message".to_string())), + Instruction::Constrain(v0, one, Some("With message".to_string().into())), Instruction::Constrain(v0, one, None), Instruction::Binary(Binary { lhs: v0, rhs: one, operator: BinaryOp::Add }), Instruction::Constrain(v1, two, None), - Instruction::Constrain(v1, two, Some("With message".to_string())), + Instruction::Constrain(v1, two, Some("With message".to_string().into())), Instruction::Binary(Binary { lhs: v1, rhs: one, operator: BinaryOp::Add }), Instruction::Constrain(v2, three, None), ]; diff --git a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index b7f154397a6..1f09a132132 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -14,7 +14,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, function::{Function, FunctionId, RuntimeType, Signature}, - instruction::{BinaryOp, Instruction}, + instruction::{BinaryOp, ConstrainError, Instruction}, types::{NumericType, Type}, value::{Value, ValueId}, }, @@ -86,9 +86,22 @@ impl DefunctionalizationContext { let instruction = func.dfg[instruction_id].clone(); let mut replacement_instruction = None; // Operate on call instructions - let (target_func_id, mut arguments) = match instruction { + let (target_func_id, arguments) = match &instruction { Instruction::Call { func: target_func_id, arguments } => { - (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::Dynamic(Instruction::Call { + func: target_func_id, + arguments, + }) = constrain_error.as_ref() + { + (*target_func_id, arguments) + } else { + continue; + } } _ => continue, }; @@ -96,6 +109,7 @@ impl DefunctionalizationContext { match func.dfg[target_func_id] { // If the target is a function used as value Value::Param { .. } | Value::Instruction { .. } => { + let mut arguments = arguments.clone(); let results = func.dfg.instruction_results(instruction_id); let signature = Signature { params: vecmap(&arguments, |param| func.dfg.type_of_value(*param)), @@ -120,7 +134,20 @@ impl DefunctionalizationContext { } _ => {} } - if let Some(new_instruction) = replacement_instruction { + 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::Dynamic(_) => { + Some(Box::new(ConstrainError::Dynamic(new_instruction))) + } + _ => None, + } + } else { + None + }; + new_instruction = Instruction::Constrain(lhs, rhs, new_error_call); + } func.dfg[instruction_id] = new_instruction; } } diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 0e155776545..adf68bacba4 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -435,7 +435,7 @@ impl<'a> FunctionContext<'a> { self.builder.set_location(location).insert_constrain( sign, one, - Some("attempt to bit-shift with overflow".to_string()), + Some("attempt to bit-shift with overflow".to_owned().into()), ); } @@ -446,7 +446,7 @@ impl<'a> FunctionContext<'a> { self.builder.set_location(location).insert_constrain( overflow, one, - Some("attempt to bit-shift with overflow".to_owned()), + Some("attempt to bit-shift with overflow".to_owned().into()), ); self.builder.insert_truncate(result, bit_size, bit_size + 1) } @@ -498,8 +498,11 @@ impl<'a> FunctionContext<'a> { let sign_diff = self.builder.insert_binary(result_sign, BinaryOp::Eq, lhs_sign); let sign_diff_with_predicate = self.builder.insert_binary(sign_diff, BinaryOp::Mul, same_sign); - let overflow_check = - Instruction::Constrain(sign_diff_with_predicate, same_sign, Some(message)); + let overflow_check = Instruction::Constrain( + sign_diff_with_predicate, + same_sign, + Some(message.into()), + ); self.builder.set_location(location).insert_instruction(overflow_check, None); } BinaryOpKind::Multiply => { @@ -509,11 +512,10 @@ impl<'a> FunctionContext<'a> { let rhs_abs = self.absolute_value_helper(rhs, rhs_sign, bit_size); let product_field = self.builder.insert_binary(lhs_abs, BinaryOp::Mul, rhs_abs); // It must not already overflow the bit_size - let message = "attempt to multiply with overflow".to_string(); self.builder.set_location(location).insert_range_check( product_field, bit_size, - Some(message.clone()), + Some("attempt to multiply with overflow".to_string()), ); let product = self.builder.insert_cast(product_field, Type::unsigned(bit_size)); @@ -530,7 +532,7 @@ impl<'a> FunctionContext<'a> { self.builder.set_location(location).insert_constrain( product_overflow_check, one, - Some(message), + Some(message.into()), ); } _ => unreachable!("operator {} should not overflow", operator), diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 9d9635fed34..0efc73fb85b 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -13,7 +13,7 @@ use noirc_frontend::{ }; use crate::{ - errors::RuntimeError, + errors::{InternalError, RuntimeError}, ssa::{ function_builder::data_bus::DataBusBuilder, ir::{instruction::Intrinsic, types::NumericType}, @@ -29,7 +29,7 @@ use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{BinaryOp, TerminatorInstruction}, + instruction::{BinaryOp, ConstrainError, Instruction, TerminatorInstruction}, types::Type, value::ValueId, }, @@ -141,7 +141,7 @@ 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.clone()) + self.codegen_constrain(expr, *location, assert_message) } Expression::Assign(assign) => self.codegen_assign(assign), Expression::Semi(semi) => self.codegen_semi(semi), @@ -438,10 +438,11 @@ impl<'a> FunctionContext<'a> { let is_offset_out_of_bounds = self.builder.insert_binary(index, BinaryOp::Lt, array_len); let true_const = self.builder.numeric_constant(true, Type::bool()); + self.builder.insert_constrain( is_offset_out_of_bounds, true_const, - Some("Index out of bounds".to_owned()), + Some(Box::new("Index out of bounds".to_owned().into())), ); } @@ -665,15 +666,59 @@ impl<'a> FunctionContext<'a> { &mut self, expr: &Expression, location: Location, - assert_message: Option, + assert_message: &Option>, ) -> Result { let expr = self.codegen_non_tuple_expression(expr)?; let true_literal = self.builder.numeric_constant(true, Type::bool()); - self.builder.set_location(location).insert_constrain(expr, true_literal, assert_message); + + // 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)?; + + self.builder.insert_constrain(expr, true_literal, assert_message); Ok(Self::unit_value()) } + // This method does not necessary codegen the full assert message expression, thus it does not + // return a `Values` object. Instead we check the internals of the expression to make sure + // we have an `Expression::Call` as expected. An `Instruction::Call` is then constructed but not + // 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) + }; + + 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::Dynamic(instr)))) + } + fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { let lhs = self.extract_current_value(&assign.lvalue)?; let rhs = self.codegen_expression(&assign.expression)?; diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 73b1f68778d..a2c29a8c52e 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -416,7 +416,7 @@ pub enum LValue { } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct ConstrainStatement(pub Expression, pub Option, pub ConstrainKind); +pub struct ConstrainStatement(pub Expression, pub Option, pub ConstrainKind); #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ConstrainKind { diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 8243b684c8a..7b876244601 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1133,9 +1133,16 @@ 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 = constrain_stmt.1; - HirStatement::Constrain(HirConstrainStatement(expr_id, self.file, assert_message)) + + HirStatement::Constrain(HirConstrainStatement( + expr_id, + self.file, + assert_msg_call_expr_id, + )) } StatementKind::Expression(expr) => { HirStatement::Expression(self.resolve_expression(expr)) @@ -1184,6 +1191,41 @@ impl<'a> Resolver<'a> { } } + fn resolve_assert_message( + &mut self, + assert_message_expr: Option, + span: Span, + condition: Expression, + ) -> Option { + let mut assert_msg_call_args = if let Some(assert_message_expr) = assert_message_expr { + vec![assert_message_expr.clone()] + } else { + return None; + }; + assert_msg_call_args.push(condition); + + 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("resolve_assert_message")], + kind: PathKind::Crate, + span, + }) + } else { + ExpressionKind::Variable(Path { + segments: vec![Ident::from("std"), Ident::from("resolve_assert_message")], + kind: PathKind::Dep, + span, + }) + }; + 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: StatementKind) -> StmtId { let hir_stmt = self.resolve_stmt(stmt); self.interner.push_stmt(hir_stmt) diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index fd8ae62d34e..a38a16a6580 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -303,6 +303,9 @@ impl<'interner> TypeChecker<'interner> { let expr_type = self.check_expression(&stmt.0); let expr_span = self.interner.expr_span(&stmt.0); + // Must type check the assertion message expression so that we instantiate bindings + stmt.2.map(|assert_msg_expr| self.check_expression(&assert_msg_expr)); + self.unify(&expr_type, &Type::Bool, || TypeCheckError::TypeMismatch { expr_typ: expr_type.to_string(), expected_typ: Type::Bool.to_string(), diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index 34c9302c251..4d946690151 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -55,7 +55,7 @@ pub struct HirAssignStatement { /// originates from. This is used later in the SSA pass to issue /// an error if a constrain is found to be always false. #[derive(Debug, Clone)] -pub struct HirConstrainStatement(pub ExprId, pub FileId, pub Option); +pub struct HirConstrainStatement(pub ExprId, pub FileId, pub Option); #[derive(Debug, Clone, Hash)] pub enum HirPattern { diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index b6d4c568334..b14cb632f81 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -48,7 +48,7 @@ pub mod macros_api { pub use crate::hir_def::expr::{HirExpression, HirLiteral}; pub use crate::hir_def::stmt::HirStatement; pub use crate::node_interner::{NodeInterner, StructId}; - pub use crate::parser::SortedModule; + pub use crate::parser::{parse_program, SortedModule}; pub use crate::token::SecondaryAttribute; pub use crate::hir::def_map::ModuleDefId; diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 42a618e7d77..5172eabde46 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -31,7 +31,7 @@ pub enum Expression { ExtractTupleField(Box, usize), Call(Call), Let(Let), - Constrain(Box, Location, Option), + Constrain(Box, Location, Option>), Assign(Assign), Semi(Box), } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 67b246a02ce..663731d462c 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -479,7 +479,9 @@ impl<'interner> Monomorphizer<'interner> { HirStatement::Constrain(constrain) => { let expr = self.expr(constrain.0); let location = self.interner.expr_location(&constrain.0); - ast::Expression::Constrain(Box::new(expr), location, constrain.2) + let assert_message = + constrain.2.map(|assert_msg_expr| Box::new(self.expr(assert_msg_expr))); + ast::Expression::Constrain(Box::new(expr), location, assert_message) } HirStatement::Assign(assign) => self.assign(assign), HirStatement::For(for_loop) => { @@ -929,6 +931,9 @@ 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 mesage to an `assert` or `assert_eq` statement + self.append_printable_type_info(&hir_arguments[0], &mut arguments); } } } @@ -1024,7 +1029,7 @@ impl<'interner> Monomorphizer<'interner> { // The caller needs information as to whether it is handling a format string or a single type arguments.push(ast::Expression::Literal(ast::Literal::Bool(is_fmt_str))); } - _ => unreachable!("logging expr {:?} is not supported", arguments[0]), + _ => unreachable!("logging expr {:?} is not supported", hir_argument), } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index f82ce95c718..1bdb276d4fb 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -832,23 +832,10 @@ where ignore_then_commit(keyword(Keyword::Assert), parenthesized(argument_parser)) .labelled(ParsingRuleLabel::Statement) - .validate(|expressions, span, emit| { + .validate(|expressions, span, _| { let condition = expressions.get(0).unwrap_or(&Expression::error(span)).clone(); - let mut message_str = None; - - if let Some(message) = expressions.get(1) { - if let ExpressionKind::Literal(Literal::Str(message)) = &message.kind { - message_str = Some(message.clone()); - } else { - emit(ParserError::with_reason(ParserErrorReason::AssertMessageNotString, span)); - } - } - - StatementKind::Constrain(ConstrainStatement( - condition, - message_str, - ConstrainKind::Assert, - )) + let message = expressions.get(1).cloned(); + StatementKind::Constrain(ConstrainStatement(condition, message, ConstrainKind::Assert)) }) } @@ -861,7 +848,7 @@ where ignore_then_commit(keyword(Keyword::AssertEq), parenthesized(argument_parser)) .labelled(ParsingRuleLabel::Statement) - .validate(|exprs: Vec, span, emit| { + .validate(|exprs: Vec, span, _| { let predicate = Expression::new( ExpressionKind::Infix(Box::new(InfixExpression { lhs: exprs.get(0).unwrap_or(&Expression::error(span)).clone(), @@ -870,18 +857,10 @@ where })), span, ); - let mut message_str = None; - - if let Some(message) = exprs.get(2) { - if let ExpressionKind::Literal(Literal::Str(message)) = &message.kind { - message_str = Some(message.clone()); - } else { - emit(ParserError::with_reason(ParserErrorReason::AssertMessageNotString, span)); - } - } + let message = exprs.get(2).cloned(); StatementKind::Constrain(ConstrainStatement( predicate, - message_str, + message, ConstrainKind::AssertEq, )) }) @@ -2092,7 +2071,13 @@ mod test { match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() { StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - assert_eq!(message, Some("assertion message".to_owned())); + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } } _ => unreachable!(), } @@ -2116,7 +2101,13 @@ mod test { .unwrap() { StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - assert_eq!(message, Some("assertion message".to_owned())); + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } } _ => unreachable!(), } @@ -2483,7 +2474,7 @@ mod test { Case { source: "assert(x == x, x)", expect: "constrain (plain::x == plain::x)", - errors: 1, + errors: 0, }, Case { source: "assert_eq(x,)", expect: "constrain (Error == Error)", errors: 1 }, Case { @@ -2494,7 +2485,7 @@ mod test { Case { source: "assert_eq(x, x, x)", expect: "constrain (plain::x == plain::x)", - errors: 1, + errors: 0, }, ]; diff --git a/compiler/noirc_printable_type/src/lib.rs b/compiler/noirc_printable_type/src/lib.rs index 18f2fe0a873..eb206f0af3d 100644 --- a/compiler/noirc_printable_type/src/lib.rs +++ b/compiler/noirc_printable_type/src/lib.rs @@ -69,6 +69,9 @@ pub enum ForeignCallError { #[error("Failed calling external resolver. {0}")] ExternalResolverError(#[from] jsonrpc::Error), + + #[error("Assert message resolved after an unsatisified constrain. {0}")] + ResolvedAssertMessage(String), } impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { diff --git a/docs/docs/noir/concepts/assert.md b/docs/docs/noir/concepts/assert.md index c5f9aff139c..bcff613a695 100644 --- a/docs/docs/noir/concepts/assert.md +++ b/docs/docs/noir/concepts/assert.md @@ -18,10 +18,28 @@ fn main(x : Field, y : Field) { } ``` +> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. + You can optionally provide a message to be logged when the assertion fails: ```rust assert(x == y, "x and y are not equal"); ``` -> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. +Aside string literals, the optional message can be a format string or any other type supported as input for Noir's [print](../standard_library/logging.md) functions. This feature lets you incorporate runtime variables into your failed assertion logs: + +```rust +assert(x == y, f"Expected x == y, but got {x} == {y}"); +``` + +Using a variable as an assertion message directly: + +```rust +struct myStruct { + myField: Field +} + +let s = myStruct { myField: y }; +assert(s.myField == x, s); +``` + diff --git a/noirc_macros/Cargo.toml b/noirc_macros/Cargo.toml new file mode 100644 index 00000000000..699e6b01cae --- /dev/null +++ b/noirc_macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "noirc_macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +noirc_frontend.workspace = true +iter-extended.workspace = true \ No newline at end of file diff --git a/noirc_macros/src/lib.rs b/noirc_macros/src/lib.rs new file mode 100644 index 00000000000..4337214d69f --- /dev/null +++ b/noirc_macros/src/lib.rs @@ -0,0 +1,61 @@ +use noirc_frontend::macros_api::parse_program; +use noirc_frontend::macros_api::HirContext; +use noirc_frontend::macros_api::SortedModule; +use noirc_frontend::macros_api::{CrateId, FileId}; +use noirc_frontend::macros_api::{MacroError, MacroProcessor}; + +pub struct AssertMessageMacro; + +impl MacroProcessor for AssertMessageMacro { + fn process_untyped_ast( + &self, + ast: SortedModule, + crate_id: &CrateId, + _context: &HirContext, + ) -> Result { + transform(ast, crate_id) + } + + // This macro does not need to process any information after name resolution + fn process_typed_ast( + &self, + _crate_id: &CrateId, + _context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)> { + Ok(()) + } +} + +fn transform(ast: SortedModule, crate_id: &CrateId) -> Result { + let ast = add_resolve_assert_message_funcs(ast, crate_id)?; + + Ok(ast) +} + +fn add_resolve_assert_message_funcs( + mut ast: SortedModule, + crate_id: &CrateId, +) -> Result { + if !crate_id.is_stdlib() { + return Ok(ast); + } + let assert_message_oracles = " + #[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); + } + }"; + + let (assert_msg_funcs_ast, errors) = parse_program(assert_message_oracles); + assert_eq!(errors.len(), 0, "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code"); + + let assert_msg_funcs_ast = assert_msg_funcs_ast.into_sorted(); + + for func in assert_msg_funcs_ast.functions { + ast.functions.push(func) + } + + Ok(ast) +} diff --git a/test_programs/compile_failure/assert_msg_runtime/Nargo.toml b/test_programs/compile_failure/assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..765f632ff74 --- /dev/null +++ b/test_programs/compile_failure/assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_msg_runtime/src/main.nr b/test_programs/compile_failure/assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..bec3082550a --- /dev/null +++ b/test_programs/compile_failure/assert_msg_runtime/src/main.nr @@ -0,0 +1,7 @@ +fn main(x: Field, y: pub Field) { + assert(x != y, f"Expected x != y, but got both equal {x}"); + assert(x != y); + let z = x + y; + assert(z != y, f"Expected z != y, but got both equal {z}"); + assert_eq(x, y, f"Expected x == y, but x is {x} and y is {y}"); +} \ No newline at end of file diff --git a/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml b/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..00f97b7273a --- /dev/null +++ b/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "brillig_assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr b/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..428b2006363 --- /dev/null +++ b/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr @@ -0,0 +1,10 @@ +fn main(x: Field) { + assert(1 == conditional(x)); +} + +unconstrained fn conditional(x: Field) -> Field { + let z = x as u8 + 20; + assert_eq(z, 25, f"Expected 25 but got {z}"); + assert(x == 10, f"Expected x to equal 10, but got {x}"); + 1 +} \ No newline at end of file diff --git a/test_programs/compile_success_empty/method_call_regression/Nargo.toml b/test_programs/compile_success_empty/method_call_regression/Nargo.toml index 92c9b942008..09f95590aad 100644 --- a/test_programs/compile_success_empty/method_call_regression/Nargo.toml +++ b/test_programs/compile_success_empty/method_call_regression/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "short" +name = "method_call_regression" type = "bin" authors = [""] compiler_version = ">=0.19.4" diff --git a/test_programs/compile_success_empty/trait_static_methods/Nargo.toml b/test_programs/compile_success_empty/trait_static_methods/Nargo.toml index 71c541ccd4f..ea30031b9a5 100644 --- a/test_programs/compile_success_empty/trait_static_methods/Nargo.toml +++ b/test_programs/compile_success_empty/trait_static_methods/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "trait_self" +name = "trait_static_methods" type = "bin" authors = [""] diff --git a/test_programs/execution_success/brillig_assert/src/main.nr b/test_programs/execution_success/brillig_assert/src/main.nr index 91e4cebd9d3..16fe7b29061 100644 --- a/test_programs/execution_success/brillig_assert/src/main.nr +++ b/test_programs/execution_success/brillig_assert/src/main.nr @@ -6,7 +6,7 @@ fn main(x: Field) { } unconstrained fn conditional(x: bool) -> Field { - assert(x, "x is false"); - assert_eq(x, true, "x is false"); + assert(x, f"Expected x to be false but got {x}"); + assert_eq(x, true, f"Expected x to be false but got {x}"); 1 } diff --git a/test_programs/execution_success/debug_logs/src/main.nr b/test_programs/execution_success/debug_logs/src/main.nr index 52c910065c1..b640e4f5f0c 100644 --- a/test_programs/execution_success/debug_logs/src/main.nr +++ b/test_programs/execution_success/debug_logs/src/main.nr @@ -3,8 +3,15 @@ use dep::std; fn main(x: Field, y: pub Field) { let string = "i: {i}, j: {j}"; std::println(string); + + // TODO: fmtstr cannot be printed + // let fmt_str: fmtstr<14, (Field, Field)> = f"i: {x}, j: {y}"; + // let fmt_fmt_str = f"fmtstr: {fmt_str}, i: {x}"; + // std::println(fmt_fmt_str); + // A `fmtstr` lets you easily perform string interpolation. let fmt_str: fmtstr<14, (Field, Field)> = f"i: {x}, j: {y}"; + let fmt_str = string_identity(fmt_str); std::println(fmt_str); diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 12b55708b15..ab807c65a46 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -1,5 +1,6 @@ use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; +use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::brillig_vm::{brillig::Value, Registers}; use acvm::pwg::{ ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM, @@ -224,6 +225,9 @@ 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/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 4fc7f7b599f..370393fea09 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -1,3 +1,4 @@ +use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -5,7 +6,7 @@ use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use crate::errors::ExecutionError; use crate::NargoError; -use super::foreign_calls::ForeignCallExecutor; +use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; #[tracing::instrument(level = "trace", skip_all)] pub fn execute_circuit( @@ -16,6 +17,8 @@ pub fn execute_circuit( ) -> Result { let mut acvm = ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness); + // 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(); @@ -37,7 +40,13 @@ pub fn execute_circuit( return Err(NargoError::ExecutionError(match call_stack { Some(call_stack) => { - if let Some(assert_message) = circuit.get_assert_message( + // 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 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 Some(assert_message) = circuit.get_assert_message( *call_stack.last().expect("Call stacks should not be empty"), ) { ExecutionError::AssertionFailed(assert_message.to_owned(), call_stack) @@ -50,7 +59,19 @@ pub fn execute_circuit( } ACVMStatus::RequiresForeignCall(foreign_call) => { let foreign_call_result = foreign_call_executor.execute(&foreign_call)?; - acvm.resolve_pending_foreign_call(foreign_call_result); + match foreign_call_result { + NargoForeignCallResult::BrilligOutput(foreign_call_result) => { + 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()); + } + } } } } diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index e3a3174f8dc..f600b3047fc 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -9,13 +9,69 @@ pub trait ForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result; + ) -> 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: Value) -> 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() + } } /// 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(crate) enum ForeignCall { Print, + AssertMessage, CreateMock, SetMockParams, SetMockReturns, @@ -33,6 +89,7 @@ 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::SetMockReturns => "set_mock_returns", @@ -44,6 +101,7 @@ 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), "set_mock_returns" => Some(ForeignCall::SetMockReturns), @@ -134,29 +192,49 @@ impl DefaultForeignCallExecutor { fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { let skip_newline = foreign_call_inputs[0].unwrap_value().is_zero(); - let display_values: PrintableValueDisplay = foreign_call_inputs - .split_first() - .ok_or(ForeignCallError::MissingForeignCallInputs)? - .1 - .try_into()?; - print!("{display_values}{}", if skip_newline { "" } else { "\n" }); + + let foreign_call_inputs = + foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?.1; + let display_string = Self::format_printable_value(foreign_call_inputs, skip_newline)?; + + print!("{display_string}"); + 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, + ) -> Result { + let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; + + let result = format!("{display_values}{}", if skip_newline { "" } else { "\n" }); + + Ok(result) + } } 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 { values: vec![] }) + Ok(ForeignCallResult::default().into()) } + 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()); @@ -164,7 +242,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { self.mocked_responses.push(MockedCall::new(id, mock_oracle_name)); self.last_mock_id += 1; - Ok(ForeignCallResult { values: vec![Value::from(id).into()] }) + Ok(Value::from(id).into()) } Some(ForeignCall::SetMockParams) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -172,7 +250,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .params = Some(params.to_vec()); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::SetMockReturns) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -180,7 +258,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .result = ForeignCallResult { values: params.to_vec() }; - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::SetMockTimes) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -194,12 +272,12 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .times_left = Some(times); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::ClearMock) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; self.mocked_responses.retain(|response| response.id != id); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } None => { let mock_response_position = self @@ -222,7 +300,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { } } - Ok(ForeignCallResult { values: result }) + Ok(result.into()) } (None, Some(external_resolver)) => { let encoded_params: Vec<_> = @@ -235,7 +313,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { let parsed_response: ForeignCallResult = response.result()?; - Ok(parsed_response) + Ok(parsed_response.into()) } (None, None) => panic!("Unknown foreign call {}", foreign_call_name), } @@ -312,7 +390,7 @@ mod tests { }; let result = executor.execute(&foreign_call); - assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }); + assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }.into()); server.close(); } diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs index 800a8656ef3..cfe5acb21a7 100644 --- a/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -38,8 +38,7 @@ impl super::FmtVisitor<'_> { nested_shape.indent.block_indent(self.config); - let message = - message.map_or(String::new(), |message| format!(", \"{message}\"")); + let message = message.map_or(String::new(), |message| format!(", {message}")); let (callee, args) = match kind { ConstrainKind::Assert => { diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index 1f233422061..cef1d817d9b 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -26,6 +26,12 @@ 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(', ')})`); }; diff --git a/tooling/noir_js/test/node/execute.test.ts b/tooling/noir_js/test/node/execute.test.ts index bfaf80882ab..491bcb0dfc4 100644 --- a/tooling/noir_js/test/node/execute.test.ts +++ b/tooling/noir_js/test/node/execute.test.ts @@ -1,9 +1,11 @@ import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; +import assert_msg_json from '../noir_compiled_examples/assert_msg_runtime/target/assert_msg_runtime.json' assert { type: 'json' }; import { Noir } from '@noir-lang/noir_js'; import { CompiledCircuit } from '@noir-lang/types'; import { expect } from 'chai'; const assert_lt_program = assert_lt_json as CompiledCircuit; +const assert_msg_runtime = assert_msg_json as CompiledCircuit; it('returns the return value of the circuit', async () => { const inputs = { @@ -14,3 +16,16 @@ it('returns the return value of the circuit', async () => { expect(returnValue).to.be.eq('0x05'); }); + +it('circuit with a dynamic assert message should fail on an assert failure not the foreign call handler', async () => { + const inputs = { + x: '10', + y: '5', + }; + try { + await new Noir(assert_msg_runtime).execute(inputs); + } catch (error) { + const knownError = error as Error; + expect(knownError.message).to.equal('Circuit execution failed: Error: Cannot satisfy constraint'); + } +}); diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr index 693e7285736..a9aaae5f2f7 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr +++ b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr @@ -4,6 +4,10 @@ fn main(x: u64, y: pub u64) -> pub u64 { // We include a println statement to show that noirJS will ignore this and continue execution std::println("foo"); + // A dynamic assertion message is used to show that noirJS will ignore the call and continue execution + // The assertion passes and thus the foreign call for resolving an assertion message should not be called. + assert(x < y, f"Expected x < y but got {x} < {y}"); + assert(x < y); x + y } diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json b/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json index 5b511cdc140..027df0b39d9 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json +++ b/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json @@ -1 +1 @@ -{"noir_version":"0.20.0+010fdb69616f47fc0a9f252a65a903316d3cbe80","hash":17538653710107541030,"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[{"start":1,"end":2}],"y":[{"start":2,"end":3}]},"return_type":{"abi_type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},"return_witnesses":[5]},"bytecode":"H4sIAAAAAAAA/9Wa627aMBiGHcKhBBLOR+0Hl+Ak5PQP9U6gBK3SNqoqWm9kFzzMYu2DujhbPjvFEkrsJu/7vJ+TFDAtQkiH/GnG6VXLtxvQr+V9Mx/jx5pE3Db5lpZqYahI96Ba91e+bee1g60J6objS90mehbqN8nfuUbSpLAeNVAjXg++bZxeD/m+k9esjlyz6+t3A/p1MFfYvkyzharpPrXzmsFmXPU3YL8F8jUV5HvA1aRMs42qGe2YhgVqwuvH2Tvg721QLwu5Xgbw5Lq8bynz9Tye8Vb+joCjozF/R5lveJ7/riR/V8DR1Zi/q8w3TJiGLclvCzhsjfltZb5hyjQcSX5HwOFozO8o8w3XTKMnyd8TcPQ05od8RVmtilnxff0t0+hL8vcFHH2N+SFfUVarYlZ838hnGgNJ/oGAY6Ax/0CZb3R+rgwl+YcCjqHG/ENlvtH5fdVIkn8k4BhpzA/5irJ274jVrpgV3zeMmMZYkn8s4BhrzA/5irJaFbPi+3pPTGMiyT8RcEw05od8RVmtilnxfcPzXE0l+acCjqnG/FNlvmHANGaS/DMBx0xjfshXlNW+I9bRHbEOKmbF9w1jpjGX5J8LOOYa80O+oqzWHbH2KmbF9/XPnwUXkvwLAcdCY/6FMt9ozzSWkvxLAcdSY/4l8MVet3gAmV9en39kHKAOYPg+X2xlzQRjXOALOIeDtsj7hR60qpnk/eolAWNYPgbQ8k/fTK7TyEtd391SL9nFAV0HuzB2YzeIg70X+34ar+Mo2SURTdy1n7qHIPEPuVjt/7nc6wFBdDRtWFe46tgAE18D44/geLgCb4A5eQTniI4xPtBpgzF+vkMUXljQHEvTJJc/T+C6ZS8oE4+R8kmtk8vlWELwfxKg6qYqq9VErOet+v0jJ73idE3EzHXEeS1Rv5sPuM9839yaZ1quXdwntFxzMe+TBsF/7nDNj/6B8P0GuXz4848R/B3INsvS7y/ZKjuutvv96u05+7o6/kxfD9+Ob78Bhjydn08mAAA="} \ No newline at end of file +{"noir_version":"0.23.0+2dc4805116123ecde584fe0f2cd280de817d3915","hash":17955244824623030861,"abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[{"start":0,"end":1}],"y":[{"start":1,"end":2}]},"return_type":{"abi_type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},"return_witnesses":[4]},"bytecode":"H4sIAAAAAAAA/+2cbW/aSBDHbUhIgJjHQAJ5opXuvY3BhndRP8hVEOCu0vVSJej6Re4DX9b2qoNZs74ys+5IsRRhOzDz+48X2+z+1xXLsupWvNhvf6Vk/RFs28l2OVmX7y1b6uUxeXWPWoKAKO6GOu6/yWsV1FMuFVA3nLyuV0HX4voV68exRorpwnqUQI1kPeTr6dvfebLeSGp2glyzdPt9BNsn4Fhh5xUxz1Bjek/VpGZwsVPbj2D9DOirEOg7x43piphV1JjhUsSogZrI+kn2Ovh/FdSrhlwvG+SUceV2jSzveCw1HtJfV3DUDeqvk+UNouN/odF/oeC4MKj/gixvMBcxHI1+R8HhGNTvkOUN1iJGQ6O/oeBoGNTfIMsbTESMpkZ/U8HRNKgf8uVlrRXMip/XX4gYLY3+loKjZVA/5MvLWiuYFT9v6IsYbY3+toKjbVB/myxvGJ1XOhr9HQVHx6D+DlneMLqv6mr0dxUcXYP6IV9e1gtGrE7BrPh5g1DEuNTov1RwXBrUD/nystYKZsXPO34SMXoa/T0FR8+gfsiXl7VWMCt+3iA6Vn2N/r6Co29Qf58sbzAVMa40+q8UHFcG9UO+vKwOI9YuI9Z2waz4eYOZiHGt0X+t4Lg2qB/y5WWtMWJtFsyKn9ePfgsONPoHCo6BQf0DsrzhSsQYavQPFRxDg/qHIC/2uMU50Pzt5cvfWwlwAmDkOhx8LYN9MsAt+IwEPbP2B3rQqla29kcvLWt3hBgjD4zlv/VMTtbheO353sIdz5ezqTuZLoOZN/Oms+lqPPP99WwyC+fLeejOvYm/9jbTub9Jgtk/z+Wldyiko8UuKepZBgcbNgbRQD6B98MReBvE+AQ+o3qPnRGnqmBpWIQNS4orW7vDkrIImLlgLMzGdWysUzyN7qH6/U9ON8XplRA1lxGP6xH12/sypr94I0u94LBPpjRxwxlRXJcmbkBUh8AnqsOEWR2IeMdE7YGq/VLx+k/M6jvnxRuMiepAdf5l9n2LhxQI4m7e6xvxLoniUl3nV7zqy+78S9UeiM7r++0BL7a7c25Hthl7iLFcSk7bMsN57O+2EvKxEcvnBFJlXaawJYs8Z6n6iH2yJ60E9skOkTLYJ7vSoTXcQmUcu9SW+1Ogp5zSUQXazsC+dJdtNfUZ+So/UwO5pL1JWvhxLdy73bqyo6ua0lC29of8hc7f7R/6kbt9D04tkLkILN1T2J4tUBcrxSIXODyEfWwIrOIuvg06viZDG3Taem7SBl1PHSe5TWeDjqcBNDT6Tdmgs/QT2qCj49/U6Ddlg87ST2ctjqcBtDT6TVmLs/TT2XXjaQBtjX5Tdt0s/XR23WDPrqvSb8qum6Uf8uVldQpmxc8bTwPoavSbshZn6c+y6x5ipbbr6ljx88Z9jJca/absuln6s+y6h1hbBbPi542nbPQ0+k3ZdbP0Z9l1D7E2C2YlsOtGfdZ9jX5Tdt0s/ZAvL2urYFYT1mKVflPW4iz9WRbYQ6zUFlhM1iYj1lbBrPh54zGra41+U9biLP1Zdt1DrH1GrD1GrEXXFT9vPCVyoNFvygadpR/y5WV1CmbFzxtPXRtq9JuybGfph3x5WZ2CWfHzhtG98I1G/42C48agfsiXl7XJiLXNiPWqYFb8vPHUrVuN/lsFx61B/ZAvL6vDiLVTMCt+Xj969OCdRv+dguPOoP47srx+9LvlXqP/XsFxb1D/PVne2Pv0oNH/oOB4MKj/AeQdoeaNvQQwr1gOeQlGhDUQMT/gxoz8Uh8Bv9T6AWzL/8Nx/4/I2myQU8aV25AvL6vDiLXBiLXJiLXFiLXNiJXTd6vDiJVTXbuMWDmdBy4ZsXK6FvQYsXJqr31GrJyuse/3WTSsV4xYOX23OJ1fOdX1mhErp/vXASNWTnUdMmLldI3ldO/C6Rp7w4j1vd+FhvWWEesdI9b7glkJxks8Mf4l++sXr6/rl+3nr+vX18Ufa0kCZ1eXkvURIIXPPIQzvivgc3KU7Tewr57aZwF1cCYv6bMQYUw7FfsXml5/8FFw7nEL5mMVdh59cGz9ThDrJxuvjGkrjrsN1k9S75FTquWpdbHdrr9+2462z6PFajX6/mX75+j5n/XL5q/n7/8BxzOHw3pxAAA=","debug_symbols":"ndvNilVHFIbhezljCftbq/auqr6VkIEkBgTREJ1J33sSYjLQ/uE8Mx180OhLD55d6+vtw6df3355/+nj59vD11vdHn7+evv8x9uP//zt85e3f365PeRcb27vPv7295/mfHxz+/39h3e3h1GPv7y59VODcexvg5Hx3WDcOzjvHVz3Dua9g3XvYD85mP/9s47V3w1y3Luon578qeqY3ybV5/+TWv9O9v2Tp3+yOerbZl7zx01gU7Bp2AzYnLC5YDNhs2Cz798UdFDQQUEHBR0UdFDQQUEHBR0UdFDQQUMHDR00dNDQQUMHDR00dNDQQUMHDR0M6GBABwM6GNDBgA4GdDCggwEdDOhgQAcndHBCByd0cEIHJ3RwQgcndHBCByd0cEIHF3RwQQcXdHBBBxd0cEEHF3RwQQcXdHBBBxM6mNDBhA4mdDChgwkdTOhgQgcTOpjQwYIOFnSwoIMFHSzoYEEHCzpY0MGCDhZ0sKGDDR1s6GBDBxs62NDBhg42dLChgw0d5BBIOkSSDqGkQyzpEEw6RJMO4aRDPOkQUDqkCKNFKYJwkXSReJF8kYCRhJGIUYwxgowp0mYpQpwxAo0RaYxQY8QaI9gY0cYIN0a8MU0fIKQIIceIOUbQMaKOEXaMuGMEHiPyGKHHDPomJUWIPuZ+fpzPfMp66ePkfOZT1subOmAT2Dz5//rSZ9D5zCemVzYDNidsLthM2CzY7Ps3T//Of2UT2EAHDR00dNDQQUMHDR00dNDQwYAOBnQwoIMBHQzoYEAHAzoY0MGADgZ0cEIHJ3RwQgcndHBCByd0cEIHJ3RwQgcndHBBBxd0cEEHF3RwQQcXdHBBBxd0cEEHF3QwoYMJHUzoYEIHEzqY0MGEDiZ0MKGDCR0s6GBBBws6WNDBgg4WdLCggwUdLOhgQQcbOtjQwYYONnSwoYMNHWzoYEMHGzrY0MEzn5heG0VGJaOW0ZDRKaNLRlNGS0ZSRKSISBGRIiJFRIqIFBEpIlJEpIhIESVFlBQhzBhxxgg0RqQxQo0Ra4xgY0QbI9wY8cYIOEbEMUKOEXOMoGNEHSPsGHHHCDxG5DFCjxF7jOBjRB8j/BjxxwhARgQyQpARg4wgZEQhIwwZccgIREYkMkKREYuMYGREIyMcGfHICEhGRDJCkhGTjKBkRCUjLBlxyQhMRmQyQpMRm4zgZEQnIzwZ8ckIUEaEMkKUEaOMIGVEKSNMGXHKCFRGpDJClRGrjGBlRCsjXBnxyghYRsQyQpYRsywxyxKzLDHLErMsMcsSsywxyxKzLDHLErMsMcsSsywxyxKzLDHLErMsMcsSsywxyxKzLDHLErMsehpJbyPpcSS9jqTnkfQ+kh5IilmWmGWJWVbTY1kpQsyyxCxLzLLELEvMssQsS8yyxCxLzLIGvZ+WIsQsS8yyxCxLzLLELEvMssQsS8yyxCxLzLLELEvMssQsS8yyxCxLzLLELEvMssQsS8yyLrqykCLELEvMssQsS8yyxCxLzLLELEvMssQsa9LhjRQhZlliliVmWWKWJWZZYpYlZlliliVmWYtusaQIMcsSsywxyxKzLDHLErMsMcsSsywxy9p0nkf3eXKgJ2bZYpYtZtlili1m2WKWLWbZYpYtZtmhk00pQsyyxSxbzLLFLFvMssUsW8yyxSxbzLKLrnilCDHLFrNsMcsWs2wxyxazbLrqprNuuuu2w24pgk676babjrvpupvOu8UsW8yyxSxbzLIH3fpLEWKWLWbZYpZ9r1k+Pv4F","file_map":{"28":{"source":"mod hash;\nmod array;\nmod slice;\nmod merkle;\nmod schnorr;\nmod ecdsa_secp256k1;\nmod ecdsa_secp256r1;\nmod eddsa;\nmod grumpkin_scalar;\nmod grumpkin_scalar_mul;\nmod scalar_mul;\nmod sha256;\nmod sha512;\nmod field;\nmod ec;\nmod unsafe;\nmod collections;\nmod compat;\nmod convert;\nmod option;\nmod string;\nmod test;\nmod cmp;\nmod ops;\nmod default;\nmod prelude;\nmod uint128;\n// mod bigint;\n\n// Oracle calls are required to be wrapped in an unconstrained function\n// Thus, the only argument to the `println` oracle is expected to always be an ident\n#[oracle(print)]\nunconstrained fn print_oracle(with_newline: bool, input: T) {}\n\nunconstrained pub fn print(input: T) {\n print_oracle(false, input);\n}\n\nunconstrained pub fn println(input: T) {\n print_oracle(true, input);\n}\n\n#[foreign(recursive_aggregation)]\npub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {}\n\n// Asserts that the given value is known at compile-time.\n// Useful for debugging for-loop bounds.\n#[builtin(assert_constant)]\npub fn assert_constant(x: T) {}\n// from_field and as_field are private since they are not valid for every type.\n// `as` should be the default for users to cast between primitive types, and in the future\n// traits can be used to work with generic types.\n#[builtin(from_field)]\nfn from_field(x: Field) -> T {}\n\n#[builtin(as_field)]\nfn as_field(x: T) -> Field {}\n\npub fn wrapping_add(x: T, y: T) -> T {\n crate::from_field(crate::as_field(x) + crate::as_field(y))\n}\n\npub fn wrapping_sub(x: T, y: T) -> T {\n //340282366920938463463374607431768211456 is 2^128, it is used to avoid underflow\n crate::from_field(crate::as_field(x) + 340282366920938463463374607431768211456 - crate::as_field(y))\n}\n\npub fn wrapping_mul(x: T, y: T) -> T {\n crate::from_field(crate::as_field(x) * crate::as_field(y))\n}\n","path":"std/lib.nr"},"42":{"source":"use dep::std;\n\nfn main(x: u64, y: pub u64) -> pub u64 {\n // We include a println statement to show that noirJS will ignore this and continue execution\n std::println(\"foo\");\n\n // A dynamic assertion message is used to show that noirJS will ignore the call and continue execution\n // The assertion passes and thus the foreign call for resolving an assertion message should not be called.\n assert(x < y, f\"Expected x < y but got {x} < {y}\");\n\n assert(x < y);\n x + y\n}\n","path":"/mnt/user-data/maxim/noir/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr"}}} \ No newline at end of file diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..765f632ff74 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..40e447cad02 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr @@ -0,0 +1,6 @@ +fn main(x: u64, y: pub u64) { + // A dynamic assertion message is used to show that noirJS will ignore the call and continue execution + // We need this assertion to fail as the `assert_message` oracle in Noir is only called + // upon a failing condition in an assert. + assert(x < y, f"Expected x < y but got {x} < {y}"); +} diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/target/assert_msg_runtime.json b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/target/assert_msg_runtime.json new file mode 100644 index 00000000000..300e9a06e13 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/target/assert_msg_runtime.json @@ -0,0 +1 @@ +{"noir_version":"0.23.0+2dc4805116123ecde584fe0f2cd280de817d3915","hash":8331765605233675558,"abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[{"start":0,"end":1}],"y":[{"start":1,"end":2}]},"return_type":null,"return_witnesses":[]},"bytecode":"H4sIAAAAAAAA/+2c2W6bQBSGwVviJBi870si9Z4dfFflQRrZDelVb+q+vxqWUU/wNEPrM0OPlJEsYDDnfP/PsHgG3NY07VrLi/76aRTzn8GyXiw3wXpY4DZpsS8rDozl2aHvJ5GbOJ5zsN39MQ5sPziGsRM7QRw8u7HnJbEfR/vjPrL3ju8lzkuw916KYPq/cznlCo50tNgNjp+p323Ovmm9fh7B93Uw1UGMR7AN7zv6H+J0OSw9sP5S8WdmMHFNEFMDJmDmgrEwG9elsVp4Gu33/PtLTrvE6TQQNTcR9+sF/p0djOUDb6fxCw67H8iJG8WS4tpy4oaSfAg9ST74xHyQxOtKag+y2q8sXu8rMX/3tHhDV5IPss6/xI63MJIU9+XD34z3KCmurOv8My1/yZ1/ZbUHSef18/aAF9t+c27XcWM7iLFsmZy6pobz0t9tDeR9k5anAjLtfGhrb0un+GB6nea5KvmT1rHOsAaoYx0iTVB3U8y3AKOGyujaHQ29jdldoK0N9DRLOrpA2xWoY9tcgzq4DZuybW5ALquY7xXbdHG1ZccP7MzUQQ7G0wTfGRXTVOcX/bf+awlczF8Wt1nysQO8wsnrBrA9a8AXrcTCyg1gwd43acxb3JjZ8XGHGjO/JhvAE+bfLZiy9XfALwPZLx3kZHHZsiEtr5tdh3sC/T0OR0+h/p60vGG2/02BfpPDYSrUb0rLG2b3jpZAv8XhsBTqt6TlDZM0Rl+gv8/h6CvU35eWN+8jHAj0DzgcA4X6IV9VVqNmVvy83iGNMRToH3I4hgr1Q76qrEbNrPh58z7GkUD/iMMxUqgf8lVltWpmxc8bZeNFY4H+MYdjrFA/5KvKatbMip8377OeCPRPOBwThfohX1VWq2ZW/Lz5uOZUoH/K4Zgq1A/5qrL2CbGahFitmlnx8+ZjVjOB/hmHY6ZQP+SryjohxDomxFq3r/h5o6yPaS7QP+dwzBXqh3xVWY2aWfHzutmY6kKgf8HhWCjUD/mqsho1s+LnjbJ74aVA/5LDsVSoH/JVZTUJsfYJsU5rZsXPG2bPwqwE+lccjpVC/ZCvKqtBiHVQMyt+Xi9MY6wF+tccjrVC/Wtpeb3sd8tGoH/D4dgo1L+Rljd/9mkr0L/lcGwV6t+CvDvUvPmzBDBvWt57lmAn0YM05j1uzOx5qQfAz7Teg2W2Ho77PyBr00FOFpctQ76qrAYh1h4hVpMQq0WItU+IldKxNSDESsnXISFWSueBESFWSteCMSFWSu11QoiV0jX24z5LDuuUECulY4vS+ZWSrzNCrJTuX+eEWCn5uiDESukaS+nehdI1dkmI9aPfRQ7rihDrmhDrpmZWCeMlTjr+xfrrD6dT8uPn0/fkdDp8SxgJfLu6UczvAGkLzMM3vjtgOzbK9gnU3ZbqNKAOvsl7pUn8yzoYUy/F/o9er7d1DueZKa/lFybuFpAIUgAA"} \ No newline at end of file