Skip to content

Commit

Permalink
fix(debugger): Step through foreign calls and breakpoints inside Bril…
Browse files Browse the repository at this point in the history
…lig blocks (#3511)
  • Loading branch information
ggiraldez authored Nov 22, 2023
1 parent 4f7a6ae commit 5d77d7a
Showing 1 changed file with 253 additions and 6 deletions.
259 changes: 253 additions & 6 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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<usize> {
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 {
Expand Down Expand Up @@ -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<bool, acvm::BlackBoxResolutionError> {
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<FieldElement, acvm::BlackBoxResolutionError> {
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);
}

0 comments on commit 5d77d7a

Please sign in to comment.