Skip to content

Commit f502108

Browse files
feat: Manage breakpoints and allow restarting a debugging session (#3325)
Co-authored-by: Tom French <[email protected]>
1 parent ae81a78 commit f502108

2 files changed

Lines changed: 299 additions & 39 deletions

File tree

tooling/debugger/src/context.rs

Lines changed: 121 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,44 @@ use acvm::pwg::{
55
use acvm::BlackBoxFunctionSolver;
66
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};
77

8-
use nargo::errors::ExecutionError;
8+
use nargo::artifacts::debug::DebugArtifact;
9+
use nargo::errors::{ExecutionError, Location};
910
use nargo::ops::ForeignCallExecutor;
1011
use nargo::NargoError;
1112

13+
use std::collections::{hash_set::Iter, HashSet};
14+
1215
#[derive(Debug)]
1316
pub(super) enum DebugCommandResult {
1417
Done,
1518
Ok,
19+
BreakpointReached(OpcodeLocation),
1620
Error(NargoError),
1721
}
1822

1923
pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> {
2024
acvm: ACVM<'a, B>,
2125
brillig_solver: Option<BrilligSolver<'a, B>>,
2226
foreign_call_executor: ForeignCallExecutor,
27+
debug_artifact: &'a DebugArtifact,
2328
show_output: bool,
29+
breakpoints: HashSet<OpcodeLocation>,
2430
}
2531

2632
impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
2733
pub(super) fn new(
2834
blackbox_solver: &'a B,
2935
circuit: &'a Circuit,
36+
debug_artifact: &'a DebugArtifact,
3037
initial_witness: WitnessMap,
3138
) -> Self {
3239
Self {
3340
acvm: ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness),
3441
brillig_solver: None,
3542
foreign_call_executor: ForeignCallExecutor::default(),
43+
debug_artifact,
3644
show_output: true,
45+
breakpoints: HashSet::new(),
3746
}
3847
}
3948

@@ -55,25 +64,41 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
5564
}
5665
}
5766

67+
// Returns the callstack in source code locations for the currently
68+
// executing opcode. This can be None if the execution finished (and
69+
// get_current_opcode_location() returns None) or if the opcode is not
70+
// mapped to a specific source location in the debug artifact (which can
71+
// happen for certain opcodes inserted synthetically by the compiler)
72+
pub(super) fn get_current_source_location(&self) -> Option<Vec<Location>> {
73+
self.get_current_opcode_location()
74+
.as_ref()
75+
.and_then(|location| self.debug_artifact.debug_symbols[0].opcode_location(location))
76+
}
77+
5878
fn step_brillig_opcode(&mut self) -> DebugCommandResult {
5979
let Some(mut solver) = self.brillig_solver.take() else {
6080
unreachable!("Missing Brillig solver");
6181
};
6282
match solver.step() {
63-
Ok(status) => match status {
64-
BrilligSolverStatus::InProgress => {
65-
self.brillig_solver = Some(solver);
83+
Ok(BrilligSolverStatus::InProgress) => {
84+
self.brillig_solver = Some(solver);
85+
if self.breakpoint_reached() {
86+
DebugCommandResult::BreakpointReached(
87+
self.get_current_opcode_location()
88+
.expect("Breakpoint reached but we have no location"),
89+
)
90+
} else {
6691
DebugCommandResult::Ok
6792
}
68-
BrilligSolverStatus::Finished => {
69-
let status = self.acvm.finish_brillig_with_solver(solver);
70-
self.handle_acvm_status(status)
71-
}
72-
BrilligSolverStatus::ForeignCallWait(foreign_call) => {
73-
self.brillig_solver = Some(solver);
74-
self.handle_foreign_call(foreign_call)
75-
}
76-
},
93+
}
94+
Ok(BrilligSolverStatus::Finished) => {
95+
let status = self.acvm.finish_brillig_with_solver(solver);
96+
self.handle_acvm_status(status)
97+
}
98+
Ok(BrilligSolverStatus::ForeignCallWait(foreign_call)) => {
99+
self.brillig_solver = Some(solver);
100+
self.handle_foreign_call(foreign_call)
101+
}
77102
Err(err) => DebugCommandResult::Error(NargoError::ExecutionError(
78103
ExecutionError::SolvingError(err),
79104
)),
@@ -95,32 +120,41 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
95120

96121
fn handle_acvm_status(&mut self, status: ACVMStatus) -> DebugCommandResult {
97122
if let ACVMStatus::RequiresForeignCall(foreign_call) = status {
98-
self.handle_foreign_call(foreign_call)
99-
} else {
100-
match status {
101-
ACVMStatus::Solved => DebugCommandResult::Done,
102-
ACVMStatus::InProgress => DebugCommandResult::Ok,
103-
ACVMStatus::Failure(error) => DebugCommandResult::Error(
104-
NargoError::ExecutionError(ExecutionError::SolvingError(error)),
105-
),
106-
ACVMStatus::RequiresForeignCall(_) => {
107-
unreachable!("Unexpected pending foreign call resolution");
123+
return self.handle_foreign_call(foreign_call);
124+
}
125+
126+
match status {
127+
ACVMStatus::Solved => DebugCommandResult::Done,
128+
ACVMStatus::InProgress => {
129+
if self.breakpoint_reached() {
130+
DebugCommandResult::BreakpointReached(
131+
self.get_current_opcode_location()
132+
.expect("Breakpoint reached but we have no location"),
133+
)
134+
} else {
135+
DebugCommandResult::Ok
108136
}
109137
}
138+
ACVMStatus::Failure(error) => DebugCommandResult::Error(NargoError::ExecutionError(
139+
ExecutionError::SolvingError(error),
140+
)),
141+
ACVMStatus::RequiresForeignCall(_) => {
142+
unreachable!("Unexpected pending foreign call resolution");
143+
}
110144
}
111145
}
112146

113147
pub(super) fn step_into_opcode(&mut self) -> DebugCommandResult {
114148
if self.brillig_solver.is_some() {
115-
self.step_brillig_opcode()
116-
} else {
117-
match self.acvm.step_into_brillig_opcode() {
118-
StepResult::IntoBrillig(solver) => {
119-
self.brillig_solver = Some(solver);
120-
self.step_brillig_opcode()
121-
}
122-
StepResult::Status(status) => self.handle_acvm_status(status),
149+
return self.step_brillig_opcode();
150+
}
151+
152+
match self.acvm.step_into_brillig_opcode() {
153+
StepResult::IntoBrillig(solver) => {
154+
self.brillig_solver = Some(solver);
155+
self.step_brillig_opcode()
123156
}
157+
StepResult::Status(status) => self.handle_acvm_status(status),
124158
}
125159
}
126160

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

170+
pub(super) fn next(&mut self) -> DebugCommandResult {
171+
let start_location = self.get_current_source_location();
172+
loop {
173+
let result = self.step_into_opcode();
174+
if !matches!(result, DebugCommandResult::Ok) {
175+
return result;
176+
}
177+
let new_location = self.get_current_source_location();
178+
if new_location.is_some() && new_location != start_location {
179+
return DebugCommandResult::Ok;
180+
}
181+
}
182+
}
183+
136184
pub(super) fn cont(&mut self) -> DebugCommandResult {
137185
loop {
138186
let result = self.step_into_opcode();
@@ -142,6 +190,48 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
142190
}
143191
}
144192

193+
fn breakpoint_reached(&self) -> bool {
194+
if let Some(location) = self.get_current_opcode_location() {
195+
self.breakpoints.contains(&location)
196+
} else {
197+
false
198+
}
199+
}
200+
201+
pub(super) fn is_valid_opcode_location(&self, location: &OpcodeLocation) -> bool {
202+
let opcodes = self.get_opcodes();
203+
match *location {
204+
OpcodeLocation::Acir(acir_index) => acir_index < opcodes.len(),
205+
OpcodeLocation::Brillig { acir_index, brillig_index } => {
206+
acir_index < opcodes.len()
207+
&& matches!(opcodes[acir_index], Opcode::Brillig(..))
208+
&& {
209+
if let Opcode::Brillig(ref brillig) = opcodes[acir_index] {
210+
brillig_index < brillig.bytecode.len()
211+
} else {
212+
false
213+
}
214+
}
215+
}
216+
}
217+
}
218+
219+
pub(super) fn is_breakpoint_set(&self, location: &OpcodeLocation) -> bool {
220+
self.breakpoints.contains(location)
221+
}
222+
223+
pub(super) fn add_breakpoint(&mut self, location: OpcodeLocation) -> bool {
224+
self.breakpoints.insert(location)
225+
}
226+
227+
pub(super) fn delete_breakpoint(&mut self, location: &OpcodeLocation) -> bool {
228+
self.breakpoints.remove(location)
229+
}
230+
231+
pub(super) fn iterate_breakpoints(&self) -> Iter<'_, OpcodeLocation> {
232+
self.breakpoints.iter()
233+
}
234+
145235
pub(super) fn is_solved(&self) -> bool {
146236
matches!(self.acvm.get_status(), ACVMStatus::Solved)
147237
}

0 commit comments

Comments
 (0)