diff --git a/acvm-repo/acvm/src/pwg/brillig.rs b/acvm-repo/acvm/src/pwg/brillig.rs index 67faf7f5007..10178465d58 100644 --- a/acvm-repo/acvm/src/pwg/brillig.rs +++ b/acvm-repo/acvm/src/pwg/brillig.rs @@ -30,19 +30,6 @@ pub struct BrilligSolver<'b, B: BlackBoxFunctionSolver> { } impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { - /// Evaluates if the Brillig block should be skipped entirely - pub(super) fn should_skip( - witness: &WitnessMap, - brillig: &Brillig, - ) -> Result { - // If the predicate is `None`, the block should never be skipped - // If the predicate is `Some` but we cannot find a value, then we return stalled - match &brillig.predicate { - Some(pred) => Ok(get_value(pred, witness)?.is_zero()), - None => Ok(false), - } - } - /// Assigns the zero value to all outputs of the given [`Brillig`] bytecode. pub(super) fn zero_out_brillig_outputs( initial_witness: &mut WitnessMap, diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index 652e173867a..3d3c52c661b 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -430,7 +430,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let Opcode::BrilligCall { id, inputs, outputs, predicate } = &self.opcodes[self.instruction_pointer] else { - unreachable!("Not executing a Brillig opcode"); + unreachable!("Not executing a BrilligCall opcode"); }; let witness = &mut self.witness_map; @@ -468,27 +468,28 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } } - pub fn step_into_brillig_opcode(&mut self) -> StepResult<'a, B> { - let Opcode::Brillig(brillig) = &self.opcodes[self.instruction_pointer] else { + pub fn step_into_brillig(&mut self) -> StepResult<'a, B> { + let Opcode::BrilligCall { id, inputs, outputs, predicate } = + &self.opcodes[self.instruction_pointer] + else { return StepResult::Status(self.solve_opcode()); }; let witness = &mut self.witness_map; - let should_skip = match BrilligSolver::::should_skip(witness, brillig) { + let should_skip = match is_predicate_false(witness, predicate) { Ok(result) => result, Err(err) => return StepResult::Status(self.handle_opcode_resolution(Err(err))), }; - if should_skip { - let resolution = - BrilligSolver::::zero_out_brillig_outputs(witness, &brillig.outputs); + let resolution = BrilligSolver::::zero_out_brillig_outputs(witness, outputs); return StepResult::Status(self.handle_opcode_resolution(resolution)); } - let solver = BrilligSolver::new( + let solver = BrilligSolver::new_call( witness, &self.block_solvers, - brillig, + inputs, + &self.unconstrained_functions[*id as usize].bytecode, self.backend, self.instruction_pointer, ); @@ -499,8 +500,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } pub fn finish_brillig_with_solver(&mut self, solver: BrilligSolver<'a, B>) -> ACVMStatus { - if !matches!(&self.opcodes[self.instruction_pointer], Opcode::Brillig(..)) { - unreachable!("Not executing a Brillig opcode"); + if !matches!(self.opcodes[self.instruction_pointer], Opcode::BrilligCall { .. }) { + unreachable!("Not executing a Brillig/BrilligCall opcode"); } self.brillig_solver = Some(solver); self.solve_opcode() diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 9b535075484..a423016eacf 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -34,6 +34,7 @@ pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> { debug_artifact: &'a DebugArtifact, breakpoints: HashSet, source_to_opcodes: BTreeMap>, + unconstrained_functions: &'a [BrilligBytecode], } impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { @@ -59,6 +60,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { debug_artifact, breakpoints: HashSet::new(), source_to_opcodes, + unconstrained_functions, } } @@ -215,7 +217,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.get_opcodes() .iter() .map(|opcode| match opcode { - Opcode::Brillig(brillig_block) => brillig_block.bytecode.len(), + Opcode::BrilligCall { id, .. } => { + self.unconstrained_functions[*id as usize].bytecode.len() + } _ => 1, }) .collect() @@ -296,19 +300,22 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { None => String::from("invalid"), Some(OpcodeLocation::Acir(acir_index)) => { let opcode = &opcodes[*acir_index]; - if let Opcode::Brillig(ref brillig) = opcode { - let first_opcode = &brillig.bytecode[0]; - format!("BRILLIG {first_opcode:?}") - } else { - format!("{opcode:?}") + match opcode { + Opcode::BrilligCall { id, .. } => { + let first_opcode = &self.unconstrained_functions[*id as usize].bytecode[0]; + format!("BRILLIG CALL {first_opcode:?}") + } + _ => format!("{opcode:?}"), } } Some(OpcodeLocation::Brillig { acir_index, brillig_index }) => { - if let Opcode::Brillig(ref brillig) = opcodes[*acir_index] { - let opcode = &brillig.bytecode[*brillig_index]; - format!(" | {opcode:?}") - } else { - String::from(" | invalid") + match &opcodes[*acir_index] { + Opcode::BrilligCall { id, .. } => { + let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + let opcode = &bytecode[*brillig_index]; + format!(" | {opcode:?}") + } + _ => String::from(" | invalid"), } } } @@ -400,7 +407,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { return self.step_brillig_opcode(); } - match self.acvm.step_into_brillig_opcode() { + match self.acvm.step_into_brillig() { StepResult::IntoBrillig(solver) => { self.brillig_solver = Some(solver); self.step_brillig_opcode() @@ -409,20 +416,6 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - fn currently_executing_brillig(&self) -> bool { - if self.brillig_solver.is_some() { - return true; - } - - match self.get_current_opcode_location() { - Some(OpcodeLocation::Brillig { .. }) => true, - Some(OpcodeLocation::Acir(acir_index)) => { - matches!(self.get_opcodes()[acir_index], Opcode::Brillig(_)) - } - _ => false, - } - } - fn get_current_acir_index(&self) -> Option { self.get_current_opcode_location().map(|opcode_location| match opcode_location { OpcodeLocation::Acir(acir_index) => acir_index, @@ -446,8 +439,22 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } + pub(super) fn is_executing_brillig(&self) -> bool { + if self.brillig_solver.is_some() { + return true; + } + + match self.get_current_opcode_location() { + Some(OpcodeLocation::Brillig { .. }) => true, + Some(OpcodeLocation::Acir(acir_index)) => { + matches!(self.get_opcodes()[acir_index], Opcode::BrilligCall { .. }) + } + _ => false, + } + } + pub(super) fn step_acir_opcode(&mut self) -> DebugCommandResult { - if self.currently_executing_brillig() { + if self.is_executing_brillig() { self.step_out_of_brillig_opcode() } else { let status = self.acvm.solve_opcode(); @@ -511,12 +518,6 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - pub(super) fn is_executing_brillig(&self) -> bool { - let opcodes = self.get_opcodes(); - let acir_index = self.acvm.instruction_pointer(); - acir_index < opcodes.len() && matches!(opcodes[acir_index], Opcode::Brillig(..)) - } - pub(super) fn get_brillig_memory(&self) -> Option<&[MemoryValue]> { self.brillig_solver.as_ref().map(|solver| solver.get_memory()) } @@ -552,15 +553,17 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { match *location { OpcodeLocation::Acir(acir_index) => acir_index < opcodes.len(), OpcodeLocation::Brillig { acir_index, brillig_index } => { - acir_index < opcodes.len() - && matches!(opcodes[acir_index], Opcode::Brillig(..)) - && { - if let Opcode::Brillig(ref brillig) = opcodes[acir_index] { - brillig_index < brillig.bytecode.len() - } else { - false + if acir_index < opcodes.len() { + match &opcodes[acir_index] { + Opcode::BrilligCall { id, .. } => { + let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + brillig_index < bytecode.len() } + _ => false, } + } else { + false + } } } } @@ -649,7 +652,7 @@ mod tests { use acvm::{ acir::{ circuit::{ - brillig::{Brillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligInputs, BrilligOutputs}, opcodes::BlockId, }, native_types::Expression, @@ -666,12 +669,7 @@ mod tests { let fe_1 = FieldElement::one(); let w_x = Witness(1); - let brillig_opcodes = Brillig { - inputs: vec![BrilligInputs::Single(Expression { - linear_combinations: vec![(fe_1, w_x)], - ..Expression::default() - })], - outputs: vec![], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -692,9 +690,17 @@ mod tests { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, ], - predicate: None, }; - let opcodes = vec![Opcode::Brillig(brillig_opcodes)]; + let opcodes = vec![Opcode::BrilligCall { + id: 0, + inputs: vec![BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_x)], + ..Expression::default() + })], + outputs: vec![], + predicate: None, + }]; + let brillig_funcs = &vec![brillig_bytecode]; let current_witness_index = 2; let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; @@ -707,7 +713,6 @@ mod tests { let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); - let brillig_funcs = &vec![]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, circuit, @@ -766,18 +771,7 @@ mod tests { let w_z = Witness(3); // This Brillig block is equivalent to: z = x + y - let brillig_opcodes = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { - linear_combinations: vec![(fe_1, w_x)], - ..Expression::default() - }), - BrilligInputs::Single(Expression { - linear_combinations: vec![(fe_1, w_y)], - ..Expression::default() - }), - ], - outputs: vec![BrilligOutputs::Simple(w_z)], + let brillig_bytecode = BrilligBytecode { bytecode: vec![ BrilligOpcode::CalldataCopy { destination_address: MemoryAddress(0), @@ -792,11 +786,24 @@ mod tests { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 1 }, ], - predicate: None, }; let opcodes = vec![ // z = x + y - Opcode::Brillig(brillig_opcodes), + Opcode::BrilligCall { + id: 0, + inputs: vec![ + BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_x)], + ..Expression::default() + }), + BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_y)], + ..Expression::default() + }), + ], + outputs: vec![BrilligOutputs::Simple(w_z)], + predicate: None, + }, // x + y - z = 0 Opcode::AssertZero(Expression { mul_terms: vec![], @@ -816,7 +823,7 @@ mod tests { let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); - let brillig_funcs = &vec![]; + let brillig_funcs = &vec![brillig_bytecode]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, circuit, @@ -848,34 +855,24 @@ mod tests { #[test] fn test_offset_opcode_location() { + let brillig_bytecode = BrilligBytecode { + bytecode: vec![ + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, + BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, + ], + }; + let opcodes = vec![ - Opcode::Brillig(Brillig { - inputs: vec![], - outputs: vec![], - bytecode: vec![ - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - ], - predicate: None, - }), + Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, Opcode::MemoryInit { block_id: BlockId(0), init: vec![] }, - Opcode::Brillig(Brillig { - inputs: vec![], - outputs: vec![], - bytecode: vec![ - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }, - ], - predicate: None, - }), + Opcode::BrilligCall { id: 0, inputs: vec![], outputs: vec![], predicate: None }, Opcode::AssertZero(Expression::default()), ]; let circuit = Circuit { opcodes, ..Circuit::default() }; let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new(), warnings: vec![] }; - let brillig_funcs = &vec![]; + let brillig_funcs = &vec![brillig_bytecode]; let context = DebugContext::new( &StubbedBlackBoxSolver, &circuit, diff --git a/tooling/debugger/src/repl.rs b/tooling/debugger/src/repl.rs index 2a92698e5ce..8f908a38ffc 100644 --- a/tooling/debugger/src/repl.rs +++ b/tooling/debugger/src/repl.rs @@ -3,6 +3,7 @@ use crate::context::{DebugCommandResult, DebugContext}; 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::Opcode as BrilligOpcode; use acvm::{BlackBoxFunctionSolver, FieldElement}; use crate::foreign_calls::DefaultDebugForeignCallExecutor; @@ -68,23 +69,18 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { Some(location) => { match location { OpcodeLocation::Acir(ip) => { - // Default Brillig display is too bloated for this context, - // so we limit it to denoting it's the start of a Brillig - // block. The user can still use the `opcodes` command to - // take a look at the whole block. - let opcode_summary = match opcodes[ip] { - Opcode::Brillig(..) => "BRILLIG: ...".into(), - _ => format!("{}", opcodes[ip]), - }; - println!("At opcode {}: {}", ip, opcode_summary); + println!("At opcode {}: {}", ip, opcodes[ip]); } OpcodeLocation::Brillig { acir_index, brillig_index } => { - let Opcode::Brillig(ref brillig) = opcodes[acir_index] else { - unreachable!("Brillig location does not contain a Brillig block"); - }; + let brillig_bytecode = + if let Opcode::BrilligCall { id, .. } = opcodes[acir_index] { + &self.unconstrained_functions[id as usize].bytecode + } else { + unreachable!("Brillig location does not contain Brillig opcodes"); + }; println!( "At opcode {}.{}: {:?}", - acir_index, brillig_index, brillig.bytecode[brillig_index] + acir_index, brillig_index, brillig_bytecode[brillig_index] ); } } @@ -104,12 +100,15 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { ) } OpcodeLocation::Brillig { acir_index, brillig_index } => { - let Opcode::Brillig(ref brillig) = opcodes[*acir_index] else { - unreachable!("Brillig location does not contain a Brillig block"); + let brillig_bytecode = if let Opcode::BrilligCall { id, .. } = opcodes[*acir_index] + { + &self.unconstrained_functions[id as usize].bytecode + } else { + unreachable!("Brillig location does not contain Brillig opcodes"); }; println!( "Frame #{index}, opcode {}.{}: {:?}", - acir_index, brillig_index, brillig.bytecode[*brillig_index] + acir_index, brillig_index, brillig_bytecode[*brillig_index] ); } } @@ -162,22 +161,30 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { "" } }; + let print_brillig_bytecode = |acir_index, bytecode: &[BrilligOpcode]| { + for (brillig_index, brillig_opcode) in bytecode.iter().enumerate() { + println!( + "{:>3}.{:<2} |{:2} {:?}", + acir_index, + brillig_index, + brillig_marker(acir_index, brillig_index), + brillig_opcode + ); + } + }; for (acir_index, opcode) in opcodes.iter().enumerate() { let marker = outer_marker(acir_index); - if let Opcode::Brillig(brillig) = opcode { - println!("{:>3} {:2} BRILLIG inputs={:?}", acir_index, marker, brillig.inputs); - println!(" | outputs={:?}", brillig.outputs); - for (brillig_index, brillig_opcode) in brillig.bytecode.iter().enumerate() { + match &opcode { + Opcode::BrilligCall { id, inputs, outputs, .. } => { println!( - "{:>3}.{:<2} |{:2} {:?}", - acir_index, - brillig_index, - brillig_marker(acir_index, brillig_index), - brillig_opcode + "{:>3} {:2} BRILLIG CALL id={} inputs={:?}", + acir_index, marker, id, inputs ); + println!(" | outputs={:?}", outputs); + let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + print_brillig_bytecode(acir_index, bytecode); } - } else { - println!("{:>3} {:2} {:?}", acir_index, marker, opcode); + _ => println!("{:>3} {:2} {:?}", acir_index, marker, opcode), } } }