Skip to content

Commit

Permalink
feat: Manage breakpoints and allow restarting a debugging session (#3325
Browse files Browse the repository at this point in the history
)

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
  • Loading branch information
ggiraldez and TomAFrench authored Oct 31, 2023
1 parent ae81a78 commit f502108
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 39 deletions.
152 changes: 121 additions & 31 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@ use acvm::pwg::{
use acvm::BlackBoxFunctionSolver;
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};

use nargo::errors::ExecutionError;
use nargo::artifacts::debug::DebugArtifact;
use nargo::errors::{ExecutionError, Location};
use nargo::ops::ForeignCallExecutor;
use nargo::NargoError;

use std::collections::{hash_set::Iter, HashSet};

#[derive(Debug)]
pub(super) enum DebugCommandResult {
Done,
Ok,
BreakpointReached(OpcodeLocation),
Error(NargoError),
}

pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> {
acvm: ACVM<'a, B>,
brillig_solver: Option<BrilligSolver<'a, B>>,
foreign_call_executor: ForeignCallExecutor,
debug_artifact: &'a DebugArtifact,
show_output: bool,
breakpoints: HashSet<OpcodeLocation>,
}

impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
pub(super) fn new(
blackbox_solver: &'a B,
circuit: &'a Circuit,
debug_artifact: &'a DebugArtifact,
initial_witness: WitnessMap,
) -> Self {
Self {
acvm: ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness),
brillig_solver: None,
foreign_call_executor: ForeignCallExecutor::default(),
debug_artifact,
show_output: true,
breakpoints: HashSet::new(),
}
}

Expand All @@ -55,25 +64,41 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
}
}

// Returns the callstack in source code locations for the currently
// executing opcode. This can be None if the execution finished (and
// get_current_opcode_location() returns None) or if the opcode is not
// mapped to a specific source location in the debug artifact (which can
// happen for certain opcodes inserted synthetically by the compiler)
pub(super) fn get_current_source_location(&self) -> Option<Vec<Location>> {
self.get_current_opcode_location()
.as_ref()
.and_then(|location| self.debug_artifact.debug_symbols[0].opcode_location(location))
}

fn step_brillig_opcode(&mut self) -> DebugCommandResult {
let Some(mut solver) = self.brillig_solver.take() else {
unreachable!("Missing Brillig solver");
};
match solver.step() {
Ok(status) => match status {
BrilligSolverStatus::InProgress => {
self.brillig_solver = Some(solver);
Ok(BrilligSolverStatus::InProgress) => {
self.brillig_solver = Some(solver);
if self.breakpoint_reached() {
DebugCommandResult::BreakpointReached(
self.get_current_opcode_location()
.expect("Breakpoint reached but we have no location"),
)
} else {
DebugCommandResult::Ok
}
BrilligSolverStatus::Finished => {
let status = self.acvm.finish_brillig_with_solver(solver);
self.handle_acvm_status(status)
}
BrilligSolverStatus::ForeignCallWait(foreign_call) => {
self.brillig_solver = Some(solver);
self.handle_foreign_call(foreign_call)
}
},
}
Ok(BrilligSolverStatus::Finished) => {
let status = self.acvm.finish_brillig_with_solver(solver);
self.handle_acvm_status(status)
}
Ok(BrilligSolverStatus::ForeignCallWait(foreign_call)) => {
self.brillig_solver = Some(solver);
self.handle_foreign_call(foreign_call)
}
Err(err) => DebugCommandResult::Error(NargoError::ExecutionError(
ExecutionError::SolvingError(err),
)),
Expand All @@ -95,32 +120,41 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
fn handle_acvm_status(&mut self, status: ACVMStatus) -> DebugCommandResult {
if let ACVMStatus::RequiresForeignCall(foreign_call) = status {
self.handle_foreign_call(foreign_call)
} else {
match status {
ACVMStatus::Solved => DebugCommandResult::Done,
ACVMStatus::InProgress => DebugCommandResult::Ok,
ACVMStatus::Failure(error) => DebugCommandResult::Error(
NargoError::ExecutionError(ExecutionError::SolvingError(error)),
),
ACVMStatus::RequiresForeignCall(_) => {
unreachable!("Unexpected pending foreign call resolution");
return self.handle_foreign_call(foreign_call);
}
match status {
ACVMStatus::Solved => DebugCommandResult::Done,
ACVMStatus::InProgress => {
if self.breakpoint_reached() {
DebugCommandResult::BreakpointReached(
self.get_current_opcode_location()
.expect("Breakpoint reached but we have no location"),
)
} else {
DebugCommandResult::Ok
}
}
ACVMStatus::Failure(error) => DebugCommandResult::Error(NargoError::ExecutionError(
ExecutionError::SolvingError(error),
)),
ACVMStatus::RequiresForeignCall(_) => {
unreachable!("Unexpected pending foreign call resolution");
}
}
}

pub(super) fn step_into_opcode(&mut self) -> DebugCommandResult {
if self.brillig_solver.is_some() {
self.step_brillig_opcode()
} else {
match self.acvm.step_into_brillig_opcode() {
StepResult::IntoBrillig(solver) => {
self.brillig_solver = Some(solver);
self.step_brillig_opcode()
}
StepResult::Status(status) => self.handle_acvm_status(status),
return self.step_brillig_opcode();
}

match self.acvm.step_into_brillig_opcode() {
StepResult::IntoBrillig(solver) => {
self.brillig_solver = Some(solver);
self.step_brillig_opcode()
}
StepResult::Status(status) => self.handle_acvm_status(status),
}
}

Expand All @@ -133,6 +167,20 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
self.handle_acvm_status(status)
}

pub(super) fn next(&mut self) -> DebugCommandResult {
let start_location = self.get_current_source_location();
loop {
let result = self.step_into_opcode();
if !matches!(result, DebugCommandResult::Ok) {
return result;
}
let new_location = self.get_current_source_location();
if new_location.is_some() && new_location != start_location {
return DebugCommandResult::Ok;
}
}
}

pub(super) fn cont(&mut self) -> DebugCommandResult {
loop {
let result = self.step_into_opcode();
Expand All @@ -142,6 +190,48 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
}
}

fn breakpoint_reached(&self) -> bool {
if let Some(location) = self.get_current_opcode_location() {
self.breakpoints.contains(&location)
} else {
false
}
}

pub(super) fn is_valid_opcode_location(&self, location: &OpcodeLocation) -> bool {
let opcodes = self.get_opcodes();
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
}
}
}
}
}

pub(super) fn is_breakpoint_set(&self, location: &OpcodeLocation) -> bool {
self.breakpoints.contains(location)
}

pub(super) fn add_breakpoint(&mut self, location: OpcodeLocation) -> bool {
self.breakpoints.insert(location)
}

pub(super) fn delete_breakpoint(&mut self, location: &OpcodeLocation) -> bool {
self.breakpoints.remove(location)
}

pub(super) fn iterate_breakpoints(&self) -> Iter<'_, OpcodeLocation> {
self.breakpoints.iter()
}

pub(super) fn is_solved(&self) -> bool {
matches!(self.acvm.get_status(), ACVMStatus::Solved)
}
Expand Down
Loading

0 comments on commit f502108

Please sign in to comment.