Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Manage breakpoints and allow restarting a debugging session #3325

Merged
merged 11 commits into from
Oct 31, 2023
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)
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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))
}
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

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()
}
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

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