diff --git a/Cargo.toml b/Cargo.toml index 919efca4ccb..bf78ed62e9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,3 +124,16 @@ similar-asserts = "1.5.0" # This is required to be able to run `cargo test` in acvm_js due to the `locals exceeds maximum` error. # See https://ritik-mishra.medium.com/resolving-the-wasm-pack-error-locals-exceed-maximum-ec3a9d96685b opt-level = 1 + + +[profile.size] +inherits = "release" +lto = true +opt-level = "z" + +[profile.size-aggressive] +inherits = "release" +strip = true +lto = true +panic = "abort" +opt-level = "z" diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 33862ed641f..40507aaed3a 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -120,7 +120,12 @@ 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) => { - self.acvm.resolve_pending_foreign_call(foreign_call_result); + if let Some(mut solver) = self.brillig_solver.take() { + solver.resolve_pending_foreign_call(foreign_call_result); + self.brillig_solver = Some(solver); + } else { + self.acvm.resolve_pending_foreign_call(foreign_call_result); + } // TODO: should we retry executing the opcode somehow in this case? DebugCommandResult::Ok } @@ -168,13 +173,50 @@ 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, + OpcodeLocation::Brillig { acir_index, .. } => acir_index, + }) + } + + fn step_out_of_brillig_opcode(&mut self) -> DebugCommandResult { + let Some(start_acir_index) = self.get_current_acir_index() else { + return DebugCommandResult::Done; + }; + loop { + let result = self.step_into_opcode(); + if !matches!(result, DebugCommandResult::Ok) { + return result; + } + let new_acir_index = self.get_current_acir_index().unwrap(); + if new_acir_index != start_acir_index { + return DebugCommandResult::Ok; + } + } + } + pub(super) fn step_acir_opcode(&mut self) -> DebugCommandResult { - let status = if let Some(solver) = self.brillig_solver.take() { - self.acvm.finish_brillig_with_solver(solver) + if self.currently_executing_brillig() { + self.step_out_of_brillig_opcode() } else { - self.acvm.solve_opcode() - }; - self.handle_acvm_status(status) + let status = self.acvm.solve_opcode(); + self.handle_acvm_status(status) + } } pub(super) fn next(&mut self) -> DebugCommandResult { @@ -276,3 +318,208 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.acvm.finalize() } } + +#[cfg(test)] +struct StubbedSolver; + +#[cfg(test)] +impl BlackBoxFunctionSolver for StubbedSolver { + fn schnorr_verify( + &self, + _public_key_x: &FieldElement, + _public_key_y: &FieldElement, + _signature: &[u8], + _message: &[u8], + ) -> Result { + unimplemented!(); + } + + fn pedersen_commitment( + &self, + _inputs: &[FieldElement], + _domain_separator: u32, + ) -> Result<(FieldElement, FieldElement), acvm::BlackBoxResolutionError> { + unimplemented!(); + } + + fn pedersen_hash( + &self, + _inputs: &[FieldElement], + _domain_separator: u32, + ) -> Result { + unimplemented!(); + } + + fn fixed_base_scalar_mul( + &self, + _low: &FieldElement, + _high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), acvm::BlackBoxResolutionError> { + unimplemented!(); + } +} + +#[cfg(test)] +#[test] +fn test_resolve_foreign_calls_stepping_into_brillig() { + use std::collections::BTreeMap; + + use acvm::acir::{ + brillig::{Opcode as BrilligOpcode, RegisterIndex, RegisterOrMemory}, + circuit::brillig::{Brillig, BrilligInputs}, + native_types::Expression, + }; + + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + + let blackbox_solver = &StubbedSolver; + + let brillig_opcodes = Brillig { + inputs: vec![BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_x)], + ..Expression::default() + })], + outputs: vec![], + bytecode: vec![ + BrilligOpcode::Const { destination: RegisterIndex::from(1), value: Value::from(fe_0) }, + BrilligOpcode::ForeignCall { + function: "clear_mock".into(), + destinations: vec![], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + BrilligOpcode::Stop, + ], + predicate: None, + }; + let opcodes = vec![Opcode::Brillig(brillig_opcodes)]; + let current_witness_index = 2; + let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + + let debug_symbols = vec![]; + let file_map = BTreeMap::new(); + let warnings = vec![]; + let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; + + let initial_witness = BTreeMap::from([(Witness(1), fe_1)]).into(); + + let mut context = DebugContext::new(blackbox_solver, circuit, debug_artifact, initial_witness); + + assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(0))); + + // execute the first Brillig opcode (const) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) + ); + + // try to execute the second Brillig opcode (and resolve the foreign call) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) + ); + + // retry the second Brillig opcode (foreign call should be finished) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }) + ); + + // last Brillig opcode + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Done)); + assert_eq!(context.get_current_opcode_location(), None); +} + +#[cfg(test)] +#[test] +fn test_break_brillig_block_while_stepping_acir_opcodes() { + use std::collections::BTreeMap; + + use acvm::acir::{ + brillig::{Opcode as BrilligOpcode, RegisterIndex}, + circuit::brillig::{Brillig, BrilligInputs, BrilligOutputs}, + native_types::Expression, + }; + use acvm::brillig_vm::brillig::BinaryFieldOp; + + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_z = Witness(3); + + let blackbox_solver = &StubbedSolver; + + // 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)], + bytecode: vec![ + BrilligOpcode::BinaryFieldOp { + destination: RegisterIndex::from(0), + op: BinaryFieldOp::Add, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + }, + BrilligOpcode::Stop, + ], + predicate: None, + }; + let opcodes = vec![ + // z = x + y + Opcode::Brillig(brillig_opcodes), + // x + y - z = 0 + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + ]; + let current_witness_index = 3; + let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + + let debug_symbols = vec![]; + let file_map = BTreeMap::new(); + let warnings = vec![]; + let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; + + let initial_witness = BTreeMap::from([(Witness(1), fe_1), (Witness(2), fe_1)]).into(); + + let mut context = DebugContext::new(blackbox_solver, circuit, debug_artifact, initial_witness); + + // set breakpoint + let breakpoint_location = OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }; + assert!(context.add_breakpoint(breakpoint_location.clone())); + + // execute the first ACIR opcode (Brillig block) -> should reach the breakpoint instead + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::BreakpointReached(_))); + assert_eq!(context.get_current_opcode_location(), Some(breakpoint_location)); + + // continue execution to the next ACIR opcode + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(1))); + + // last ACIR opcode + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::Done)); + assert_eq!(context.get_current_opcode_location(), None); +}