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: Add debugger commands to introspect (and modify) the current state #3391

Merged
merged 8 commits into from
Nov 19, 2023
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) -> &Vec<Value> {
mverzilli marked this conversation as resolved.
Show resolved Hide resolved
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 @@
&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 Expand Up @@ -205,7 +213,7 @@
if let Some(register) = self.call_stack.pop() {
self.set_program_counter(register.to_usize() + 1)
} else {
self.fail("return opcode hit, but callstack already empty".to_string())

Check warning on line 216 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (callstack)
}
}
Opcode::ForeignCall { function, destinations, inputs } => {
Expand Down Expand Up @@ -528,7 +536,7 @@
}

#[test]
fn jmpifnot_opcode() {

Check warning on line 539 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (jmpifnot)
let input_registers =
Registers::load(vec![Value::from(1u128), Value::from(2u128), Value::from(0u128)]);

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<&Vec<Value>> {
self.brillig_solver.as_ref().map(|solver| solver.get_memory())
mverzilli marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading