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
146 changes: 115 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,36 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
}
}

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 +115,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 matches!(self.brillig_solver, 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 +162,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 matches!(new_location, Some(..)) && new_location != start_location {
ggiraldez marked this conversation as resolved.
Show resolved Hide resolved
return DebugCommandResult::Ok;
}
}
}

pub(super) fn cont(&mut self) -> DebugCommandResult {
loop {
let result = self.step_into_opcode();
Expand All @@ -142,6 +185,47 @@ 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_location(&self, location: &OpcodeLocation) -> bool {
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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(..))
&& {
let Opcode::Brillig(ref brillig) = opcodes[acir_index] else {
unreachable!("opcode at {acir_index} is not Brillig")
};
brillig_index < brillig.bytecode.len()
ggiraldez marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

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

pub(super) fn add_breakpoint(&mut self, location: OpcodeLocation) {
_ = self.breakpoints.insert(location);
}

pub(super) fn delete_breakpoint(&mut self, location: &OpcodeLocation) {
_ = 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