Skip to main content

noir_debugger/
context.rs

1use crate::foreign_calls::DebugForeignCallExecutor;
2use acvm::acir::brillig::BitSize;
3use acvm::acir::circuit::brillig::{BrilligBytecode, BrilligFunctionId};
4use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation};
5use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack};
6use acvm::brillig_vm::MemoryValue;
7use acvm::pwg::{
8    ACVM, ACVMStatus, AcirCallWaitInfo, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo,
9    OpcodeNotSolvable, StepResult,
10};
11use acvm::{BlackBoxFunctionSolver, FieldElement};
12
13use codespan_reporting::files::{Files, SimpleFile};
14use fm::FileId;
15use nargo::NargoError;
16use nargo::errors::{ExecutionError, Location, ResolvedOpcodeLocation, execution_error_from};
17use noirc_artifacts::debug::{DebugArtifact, StackFrame};
18use noirc_driver::{CompiledProgram, DebugFile};
19
20use noirc_errors::call_stack::CallStackId;
21use noirc_errors::debug_info::DebugInfo;
22use noirc_printable_type::{PrintableType, PrintableValue};
23use thiserror::Error;
24
25use std::collections::BTreeMap;
26use std::collections::HashSet;
27use std::path::PathBuf;
28
29/// A Noir program is composed by
30/// `n` ACIR circuits
31///       |_ `m` ACIR opcodes
32///                |_ Acir call
33///                |_ Acir Brillig function invocation
34///                           |_ `p` Brillig opcodes
35///
36/// The purpose of this structure is to map the opcode locations in ACIR circuits into
37/// a flat contiguous address space to be able to expose them to the DAP interface.
38/// In this address space, the ACIR circuits are laid out one after the other, and
39/// Brillig functions called from such circuits are expanded inline, replacing
40/// the `BrilligCall` ACIR opcode.
41///
42/// `addresses: Vec<Vec<usize>>`
43///  * The outer vec is `n` sized - one element per ACIR circuit
44///  * Each nested vec is `m` sized - one element per ACIR opcode in circuit
45///    * Each element is the "virtual address" of such opcode
46///
47/// For flattening we map each ACIR circuit and ACIR opcode with a sequential address number
48/// We start by assigning 0 to the very first ACIR opcode and then start accumulating by
49/// traversing by depth-first
50///
51/// Even if the address space is continuous, the `addresses` tree only
52/// keeps track of the ACIR opcodes, since the Brillig opcode addresses can be
53/// calculated from the initial opcode address.
54/// As a result the flattened indexed addresses list may have "holes".
55///
56/// If between two consequent `addresses` nodes there is a "hole" (an address jump),
57/// this means that the first one is actually a ACIR Brillig call
58/// which has as many brillig opcodes as `second_address - first_address`
59///
60#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
61pub struct AddressMap {
62    addresses: Vec<Vec<usize>>,
63
64    /// Virtual address of the last opcode of the program
65    last_valid_address: usize,
66
67    /// Maps the "holes" in the `addresses` nodes to the Brillig function ID
68    /// associated with that address space.
69    brillig_addresses: Vec<BrilligAddressSpace>,
70}
71
72/// Associates a BrilligFunctionId with the address space.
73/// A BrilligFunctionId is found by checking whether an address is between
74/// the `start_address` and `end_address`
75#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
76struct BrilligAddressSpace {
77    /// The start of the Brillig call address space
78    start_address: usize,
79    /// The end of the Brillig address space
80    end_address: usize,
81    /// The Brillig function id associated with this address space
82    brillig_function_id: BrilligFunctionId,
83}
84
85impl AddressMap {
86    pub(super) fn new(
87        circuits: &[Circuit<FieldElement>],
88        unconstrained_functions: &[BrilligBytecode<FieldElement>],
89    ) -> Self {
90        let opcode_address_size = |opcode: &Opcode<FieldElement>| {
91            if let Opcode::BrilligCall { id, .. } = opcode {
92                (unconstrained_functions[id.as_usize()].bytecode.len(), Some(*id))
93            } else {
94                (1, None)
95            }
96        };
97
98        let mut addresses = Vec::with_capacity(circuits.len());
99        let mut next_address = 0usize;
100        let mut brillig_addresses = Vec::new();
101
102        for circuit in circuits {
103            let mut circuit_addresses = Vec::with_capacity(circuit.opcodes.len());
104            for opcode in &circuit.opcodes {
105                circuit_addresses.push(next_address);
106                let (address_size, brillig_function_id) = opcode_address_size(opcode);
107                if let Some(brillig_function_id) = brillig_function_id {
108                    let brillig_address_space = BrilligAddressSpace {
109                        start_address: next_address,
110                        end_address: next_address + address_size,
111                        brillig_function_id,
112                    };
113                    brillig_addresses.push(brillig_address_space);
114                }
115                next_address += address_size;
116            }
117            addresses.push(circuit_addresses);
118        }
119
120        Self { addresses, last_valid_address: next_address - 1, brillig_addresses }
121    }
122
123    /// Returns the absolute address of the opcode at the given location.
124    /// Absolute here means accounting for nested Brillig opcodes in BrilligCall
125    /// opcodes.
126    pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize {
127        let circuit_addresses = &self.addresses[location.circuit_id as usize];
128        match &location.opcode_location {
129            OpcodeLocation::Acir(acir_index) => circuit_addresses[*acir_index],
130            OpcodeLocation::Brillig { acir_index, brillig_index } => {
131                circuit_addresses[*acir_index] + *brillig_index
132            }
133        }
134    }
135
136    pub fn address_to_debug_location(&self, address: usize) -> Option<DebugLocation> {
137        if address > self.last_valid_address {
138            return None;
139        }
140        // We binary search if the given address is the first opcode address of each circuit id
141        // if is not, this means that the address itself is "contained" in the previous
142        // circuit indicated by `Err(insert_index)`
143        let circuit_id =
144            match self.addresses.binary_search_by(|addresses| addresses[0].cmp(&address)) {
145                Ok(found_index) => found_index,
146                // This means that the address is not in `insert_index` circuit
147                // because is an `Err`, so it must be included in previous circuit vec of opcodes
148                Err(insert_index) => insert_index - 1,
149            };
150
151        // We binary search among the selected `circuit_id`` list of opcodes
152        // If Err(insert_index) this means that the given address
153        // is a Brillig addresses that's contained in previous index ACIR opcode index
154        let (opcode_location, brillig_function_id) =
155            match self.addresses[circuit_id].binary_search(&address) {
156                Ok(found_index) => (OpcodeLocation::Acir(found_index), None),
157                Err(insert_index) => {
158                    let acir_index = insert_index - 1;
159                    let base_offset = self.addresses[circuit_id][acir_index];
160                    let brillig_index = address - base_offset;
161                    let brillig_function_id = self
162                        .brillig_addresses
163                        .iter()
164                        .find(|brillig_address_space| {
165                            address >= brillig_address_space.start_address
166                                && address <= brillig_address_space.end_address
167                        })
168                        .map(|brillig_address_space| brillig_address_space.brillig_function_id);
169                    (OpcodeLocation::Brillig { acir_index, brillig_index }, brillig_function_id)
170                }
171            };
172
173        Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location, brillig_function_id })
174    }
175}
176
177#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
178pub struct DebugLocation {
179    pub circuit_id: u32,
180    pub opcode_location: OpcodeLocation,
181    pub brillig_function_id: Option<BrilligFunctionId>,
182}
183
184impl std::fmt::Display for DebugLocation {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        let circuit_id = self.circuit_id;
187        match self.opcode_location {
188            OpcodeLocation::Acir(index) => write!(f, "{circuit_id}:{index}"),
189            OpcodeLocation::Brillig { acir_index, brillig_index } => {
190                write!(f, "{circuit_id}:{acir_index}.{brillig_index}")
191            }
192        }
193    }
194}
195
196impl From<DebugLocation> for ResolvedOpcodeLocation {
197    fn from(debug_loc: DebugLocation) -> Self {
198        ResolvedOpcodeLocation {
199            acir_function_index: usize::try_from(debug_loc.circuit_id).unwrap(),
200            opcode_location: debug_loc.opcode_location,
201        }
202    }
203}
204
205#[derive(Error, Debug)]
206pub enum DebugLocationFromStrError {
207    #[error("Invalid debug location string: {0}")]
208    InvalidDebugLocationString(String),
209}
210
211impl std::str::FromStr for DebugLocation {
212    type Err = DebugLocationFromStrError;
213    fn from_str(s: &str) -> Result<Self, Self::Err> {
214        let parts: Vec<_> = s.split(':').collect();
215        let error = Err(DebugLocationFromStrError::InvalidDebugLocationString(s.to_string()));
216
217        match parts.len() {
218            1 => OpcodeLocation::from_str(parts[0]).map_or(error, |opcode_location| {
219                Ok(DebugLocation { circuit_id: 0, opcode_location, brillig_function_id: None })
220            }),
221            2 => {
222                let first_part = parts[0].parse().ok();
223                let second_part = OpcodeLocation::from_str(parts[1]).ok();
224                if let (Some(circuit_id), Some(opcode_location)) = (first_part, second_part) {
225                    Ok(DebugLocation { circuit_id, opcode_location, brillig_function_id: None })
226                } else {
227                    error
228                }
229            }
230            _ => error,
231        }
232    }
233}
234
235#[derive(Debug)]
236pub(super) enum DebugCommandResult {
237    Done,
238    Ok,
239    BreakpointReached(DebugLocation),
240    Error(NargoError<FieldElement>),
241}
242
243#[derive(Debug)]
244pub struct DebugStackFrame<F> {
245    pub function_name: String,
246    pub function_params: Vec<String>,
247    pub variables: Vec<(String, PrintableValue<F>, PrintableType)>,
248}
249
250impl<F: Clone> From<&StackFrame<'_, F>> for DebugStackFrame<F> {
251    fn from(value: &StackFrame<F>) -> Self {
252        DebugStackFrame {
253            function_name: value.function_name.to_string(),
254            function_params: value.function_params.iter().map(|param| param.to_string()).collect(),
255            variables: value
256                .variables
257                .iter()
258                .map(|(name, value, var_type)| {
259                    (name.to_string(), (**value).clone(), (*var_type).clone())
260                })
261                .collect(),
262        }
263    }
264}
265
266pub struct ExecutionFrame<'a, B: BlackBoxFunctionSolver<FieldElement>> {
267    circuit_id: u32,
268    acvm: ACVM<'a, FieldElement, B>,
269}
270
271#[derive(Debug)]
272pub enum DebugExecutionResult {
273    Solved(WitnessStack<FieldElement>),
274    Incomplete,
275    Error(NargoError<FieldElement>),
276}
277
278#[derive(Debug, Clone)]
279pub struct DebugProject {
280    pub compiled_program: CompiledProgram,
281    pub initial_witness: WitnessMap<FieldElement>,
282    pub root_dir: PathBuf,
283    pub package_name: String,
284}
285
286#[derive(Debug, Clone)]
287
288pub struct RunParams {
289    /// Use pedantic ACVM solving
290    pub pedantic_solving: bool,
291
292    /// Option for configuring the source_code_printer
293    /// This option only applies for the Repl interface
294    pub raw_source_printing: Option<bool>,
295
296    /// JSON RPC url to solve oracle calls
297    pub oracle_resolver_url: Option<String>,
298}
299
300pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver<FieldElement>> {
301    pub(crate) acvm: ACVM<'a, FieldElement, B>,
302    current_circuit_id: u32,
303    brillig_solver: Option<BrilligSolver<'a, FieldElement, B>>,
304
305    witness_stack: WitnessStack<FieldElement>,
306    acvm_stack: Vec<ExecutionFrame<'a, B>>,
307
308    backend: &'a B,
309    foreign_call_executor: Box<dyn DebugForeignCallExecutor + 'a>,
310
311    debug_artifact: &'a DebugArtifact,
312    breakpoints: HashSet<DebugLocation>,
313    source_to_locations: BTreeMap<FileId, Vec<(usize, DebugLocation)>>,
314
315    circuits: &'a [Circuit<FieldElement>],
316    unconstrained_functions: &'a [BrilligBytecode<FieldElement>],
317
318    acir_opcode_addresses: AddressMap,
319    initial_witness: WitnessMap<FieldElement>,
320}
321
322fn initialize_acvm<'a, B: BlackBoxFunctionSolver<FieldElement>>(
323    backend: &'a B,
324    circuits: &'a [Circuit<FieldElement>],
325    initial_witness: WitnessMap<FieldElement>,
326    unconstrained_functions: &'a [BrilligBytecode<FieldElement>],
327) -> ACVM<'a, FieldElement, B> {
328    let current_circuit_id: u32 = 0;
329    let initial_circuit = &circuits[current_circuit_id as usize];
330
331    ACVM::new(
332        backend,
333        &initial_circuit.opcodes,
334        initial_witness,
335        unconstrained_functions,
336        &initial_circuit.assert_messages,
337    )
338}
339
340impl<'a, B: BlackBoxFunctionSolver<FieldElement>> DebugContext<'a, B> {
341    pub(super) fn new(
342        blackbox_solver: &'a B,
343        circuits: &'a [Circuit<FieldElement>],
344        debug_artifact: &'a DebugArtifact,
345        initial_witness: WitnessMap<FieldElement>,
346        foreign_call_executor: Box<dyn DebugForeignCallExecutor + 'a>,
347        unconstrained_functions: &'a [BrilligBytecode<FieldElement>],
348    ) -> Self {
349        let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact);
350        let current_circuit_id: u32 = 0;
351        let acir_opcode_addresses = AddressMap::new(circuits, unconstrained_functions);
352        Self {
353            current_circuit_id,
354            brillig_solver: None,
355            witness_stack: WitnessStack::default(),
356            acvm_stack: vec![],
357            backend: blackbox_solver,
358            foreign_call_executor,
359            debug_artifact,
360            breakpoints: HashSet::new(),
361            source_to_locations: source_to_opcodes,
362            circuits,
363            unconstrained_functions,
364            acir_opcode_addresses,
365            initial_witness: initial_witness.clone(), // we keep it so the context can restart itself
366            acvm: initialize_acvm(
367                blackbox_solver,
368                circuits,
369                initial_witness,
370                unconstrained_functions,
371            ),
372        }
373    }
374
375    pub(super) fn get_opcodes(&self) -> &[Opcode<FieldElement>] {
376        self.acvm.opcodes()
377    }
378
379    pub(super) fn get_opcodes_of_circuit(&self, circuit_id: u32) -> &[Opcode<FieldElement>] {
380        &self.circuits[circuit_id as usize].opcodes
381    }
382
383    pub(super) fn get_witness_map(&self) -> &WitnessMap<FieldElement> {
384        self.acvm.witness_map()
385    }
386
387    pub(super) fn overwrite_witness(
388        &mut self,
389        witness: Witness,
390        value: FieldElement,
391    ) -> Option<FieldElement> {
392        self.acvm.overwrite_witness(witness, value)
393    }
394
395    pub(super) fn get_current_debug_location(&self) -> Option<DebugLocation> {
396        let ip = self.acvm.instruction_pointer();
397        if ip >= self.get_opcodes().len() {
398            None
399        } else {
400            let (opcode_location, brillig_function_id) =
401                if let Some(ref solver) = self.brillig_solver {
402                    let function_id = solver.function_id;
403                    (
404                        OpcodeLocation::Brillig {
405                            acir_index: ip,
406                            brillig_index: solver.program_counter(),
407                        },
408                        Some(function_id),
409                    )
410                } else {
411                    (OpcodeLocation::Acir(ip), None)
412                };
413            Some(DebugLocation {
414                circuit_id: self.current_circuit_id,
415                brillig_function_id,
416                opcode_location,
417            })
418        }
419    }
420
421    pub(super) fn get_call_stack(&self) -> Vec<DebugLocation> {
422        // Build the frames from parent ACIR calls
423        let mut frames: Vec<_> = self
424            .acvm_stack
425            .iter()
426            .map(|ExecutionFrame { circuit_id, acvm }| DebugLocation {
427                circuit_id: *circuit_id,
428                opcode_location: OpcodeLocation::Acir(acvm.instruction_pointer()),
429                brillig_function_id: None,
430            })
431            .collect();
432
433        // Now add the frame(s) for the currently executing ACVM
434        let instruction_pointer = self.acvm.instruction_pointer();
435        let circuit_id = self.current_circuit_id;
436        if let Some(ref solver) = self.brillig_solver {
437            frames.extend(solver.get_call_stack().iter().map(|program_counter| DebugLocation {
438                circuit_id,
439                opcode_location: OpcodeLocation::Brillig {
440                    acir_index: instruction_pointer,
441                    brillig_index: *program_counter,
442                },
443                brillig_function_id: Some(solver.function_id),
444            }));
445        } else if instruction_pointer < self.get_opcodes().len() {
446            frames.push(DebugLocation {
447                circuit_id,
448                opcode_location: OpcodeLocation::Acir(instruction_pointer),
449                brillig_function_id: None,
450            });
451        }
452        frames
453    }
454
455    pub(super) fn is_source_location_in_debug_module(&self, location: &Location) -> bool {
456        self.debug_artifact
457            .file_map
458            .get(&location.file)
459            .map(is_debug_file_in_debug_crate)
460            .unwrap_or(false)
461    }
462
463    /// Find an opcode location matching a source code location
464    // We apply some heuristics here, and there are four possibilities for the
465    // return value of this function:
466    // 1. the source location is not found -> None
467    // 2. an exact unique location is found (very rare) -> Some(opcode_location)
468    // 3. an exact but not unique location is found, ie. a source location may
469    //    be mapped to multiple opcodes, and those may be disjoint, for example for
470    //    functions called multiple times throughout the program
471    //    -> return the first opcode in program order that matches the source location
472    // 4. exact location is not found, so an opcode for a nearby source location
473    //    is returned (this again could actually be more than one opcodes)
474    //    -> return the opcode for the next source line that is mapped
475    pub(super) fn find_opcode_for_source_location(
476        &self,
477        file_id: &FileId,
478        line: i64,
479    ) -> Option<DebugLocation> {
480        let line = line as usize;
481        let line_to_opcodes = self.source_to_locations.get(file_id)?;
482        let found_location = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) {
483            Ok(index) => {
484                // move backwards to find the first opcode which matches the line
485                let mut index = index;
486                while index > 0 && line_to_opcodes[index - 1].0 == line {
487                    index -= 1;
488                }
489                line_to_opcodes[index].1
490            }
491            Err(index) => {
492                if index >= line_to_opcodes.len() {
493                    return None;
494                }
495                line_to_opcodes[index].1
496            }
497        };
498        Some(found_location)
499    }
500
501    pub(super) fn find_opcode_at_current_file_line(&self, line: i64) -> Option<DebugLocation> {
502        let file = self.get_current_file()?;
503
504        self.find_opcode_for_source_location(&file, line)
505    }
506
507    /// Returns the callstack in source code locations for the currently
508    /// executing opcode. This can be `None` if the execution finished (and
509    /// `get_current_opcode_location()` returns `None`) or if the opcode is not
510    /// mapped to a specific source location in the debug artifact (which can
511    /// happen for certain opcodes inserted synthetically by the compiler).
512    /// This function also filters source locations that are determined to be in
513    /// the internal debug module.
514    pub(super) fn get_current_source_location(&self) -> Option<Vec<Location>> {
515        self.get_current_debug_location()
516            .as_ref()
517            .map(|debug_location| self.get_source_location_for_debug_location(debug_location))
518            .filter(|v: &Vec<Location>| !v.is_empty())
519    }
520
521    /// Returns the `FileId` of the file associated with the innermost function on the call stack.
522    fn get_current_file(&self) -> Option<FileId> {
523        self.get_current_source_location()
524            .and_then(|locations| locations.last().map(|location| location.file))
525    }
526
527    /// Returns the (possible) stack of source locations corresponding to the
528    /// given opcode location. Due to compiler inlining it's possible for this
529    /// function to return multiple source locations. An empty vector means that
530    /// the given opcode location cannot be mapped back to a source location
531    /// (eg. it may be pure debug instrumentation code or other synthetically
532    /// produced opcode by the compiler)
533    pub(super) fn get_source_location_for_debug_location(
534        &self,
535        debug_location: &DebugLocation,
536    ) -> Vec<Location> {
537        self.debug_artifact.debug_symbols[debug_location.circuit_id as usize]
538            .opcode_location(&debug_location.opcode_location)
539            .unwrap_or_else(|| {
540                if let (Some(brillig_function_id), Some(brillig_location)) = (
541                    debug_location.brillig_function_id,
542                    debug_location.opcode_location.to_brillig_location(),
543                ) {
544                    let brillig_locations = self.debug_artifact.debug_symbols
545                        [debug_location.circuit_id as usize]
546                        .brillig_locations
547                        .get(&brillig_function_id);
548                    let call_stack_id = brillig_locations
549                        .unwrap()
550                        .get(&brillig_location)
551                        .copied()
552                        .unwrap_or_default();
553                    self.debug_artifact.debug_symbols[debug_location.circuit_id as usize]
554                        .location_tree
555                        .get_call_stack(call_stack_id)
556                } else {
557                    vec![]
558                }
559            })
560            .into_iter()
561            .filter(|source_location| !self.is_source_location_in_debug_module(source_location))
562            .collect()
563    }
564
565    /// Returns the current call stack with expanded source locations. In
566    /// general, the matching between opcode location and source location is 1
567    /// to 1, but due to the compiler inlining functions a single opcode
568    /// location may expand to multiple source locations.
569    pub(super) fn get_source_call_stack(&self) -> Vec<(DebugLocation, Location)> {
570        self.get_call_stack()
571            .iter()
572            .flat_map(|debug_location| {
573                self.get_source_location_for_debug_location(debug_location)
574                    .into_iter()
575                    .map(|source_location| (*debug_location, source_location))
576            })
577            .collect()
578    }
579
580    /// Returns the absolute address of the opcode at the given location.
581    pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize {
582        self.acir_opcode_addresses.debug_location_to_address(location)
583    }
584
585    // Returns the DebugLocation associated to the given address
586    pub fn address_to_debug_location(&self, address: usize) -> Option<DebugLocation> {
587        self.acir_opcode_addresses.address_to_debug_location(address)
588    }
589
590    pub(super) fn render_opcode_at_location(&self, location: &DebugLocation) -> String {
591        let opcodes = self.get_opcodes_of_circuit(location.circuit_id);
592        match &location.opcode_location {
593            OpcodeLocation::Acir(acir_index) => {
594                let opcode = &opcodes[*acir_index];
595                match opcode {
596                    Opcode::BrilligCall { id, .. } => {
597                        let first_opcode = &self.unconstrained_functions[id.as_usize()].bytecode[0];
598                        format!("BRILLIG {first_opcode:?}")
599                    }
600                    _ => format!("{opcode:?}"),
601                }
602            }
603            OpcodeLocation::Brillig { acir_index, brillig_index } => match &opcodes[*acir_index] {
604                Opcode::BrilligCall { id, .. } => {
605                    let bytecode = &self.unconstrained_functions[id.as_usize()].bytecode;
606                    let opcode = &bytecode[*brillig_index];
607                    format!("      | {opcode:?}")
608                }
609                _ => String::from("      | invalid"),
610            },
611        }
612    }
613
614    fn step_brillig_opcode(&mut self) -> DebugCommandResult {
615        let Some(mut solver) = self.brillig_solver.take() else {
616            unreachable!("Missing Brillig solver");
617        };
618        match solver.step() {
619            Ok(BrilligSolverStatus::InProgress) => {
620                self.brillig_solver = Some(solver);
621                if self.breakpoint_reached() {
622                    DebugCommandResult::BreakpointReached(
623                        self.get_current_debug_location()
624                            .expect("Breakpoint reached but we have no location"),
625                    )
626                } else {
627                    DebugCommandResult::Ok
628                }
629            }
630            Ok(BrilligSolverStatus::Finished) => {
631                let status = self.acvm.finish_brillig_with_solver(solver);
632                self.handle_acvm_status(status)
633            }
634            Ok(BrilligSolverStatus::ForeignCallWait(foreign_call)) => {
635                self.brillig_solver = Some(solver);
636                self.handle_foreign_call(foreign_call)
637            }
638            Err(err) => {
639                let error = execution_error_from(
640                    err,
641                    &self
642                        .get_call_stack()
643                        .into_iter()
644                        .map(|op| op.into())
645                        .collect::<Vec<ResolvedOpcodeLocation>>(),
646                );
647                DebugCommandResult::Error(NargoError::ExecutionError(error))
648            }
649        }
650    }
651
652    fn handle_foreign_call(
653        &mut self,
654        foreign_call: ForeignCallWaitInfo<FieldElement>,
655    ) -> DebugCommandResult {
656        let foreign_call_result = self.foreign_call_executor.execute(&foreign_call);
657
658        match foreign_call_result {
659            Ok(foreign_call_result) => {
660                if let Some(mut solver) = self.brillig_solver.take() {
661                    solver.resolve_pending_foreign_call(foreign_call_result);
662                    self.brillig_solver = Some(solver);
663                } else {
664                    self.acvm.resolve_pending_foreign_call(foreign_call_result);
665                }
666                // TODO: should we retry executing the opcode somehow in this
667                // case? Otherwise, executing a foreign call takes two debugging
668                // steps.
669                DebugCommandResult::Ok
670            }
671            Err(error) => DebugCommandResult::Error(error.into()),
672        }
673    }
674
675    fn handle_acir_call(
676        &mut self,
677        call_info: AcirCallWaitInfo<FieldElement>,
678    ) -> DebugCommandResult {
679        let callee_circuit = &self.circuits[call_info.id.as_usize()];
680        let callee_witness_map = call_info.initial_witness;
681        let callee_acvm = ACVM::new(
682            self.backend,
683            &callee_circuit.opcodes,
684            callee_witness_map,
685            self.unconstrained_functions,
686            &callee_circuit.assert_messages,
687        );
688        let caller_acvm = std::mem::replace(&mut self.acvm, callee_acvm);
689        self.acvm_stack
690            .push(ExecutionFrame { circuit_id: self.current_circuit_id, acvm: caller_acvm });
691        self.current_circuit_id = call_info.id.0;
692
693        // Explicitly handling the new ACVM status here handles two edge cases:
694        // 1. there is a breakpoint set at the beginning of a circuit
695        // 2. the called circuit has no opcodes
696        self.handle_acvm_status(self.acvm.get_status().clone())
697    }
698
699    fn handle_acir_call_finished(&mut self) -> DebugCommandResult {
700        let caller_frame = self.acvm_stack.pop().expect("Execution stack should not be empty");
701        let caller_acvm = caller_frame.acvm;
702        let callee_acvm = std::mem::replace(&mut self.acvm, caller_acvm);
703        self.current_circuit_id = caller_frame.circuit_id;
704        let call_solved_witness = callee_acvm.finalize();
705
706        let ACVMStatus::RequiresAcirCall(call_info) = self.acvm.get_status() else {
707            unreachable!("Resolving an ACIR call, the caller is in an invalid state");
708        };
709        let acir_to_call = &self.circuits[call_info.id.as_usize()];
710
711        let mut call_resolved_outputs = Vec::new();
712        for return_witness_index in acir_to_call.return_values.indices() {
713            if let Some(return_value) = call_solved_witness.get_index(return_witness_index) {
714                call_resolved_outputs.push(*return_value);
715            } else {
716                return DebugCommandResult::Error(
717                    ExecutionError::SolvingError(
718                        OpcodeNotSolvable::MissingAssignment(return_witness_index).into(),
719                        None, // Missing assignment errors do not supply user-facing diagnostics so we do not need to attach a call stack
720                    )
721                    .into(),
722                );
723            }
724        }
725        self.acvm.resolve_pending_acir_call(call_resolved_outputs);
726
727        DebugCommandResult::Ok
728    }
729
730    fn handle_acvm_status(&mut self, status: ACVMStatus<FieldElement>) -> DebugCommandResult {
731        match status {
732            ACVMStatus::Solved => {
733                if self.acvm_stack.is_empty() {
734                    return DebugCommandResult::Done;
735                }
736                self.handle_acir_call_finished()
737            }
738            ACVMStatus::InProgress => {
739                if self.breakpoint_reached() {
740                    DebugCommandResult::BreakpointReached(
741                        self.get_current_debug_location()
742                            .expect("Breakpoint reached but we have no location"),
743                    )
744                } else {
745                    DebugCommandResult::Ok
746                }
747            }
748            ACVMStatus::Failure(error) => DebugCommandResult::Error(NargoError::ExecutionError(
749                ExecutionError::SolvingError(error, None),
750            )),
751            ACVMStatus::RequiresForeignCall(foreign_call) => self.handle_foreign_call(foreign_call),
752            ACVMStatus::RequiresAcirCall(call_info) => self.handle_acir_call(call_info),
753        }
754    }
755
756    pub(super) fn step_into_opcode(&mut self) -> DebugCommandResult {
757        if self.brillig_solver.is_some() {
758            return self.step_brillig_opcode();
759        }
760
761        match self.acvm.step_into_brillig() {
762            StepResult::IntoBrillig(solver) => {
763                self.brillig_solver = Some(solver);
764                self.step_brillig_opcode()
765            }
766            StepResult::Status(status) => self.handle_acvm_status(status),
767        }
768    }
769
770    fn get_current_acir_index(&self) -> Option<usize> {
771        self.get_current_debug_location().map(|debug_location| {
772            match debug_location.opcode_location {
773                OpcodeLocation::Acir(acir_index) | OpcodeLocation::Brillig { acir_index, .. } => {
774                    acir_index
775                }
776            }
777        })
778    }
779
780    fn step_out_of_brillig_opcode(&mut self) -> DebugCommandResult {
781        let Some(start_acir_index) = self.get_current_acir_index() else {
782            return DebugCommandResult::Done;
783        };
784        loop {
785            let result = self.step_into_opcode();
786            if !matches!(result, DebugCommandResult::Ok) {
787                return result;
788            }
789            let new_acir_index = self.get_current_acir_index().unwrap();
790            if new_acir_index != start_acir_index {
791                return DebugCommandResult::Ok;
792            }
793        }
794    }
795
796    pub(super) fn is_executing_brillig(&self) -> bool {
797        if self.brillig_solver.is_some() {
798            return true;
799        }
800
801        match self.get_current_debug_location() {
802            Some(DebugLocation { opcode_location: OpcodeLocation::Brillig { .. }, .. }) => true,
803            Some(DebugLocation {
804                circuit_id,
805                opcode_location: OpcodeLocation::Acir(acir_index),
806                ..
807            }) => {
808                matches!(
809                    self.get_opcodes_of_circuit(circuit_id)[acir_index],
810                    Opcode::BrilligCall { .. }
811                )
812            }
813            _ => false,
814        }
815    }
816
817    pub(super) fn step_acir_opcode(&mut self) -> DebugCommandResult {
818        if self.is_executing_brillig() {
819            self.step_out_of_brillig_opcode()
820        } else {
821            let status = self.acvm.solve_opcode();
822            self.handle_acvm_status(status)
823        }
824    }
825
826    /// Steps debugging execution until the next source location
827    pub(super) fn next_into(&mut self) -> DebugCommandResult {
828        let start_location = self.get_current_source_location();
829        loop {
830            let result = self.step_into_opcode();
831            if !matches!(result, DebugCommandResult::Ok) {
832                return result;
833            }
834            let new_location = self.get_current_source_location();
835            if new_location.is_some() && new_location != start_location {
836                return DebugCommandResult::Ok;
837            }
838        }
839    }
840
841    /// Steps debugging execution until the next source location at the same (or
842    /// less) call stack depth (eg. don't dive into function calls)
843    pub(super) fn next_over(&mut self) -> DebugCommandResult {
844        let start_call_stack = self.get_source_call_stack();
845        loop {
846            let result = self.next_into();
847            if !matches!(result, DebugCommandResult::Ok) {
848                return result;
849            }
850            let new_call_stack = self.get_source_call_stack();
851            if new_call_stack.len() <= start_call_stack.len() {
852                return DebugCommandResult::Ok;
853            }
854        }
855    }
856
857    /// Steps debugging execution until the next source location with a smaller
858    /// call stack depth (eg. returning from the current function)
859    pub(super) fn next_out(&mut self) -> DebugCommandResult {
860        let start_call_stack = self.get_source_call_stack();
861        loop {
862            let result = self.next_into();
863            if !matches!(result, DebugCommandResult::Ok) {
864                return result;
865            }
866            let new_call_stack = self.get_source_call_stack();
867            if new_call_stack.len() < start_call_stack.len() {
868                return DebugCommandResult::Ok;
869            }
870        }
871    }
872
873    pub(super) fn cont(&mut self) -> DebugCommandResult {
874        loop {
875            let result = self.step_into_opcode();
876            if !matches!(result, DebugCommandResult::Ok) {
877                return result;
878            }
879        }
880    }
881
882    pub(super) fn get_brillig_memory(&self) -> Option<&[MemoryValue<FieldElement>]> {
883        self.brillig_solver.as_ref().map(|solver| solver.get_memory())
884    }
885
886    pub(super) fn write_brillig_memory(
887        &mut self,
888        ptr: usize,
889        value: FieldElement,
890        bit_size: BitSize,
891    ) {
892        if let Some(solver) = self.brillig_solver.as_mut() {
893            solver.write_memory_at(
894                ptr,
895                MemoryValue::new_checked(value, bit_size)
896                    .expect("Invalid value for the given bit size"),
897            );
898        }
899    }
900
901    pub(super) fn get_variables(&self) -> Vec<StackFrame<FieldElement>> {
902        self.foreign_call_executor.get_variables()
903    }
904
905    pub(super) fn current_stack_frame(&self) -> Option<StackFrame<FieldElement>> {
906        self.foreign_call_executor.current_stack_frame()
907    }
908
909    fn breakpoint_reached(&self) -> bool {
910        if let Some(location) = self.get_current_debug_location() {
911            self.breakpoints.contains(&location)
912        } else {
913            false
914        }
915    }
916
917    pub(super) fn is_valid_debug_location(&self, location: &DebugLocation) -> bool {
918        if location.circuit_id as usize >= self.circuits.len() {
919            return false;
920        }
921        let opcodes = self.get_opcodes_of_circuit(location.circuit_id);
922        match location.opcode_location {
923            OpcodeLocation::Acir(acir_index) => acir_index < opcodes.len(),
924            OpcodeLocation::Brillig { acir_index, brillig_index } => {
925                if acir_index < opcodes.len() {
926                    match &opcodes[acir_index] {
927                        Opcode::BrilligCall { id, .. } => {
928                            let bytecode = &self.unconstrained_functions[id.as_usize()].bytecode;
929                            brillig_index < bytecode.len()
930                        }
931                        _ => false,
932                    }
933                } else {
934                    false
935                }
936            }
937        }
938    }
939
940    pub(super) fn is_breakpoint_set(&self, location: &DebugLocation) -> bool {
941        self.breakpoints.contains(location)
942    }
943
944    pub(super) fn add_breakpoint(&mut self, location: DebugLocation) -> bool {
945        self.breakpoints.insert(location)
946    }
947
948    pub(super) fn delete_breakpoint(&mut self, location: &DebugLocation) -> bool {
949        self.breakpoints.remove(location)
950    }
951
952    pub(super) fn clear_breakpoints(&mut self) {
953        self.breakpoints.clear();
954    }
955
956    pub(super) fn is_solved(&self) -> bool {
957        matches!(self.acvm.get_status(), ACVMStatus::Solved)
958    }
959
960    pub fn finalize(mut self) -> WitnessStack<FieldElement> {
961        let last_witness_map = self.acvm.finalize();
962        self.witness_stack.push(0, last_witness_map);
963        self.witness_stack
964    }
965
966    pub(super) fn restart(&mut self) {
967        // restart everything that's progress related
968        // by assigning the initial values
969        self.current_circuit_id = 0;
970        self.brillig_solver = None;
971        self.witness_stack = WitnessStack::default();
972        self.acvm_stack = vec![];
973        self.foreign_call_executor.restart(self.debug_artifact);
974        self.acvm = initialize_acvm(
975            self.backend,
976            self.circuits,
977            self.initial_witness.clone(),
978            self.unconstrained_functions,
979        );
980    }
981}
982
983fn is_debug_file_in_debug_crate(debug_file: &DebugFile) -> bool {
984    debug_file.path.starts_with("__debug/")
985}
986
987/// Builds a map from FileId to an ordered vector of tuples with line
988/// numbers and opcode locations corresponding to those line numbers
989fn build_source_to_opcode_debug_mappings(
990    debug_artifact: &DebugArtifact,
991) -> BTreeMap<FileId, Vec<(usize, DebugLocation)>> {
992    if debug_artifact.debug_symbols.is_empty() {
993        return BTreeMap::new();
994    }
995    let simple_files: BTreeMap<_, _> = debug_artifact
996        .file_map
997        .iter()
998        .filter(|(_, debug_file)| !is_debug_file_in_debug_crate(debug_file))
999        .map(|(file_id, debug_file)| {
1000            (
1001                file_id,
1002                SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()),
1003            )
1004        })
1005        .collect();
1006
1007    let mut result: BTreeMap<FileId, Vec<(usize, DebugLocation)>> = BTreeMap::new();
1008    for (circuit_id, debug_symbols) in debug_artifact.debug_symbols.iter().enumerate() {
1009        let location_map = debug_symbols
1010            .acir_locations
1011            .iter()
1012            .map(|(key, val)| (OpcodeLocation::Acir(key.index()), *val))
1013            .collect();
1014        add_opcode_locations_map(
1015            debug_symbols,
1016            &location_map,
1017            &mut result,
1018            &simple_files,
1019            circuit_id,
1020            None,
1021        );
1022
1023        for (brillig_function_id, brillig_locations_map) in &debug_symbols.brillig_locations {
1024            let brillig_locations_map = brillig_locations_map
1025                .iter()
1026                .map(|(key, val)| {
1027                    (
1028                        // TODO: this is a temporary placeholder until the debugger is updated to handle the new brillig debug locations.
1029                        OpcodeLocation::Brillig { acir_index: 0, brillig_index: key.0 },
1030                        *val,
1031                    )
1032                })
1033                .collect();
1034
1035            add_opcode_locations_map(
1036                debug_symbols,
1037                &brillig_locations_map,
1038                &mut result,
1039                &simple_files,
1040                circuit_id,
1041                Some(*brillig_function_id),
1042            );
1043        }
1044    }
1045    result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| (x.0, x.1)));
1046
1047    result
1048}
1049
1050fn add_opcode_locations_map(
1051    debug_info: &DebugInfo,
1052    opcode_to_locations: &BTreeMap<OpcodeLocation, CallStackId>,
1053    source_to_locations: &mut BTreeMap<FileId, Vec<(usize, DebugLocation)>>,
1054    simple_files: &BTreeMap<&FileId, SimpleFile<&str, &str>>,
1055    circuit_id: usize,
1056    brillig_function_id: Option<BrilligFunctionId>,
1057) {
1058    for (opcode_location, source_locations) in opcode_to_locations {
1059        let source_locations = debug_info.location_tree.get_call_stack(*source_locations);
1060        source_locations.iter().for_each(|source_location| {
1061            let span = source_location.span;
1062            let file_id = source_location.file;
1063            let Some(file) = simple_files.get(&file_id) else {
1064                return;
1065            };
1066            let Ok(line_index) = file.line_index((), span.start() as usize) else {
1067                return;
1068            };
1069            let line_number = line_index + 1;
1070
1071            let debug_location = DebugLocation {
1072                circuit_id: circuit_id as u32,
1073                opcode_location: *opcode_location,
1074                brillig_function_id,
1075            };
1076            source_to_locations.entry(file_id).or_default().push((line_number, debug_location));
1077        });
1078    }
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083    use super::*;
1084
1085    use crate::foreign_calls::DefaultDebugForeignCallExecutor;
1086    use acvm::{
1087        acir::{
1088            AcirField,
1089            brillig::{HeapVector, IntegerBitSize},
1090            circuit::{
1091                brillig::{BrilligFunctionId, BrilligInputs, BrilligOutputs},
1092                opcodes::{AcirFunctionId, BlockId, BlockType},
1093            },
1094            native_types::Expression,
1095        },
1096        blackbox_solver::StubbedBlackBoxSolver,
1097        brillig_vm::brillig::{
1098            BinaryFieldOp, HeapValueType, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray,
1099        },
1100    };
1101
1102    #[test]
1103    fn test_resolve_foreign_calls_stepping_into_brillig() {
1104        let solver = StubbedBlackBoxSolver::default();
1105        let fe_1 = FieldElement::one();
1106        let w_x = Witness(1);
1107
1108        let brillig_bytecode = BrilligBytecode {
1109            bytecode: vec![
1110                BrilligOpcode::Const {
1111                    destination: MemoryAddress::direct(1),
1112                    bit_size: BitSize::Integer(IntegerBitSize::U32),
1113                    value: FieldElement::from(1u64),
1114                },
1115                BrilligOpcode::Const {
1116                    destination: MemoryAddress::direct(2),
1117                    bit_size: BitSize::Integer(IntegerBitSize::U32),
1118                    value: FieldElement::from(0u64),
1119                },
1120                BrilligOpcode::CalldataCopy {
1121                    destination_address: MemoryAddress::direct(0),
1122                    size_address: MemoryAddress::direct(1),
1123                    offset_address: MemoryAddress::direct(2),
1124                },
1125                BrilligOpcode::ForeignCall {
1126                    function: "clear_mock".into(),
1127                    destinations: vec![],
1128                    destination_value_types: vec![],
1129                    inputs: vec![ValueOrArray::MemoryAddress(MemoryAddress::direct(0))],
1130                    input_value_types: vec![HeapValueType::field()],
1131                },
1132                BrilligOpcode::Stop {
1133                    return_data: HeapVector {
1134                        pointer: MemoryAddress::direct(2),
1135                        size: MemoryAddress::direct(2),
1136                    },
1137                },
1138            ],
1139        };
1140        let opcodes = vec![Opcode::BrilligCall {
1141            id: BrilligFunctionId(0),
1142            inputs: vec![BrilligInputs::Single(Expression {
1143                linear_combinations: vec![(fe_1, w_x)],
1144                ..Expression::default()
1145            })],
1146            outputs: vec![],
1147            predicate: None,
1148        }];
1149        let brillig_functions = &[brillig_bytecode];
1150        let current_witness_index = 2;
1151        let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() };
1152        let circuits = &[circuit];
1153
1154        let debug_symbols = vec![];
1155        let file_map = BTreeMap::new();
1156        let debug_artifact = &DebugArtifact { debug_symbols, file_map };
1157
1158        let initial_witness = BTreeMap::from([(Witness(1), fe_1)]).into();
1159
1160        let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(
1161            std::io::stdout(),
1162            None,
1163            debug_artifact,
1164            None,
1165            String::new(),
1166        ));
1167        let mut context = DebugContext::<StubbedBlackBoxSolver>::new(
1168            &solver,
1169            circuits,
1170            debug_artifact,
1171            initial_witness,
1172            foreign_call_executor,
1173            brillig_functions,
1174        );
1175
1176        assert_eq!(
1177            context.get_current_debug_location(),
1178            Some(DebugLocation {
1179                circuit_id: 0,
1180                opcode_location: OpcodeLocation::Acir(0),
1181                brillig_function_id: None,
1182            })
1183        );
1184
1185        // Const
1186        let result = context.step_into_opcode();
1187        assert!(matches!(result, DebugCommandResult::Ok));
1188        assert_eq!(
1189            context.get_current_debug_location(),
1190            Some(DebugLocation {
1191                circuit_id: 0,
1192                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 },
1193                brillig_function_id: Some(BrilligFunctionId(0)),
1194            })
1195        );
1196
1197        // Const
1198        let result = context.step_into_opcode();
1199        assert!(matches!(result, DebugCommandResult::Ok));
1200        assert_eq!(
1201            context.get_current_debug_location(),
1202            Some(DebugLocation {
1203                circuit_id: 0,
1204                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 },
1205                brillig_function_id: Some(BrilligFunctionId(0)),
1206            })
1207        );
1208
1209        // cSpell:disable-next-line
1210        // Calldatacopy
1211        let result = context.step_into_opcode();
1212        assert!(matches!(result, DebugCommandResult::Ok));
1213        assert_eq!(
1214            context.get_current_debug_location(),
1215            Some(DebugLocation {
1216                circuit_id: 0,
1217                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 },
1218                brillig_function_id: Some(BrilligFunctionId(0)),
1219            })
1220        );
1221
1222        // try to execute the Brillig opcode (and resolve the foreign call)
1223        let result = context.step_into_opcode();
1224        assert!(matches!(result, DebugCommandResult::Ok));
1225        assert_eq!(
1226            context.get_current_debug_location(),
1227            Some(DebugLocation {
1228                circuit_id: 0,
1229                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 },
1230                brillig_function_id: Some(BrilligFunctionId(0)),
1231            })
1232        );
1233
1234        // retry the Brillig opcode (foreign call should be finished)
1235        let result = context.step_into_opcode();
1236        assert!(matches!(result, DebugCommandResult::Ok));
1237        assert_eq!(
1238            context.get_current_debug_location(),
1239            Some(DebugLocation {
1240                circuit_id: 0,
1241                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 4 },
1242                brillig_function_id: Some(BrilligFunctionId(0)),
1243            })
1244        );
1245
1246        // last Brillig opcode
1247        let result = context.step_into_opcode();
1248        assert!(matches!(result, DebugCommandResult::Done));
1249        assert_eq!(context.get_current_debug_location(), None);
1250    }
1251
1252    #[test]
1253    fn test_break_brillig_block_while_stepping_acir_opcodes() {
1254        let solver = StubbedBlackBoxSolver::default();
1255        let fe_0 = FieldElement::zero();
1256        let fe_1 = FieldElement::one();
1257        let w_x = Witness(1);
1258        let w_y = Witness(2);
1259        let w_z = Witness(3);
1260
1261        let zero_usize = MemoryAddress::direct(2);
1262        let one_usize = MemoryAddress::direct(3);
1263
1264        // This Brillig block is equivalent to: z = x + y
1265        let brillig_bytecode = BrilligBytecode {
1266            bytecode: vec![
1267                BrilligOpcode::Const {
1268                    destination: MemoryAddress::direct(0),
1269                    bit_size: BitSize::Integer(IntegerBitSize::U32),
1270                    value: FieldElement::from(2u64),
1271                },
1272                BrilligOpcode::Const {
1273                    destination: zero_usize,
1274                    bit_size: BitSize::Integer(IntegerBitSize::U32),
1275                    value: FieldElement::from(0u64),
1276                },
1277                BrilligOpcode::Const {
1278                    destination: one_usize,
1279                    bit_size: BitSize::Integer(IntegerBitSize::U32),
1280                    value: FieldElement::from(1u64),
1281                },
1282                BrilligOpcode::CalldataCopy {
1283                    destination_address: MemoryAddress::direct(0),
1284                    size_address: MemoryAddress::direct(0),
1285                    offset_address: zero_usize,
1286                },
1287                BrilligOpcode::BinaryFieldOp {
1288                    destination: MemoryAddress::direct(0),
1289                    op: BinaryFieldOp::Add,
1290                    lhs: MemoryAddress::direct(0),
1291                    rhs: MemoryAddress::direct(1),
1292                },
1293                BrilligOpcode::Stop {
1294                    return_data: HeapVector { pointer: zero_usize, size: one_usize },
1295                },
1296            ],
1297        };
1298        let opcodes = vec![
1299            // z = x + y
1300            Opcode::BrilligCall {
1301                id: BrilligFunctionId(0),
1302                inputs: vec![
1303                    BrilligInputs::Single(Expression {
1304                        linear_combinations: vec![(fe_1, w_x)],
1305                        ..Expression::default()
1306                    }),
1307                    BrilligInputs::Single(Expression {
1308                        linear_combinations: vec![(fe_1, w_y)],
1309                        ..Expression::default()
1310                    }),
1311                ],
1312                outputs: vec![BrilligOutputs::Simple(w_z)],
1313                predicate: None,
1314            },
1315            // x + y - z = 0
1316            Opcode::AssertZero(Expression {
1317                mul_terms: vec![],
1318                linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)],
1319                q_c: fe_0,
1320            }),
1321        ];
1322        let current_witness_index = 3;
1323        let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() };
1324        let circuits = &[circuit];
1325
1326        let debug_symbols = vec![];
1327        let file_map = BTreeMap::new();
1328        let debug_artifact = &DebugArtifact { debug_symbols, file_map };
1329
1330        let initial_witness = BTreeMap::from([(Witness(1), fe_1), (Witness(2), fe_1)]).into();
1331
1332        let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(
1333            std::io::stdout(),
1334            None,
1335            debug_artifact,
1336            None,
1337            String::new(),
1338        ));
1339        let brillig_functions = &[brillig_bytecode];
1340        let mut context = DebugContext::<StubbedBlackBoxSolver>::new(
1341            &solver,
1342            circuits,
1343            debug_artifact,
1344            initial_witness,
1345            foreign_call_executor,
1346            brillig_functions,
1347        );
1348
1349        // set breakpoint
1350        let breakpoint_location = DebugLocation {
1351            circuit_id: 0,
1352            opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 },
1353            brillig_function_id: Some(BrilligFunctionId(0)),
1354        };
1355        assert!(context.add_breakpoint(breakpoint_location));
1356
1357        // execute the first ACIR opcode (Brillig block) -> should reach the breakpoint instead
1358        let result = context.step_acir_opcode();
1359        assert!(matches!(result, DebugCommandResult::BreakpointReached(_)));
1360        assert_eq!(context.get_current_debug_location(), Some(breakpoint_location));
1361
1362        // continue execution to the next ACIR opcode
1363        let result = context.step_acir_opcode();
1364        assert!(matches!(result, DebugCommandResult::Ok));
1365        assert_eq!(
1366            context.get_current_debug_location(),
1367            Some(DebugLocation {
1368                circuit_id: 0,
1369                opcode_location: OpcodeLocation::Acir(1),
1370                brillig_function_id: None
1371            })
1372        );
1373
1374        // last ACIR opcode
1375        let result = context.step_acir_opcode();
1376        assert!(matches!(result, DebugCommandResult::Done));
1377        assert_eq!(context.get_current_debug_location(), None);
1378    }
1379
1380    #[test]
1381    fn test_address_debug_location_mapping() {
1382        let solver = StubbedBlackBoxSolver::default();
1383        let brillig_one =
1384            BrilligBytecode { bytecode: vec![BrilligOpcode::Return, BrilligOpcode::Return] };
1385        let brillig_two = BrilligBytecode {
1386            bytecode: vec![BrilligOpcode::Return, BrilligOpcode::Return, BrilligOpcode::Return],
1387        };
1388
1389        let circuit_one = Circuit {
1390            opcodes: vec![
1391                Opcode::MemoryInit {
1392                    block_id: BlockId(0),
1393                    init: vec![],
1394                    block_type: BlockType::Memory,
1395                },
1396                Opcode::BrilligCall {
1397                    id: BrilligFunctionId(0),
1398                    inputs: vec![],
1399                    outputs: vec![],
1400                    predicate: None,
1401                },
1402                Opcode::Call {
1403                    id: AcirFunctionId(1),
1404                    inputs: vec![],
1405                    outputs: vec![],
1406                    predicate: None,
1407                },
1408                Opcode::AssertZero(Expression::default()),
1409            ],
1410            ..Circuit::default()
1411        };
1412        let circuit_two = Circuit {
1413            opcodes: vec![
1414                Opcode::BrilligCall {
1415                    id: BrilligFunctionId(1),
1416                    inputs: vec![],
1417                    outputs: vec![],
1418                    predicate: None,
1419                },
1420                Opcode::AssertZero(Expression::default()),
1421            ],
1422            ..Circuit::default()
1423        };
1424        let circuits = vec![circuit_one, circuit_two];
1425        let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new() };
1426        let brillig_functions = &[brillig_one, brillig_two];
1427
1428        let context = DebugContext::<StubbedBlackBoxSolver>::new(
1429            &solver,
1430            &circuits,
1431            &debug_artifact,
1432            WitnessMap::new(),
1433            Box::new(DefaultDebugForeignCallExecutor::new(
1434                std::io::stdout(),
1435                None,
1436                None,
1437                String::new(),
1438            )),
1439            brillig_functions,
1440        );
1441
1442        let locations =
1443            (0..=8).map(|address| context.address_to_debug_location(address)).collect::<Vec<_>>();
1444
1445        // mapping from addresses to opcode locations
1446        assert_eq!(
1447            locations,
1448            vec![
1449                Some(DebugLocation {
1450                    circuit_id: 0,
1451                    opcode_location: OpcodeLocation::Acir(0),
1452                    brillig_function_id: None
1453                }),
1454                Some(DebugLocation {
1455                    circuit_id: 0,
1456                    opcode_location: OpcodeLocation::Acir(1),
1457                    brillig_function_id: None
1458                }),
1459                Some(DebugLocation {
1460                    circuit_id: 0,
1461                    opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 1 },
1462                    brillig_function_id: Some(BrilligFunctionId(0)),
1463                }),
1464                Some(DebugLocation {
1465                    circuit_id: 0,
1466                    opcode_location: OpcodeLocation::Acir(2),
1467                    brillig_function_id: None
1468                }),
1469                Some(DebugLocation {
1470                    circuit_id: 0,
1471                    opcode_location: OpcodeLocation::Acir(3),
1472                    brillig_function_id: None
1473                }),
1474                Some(DebugLocation {
1475                    circuit_id: 1,
1476                    opcode_location: OpcodeLocation::Acir(0),
1477                    brillig_function_id: None
1478                }),
1479                Some(DebugLocation {
1480                    circuit_id: 1,
1481                    opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 },
1482                    brillig_function_id: Some(BrilligFunctionId(1)),
1483                }),
1484                Some(DebugLocation {
1485                    circuit_id: 1,
1486                    opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 },
1487                    brillig_function_id: Some(BrilligFunctionId(1)),
1488                }),
1489                Some(DebugLocation {
1490                    circuit_id: 1,
1491                    opcode_location: OpcodeLocation::Acir(1),
1492                    brillig_function_id: None
1493                }),
1494            ]
1495        );
1496
1497        let addresses = locations
1498            .iter()
1499            .flatten()
1500            .map(|location| context.debug_location_to_address(location))
1501            .collect::<Vec<_>>();
1502
1503        // and vice-versa
1504        assert_eq!(addresses, (0..=8).collect::<Vec<_>>());
1505
1506        // check edge cases
1507        assert_eq!(None, context.address_to_debug_location(9));
1508        assert_eq!(
1509            1,
1510            context.debug_location_to_address(&DebugLocation {
1511                circuit_id: 0,
1512                opcode_location: OpcodeLocation::Brillig { acir_index: 1, brillig_index: 0 },
1513                brillig_function_id: Some(BrilligFunctionId(0)),
1514            })
1515        );
1516        assert_eq!(
1517            5,
1518            context.debug_location_to_address(&DebugLocation {
1519                circuit_id: 1,
1520                opcode_location: OpcodeLocation::Brillig { acir_index: 0, brillig_index: 0 },
1521                brillig_function_id: Some(BrilligFunctionId(1)),
1522            })
1523        );
1524    }
1525}