Skip to content

Commit

Permalink
feat: Add debugger commands to introspect (and modify) the current st…
Browse files Browse the repository at this point in the history
…ate (#3391)

Co-authored-by: Martin Verzilli <martin.verzilli@gmail.com>
Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 19, 2023
1 parent 9701f90 commit 9e1ad85
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 11 deletions.
16 changes: 16 additions & 0 deletions acvm-repo/acvm/src/pwg/brillig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> {
Ok(Self { vm, acir_index })
}

pub fn get_registers(&self) -> &Registers {
self.vm.get_registers()
}

pub fn set_register(&mut self, register_index: usize, value: Value) {
self.vm.set_register(RegisterIndex(register_index), value);
}

pub fn get_memory(&self) -> &[Value] {
self.vm.get_memory()
}

pub fn write_memory_at(&mut self, ptr: usize, value: Value) {
self.vm.write_memory_at(ptr, value);
}

pub(super) fn solve(&mut self) -> Result<BrilligSolverStatus, OpcodeResolutionError> {
let status = self.vm.process_opcodes();
self.handle_vm_status(status)
Expand Down
8 changes: 8 additions & 0 deletions acvm-repo/acvm/src/pwg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> {
&self.witness_map
}

pub fn overwrite_witness(
&mut self,
witness: Witness,
value: FieldElement,
) -> Option<FieldElement> {
self.witness_map.insert(witness, value)
}

/// Returns a slice containing the opcodes of the circuit being executed.
pub fn opcodes(&self) -> &[Opcode] {
self.opcodes
Expand Down
8 changes: 8 additions & 0 deletions acvm-repo/brillig_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,18 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> {
&self.registers
}

pub fn set_register(&mut self, register_index: RegisterIndex, value: Value) {
self.registers.set(register_index, value);
}

pub fn get_memory(&self) -> &Vec<Value> {
self.memory.values()
}

pub fn write_memory_at(&mut self, ptr: usize, value: Value) {
self.memory.write(ptr, value);
}

/// Process a single opcode and modify the program counter.
pub fn process_opcode(&mut self) -> VMStatus {
let opcode = &self.bytecode[self.program_counter];
Expand Down
55 changes: 47 additions & 8 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use acvm::acir::circuit::{Opcode, OpcodeLocation};
use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation};
use acvm::acir::native_types::{Witness, WitnessMap};
use acvm::brillig_vm::{brillig::Value, Registers};
use acvm::pwg::{
ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM,
};
use acvm::BlackBoxFunctionSolver;
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};
use acvm::{BlackBoxFunctionSolver, FieldElement};

use nargo::artifacts::debug::DebugArtifact;
use nargo::errors::{ExecutionError, Location};
Expand Down Expand Up @@ -50,6 +51,18 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
self.acvm.opcodes()
}

pub(super) fn get_witness_map(&self) -> &WitnessMap {
self.acvm.witness_map()
}

pub(super) fn overwrite_witness(
&mut self,
witness: Witness,
value: FieldElement,
) -> Option<FieldElement> {
self.acvm.overwrite_witness(witness, value)
}

pub(super) fn get_current_opcode_location(&self) -> Option<OpcodeLocation> {
let ip = self.acvm.instruction_pointer();
if ip >= self.get_opcodes().len() {
Expand All @@ -64,11 +77,11 @@ 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)
/// 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()
Expand Down Expand Up @@ -190,6 +203,32 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> {
}
}

pub(super) fn is_executing_brillig(&self) -> bool {
let opcodes = self.get_opcodes();
let acir_index = self.acvm.instruction_pointer();
acir_index < opcodes.len() && matches!(opcodes[acir_index], Opcode::Brillig(..))
}

pub(super) fn get_brillig_registers(&self) -> Option<&Registers> {
self.brillig_solver.as_ref().map(|solver| solver.get_registers())
}

pub(super) fn set_brillig_register(&mut self, register_index: usize, value: FieldElement) {
if let Some(solver) = self.brillig_solver.as_mut() {
solver.set_register(register_index, value.into());
}
}

pub(super) fn get_brillig_memory(&self) -> Option<&[Value]> {
self.brillig_solver.as_ref().map(|solver| solver.get_memory())
}

pub(super) fn write_brillig_memory(&mut self, ptr: usize, value: FieldElement) {
if let Some(solver) = self.brillig_solver.as_mut() {
solver.write_memory_at(ptr, value.into());
}
}

fn breakpoint_reached(&self) -> bool {
if let Some(location) = self.get_current_opcode_location() {
self.breakpoints.contains(&location)
Expand Down
163 changes: 160 additions & 3 deletions tooling/debugger/src/repl.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::context::{DebugCommandResult, DebugContext};

use acvm::acir::circuit::{Opcode, OpcodeLocation};
use acvm::BlackBoxFunctionSolver;
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};
use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation};
use acvm::acir::native_types::{Witness, WitnessMap};
use acvm::{BlackBoxFunctionSolver, FieldElement};

use nargo::artifacts::debug::DebugArtifact;
use nargo::NargoError;
Expand Down Expand Up @@ -283,6 +283,93 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> {
self.show_current_vm_status();
}

pub fn show_witness_map(&self) {
let witness_map = self.context.get_witness_map();
// NOTE: we need to clone() here to get the iterator
for (witness, value) in witness_map.clone().into_iter() {
println!("_{} = {value}", witness.witness_index());
}
}

pub fn show_witness(&self, index: u32) {
if let Some(value) = self.context.get_witness_map().get_index(index) {
println!("_{} = {value}", index);
}
}

pub fn update_witness(&mut self, index: u32, value: String) {
let Some(field_value) = FieldElement::try_from_str(&value) else {
println!("Invalid witness value: {value}");
return;
};

let witness = Witness::from(index);
_ = self.context.overwrite_witness(witness, field_value);
println!("_{} = {value}", index);
}

pub fn show_brillig_registers(&self) {
if !self.context.is_executing_brillig() {
println!("Not executing a Brillig block");
return;
}

let Some(registers) = self.context.get_brillig_registers() else {
// this can happen when just entering the Brillig block since ACVM
// would have not initialized the Brillig VM yet; in fact, the
// Brillig code may be skipped altogether
println!("Brillig VM registers not available");
return;
};

for (index, value) in registers.inner.iter().enumerate() {
println!("{index} = {}", value.to_field());
}
}

pub fn set_brillig_register(&mut self, index: usize, value: String) {
let Some(field_value) = FieldElement::try_from_str(&value) else {
println!("Invalid value: {value}");
return;
};
if !self.context.is_executing_brillig() {
println!("Not executing a Brillig block");
return;
}
self.context.set_brillig_register(index, field_value);
}

pub fn show_brillig_memory(&self) {
if !self.context.is_executing_brillig() {
println!("Not executing a Brillig block");
return;
}

let Some(memory) = self.context.get_brillig_memory() else {
// this can happen when just entering the Brillig block since ACVM
// would have not initialized the Brillig VM yet; in fact, the
// Brillig code may be skipped altogether
println!("Brillig VM memory not available");
return;
};

for (index, value) in memory.iter().enumerate() {
println!("{index} = {}", value.to_field());
}
}

pub fn write_brillig_memory(&mut self, index: usize, value: String) {
let Some(field_value) = FieldElement::try_from_str(&value) else {
println!("Invalid value: {value}");
return;
};
if !self.context.is_executing_brillig() {
println!("Not executing a Brillig block");
return;
}
self.context.write_brillig_memory(index, field_value);
}

fn is_solved(&self) -> bool {
self.context.is_solved()
}
Expand Down Expand Up @@ -393,6 +480,76 @@ pub fn run<B: BlackBoxFunctionSolver>(
}
},
)
.add(
"witness",
command! {
"show witness map",
() => || {
ref_context.borrow().show_witness_map();
Ok(CommandStatus::Done)
}
},
)
.add(
"witness",
command! {
"display a single witness from the witness map",
(index: u32) => |index| {
ref_context.borrow().show_witness(index);
Ok(CommandStatus::Done)
}
},
)
.add(
"witness",
command! {
"update a witness with the given value",
(index: u32, value: String) => |index, value| {
ref_context.borrow_mut().update_witness(index, value);
Ok(CommandStatus::Done)
}
},
)
.add(
"registers",
command! {
"show Brillig registers (valid when executing a Brillig block)",
() => || {
ref_context.borrow().show_brillig_registers();
Ok(CommandStatus::Done)
}
},
)
.add(
"regset",
command! {
"update a Brillig register with the given value",
(index: usize, value: String) => |index, value| {
ref_context.borrow_mut().set_brillig_register(index, value);
Ok(CommandStatus::Done)
}
},
)
.add(
"memory",
command! {
"show Brillig memory (valid when executing a Brillig block)",
() => || {
ref_context.borrow().show_brillig_memory();
Ok(CommandStatus::Done)
}
},
)
.add(
"memset",
command! {
"update a Brillig memory cell with the given value",
(index: usize, value: String) => |index, value| {
ref_context.borrow_mut().write_brillig_memory(index, value);
Ok(CommandStatus::Done)
}
},
)
.build()
.expect("Failed to initialize debugger repl");

Expand Down

0 comments on commit 9e1ad85

Please sign in to comment.