Skip to content

Commit

Permalink
refactor debug logic around ReplContext methods
Browse files Browse the repository at this point in the history
  • Loading branch information
nthiad authored and mverzilli committed Oct 4, 2023
1 parent f600293 commit 6105c5c
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 121 deletions.
276 changes: 156 additions & 120 deletions tooling/nargo/src/ops/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,151 +9,187 @@ use crate::NargoError;

use super::foreign_calls::ForeignCallExecutor;

use std::io::{self, Write};
use std::rc::Rc;
use std::sync::Mutex;

use reedline_repl_rs::clap::{
ArgMatches as ReplArgMatches,
Command as ReplCommand,
};
use reedline_repl_rs::Repl;

enum SolveResult {
Done,
Ok,
}

enum Command {
Step,
Continue,
Stop,
}

pub fn debug_circuit<B: BlackBoxFunctionSolver>(
blackbox_solver: &B,
circuit: Circuit,
struct ReplContext<'backend, B: BlackBoxFunctionSolver> {
acvm: Option<ACVM<'backend, B>>,
debug_artifact: DebugArtifact,
initial_witness: WitnessMap,
foreign_call_executor: ForeignCallExecutor,
circuit: Circuit,
show_output: bool,
) -> Result<WitnessMap, NargoError> {
let mut acvm = ACVM::new(blackbox_solver, circuit.opcodes, initial_witness);
let mut foreign_call_executor = ForeignCallExecutor::default();

'outer: loop {
show_current_vm_status(&acvm, &debug_artifact);
let command = match read_command() {
Ok(cmd) => cmd,
Err(err) => {
eprintln!("Error reading command: {}", err);
return Err(NargoError::ExecutionError(ExecutionError::Halted))
}
}

impl<'backend, B> ReplContext<'backend, B> where B: BlackBoxFunctionSolver {
fn step_opcode(&mut self) -> Result<SolveResult, NargoError> {
// Assert messages are not a map due to https://github.com/noir-lang/acvm/issues/522
let assert_messages = &self.circuit.assert_messages;
let get_assert_message = |opcode_location| {
assert_messages
.iter()
.find(|(loc, _)| loc == opcode_location)
.map(|(_, message)| message.clone())
};
match command {
Command::Stop => return Err(NargoError::ExecutionError(ExecutionError::Halted)),
Command::Step => {
match step_opcode(&mut acvm, &circuit.assert_messages, show_output, &mut foreign_call_executor)? {
SolveResult::Done => break,
SolveResult::Ok => {},
}
}
Command::Continue => {
println!("(Continuing execution...)");
loop {
match step_opcode(&mut acvm, &circuit.assert_messages, show_output, &mut foreign_call_executor)? {
SolveResult::Done => break 'outer,
SolveResult::Ok => {},

let acvm = self.acvm.as_mut().unwrap();
let solver_status = acvm.solve_opcode();

match solver_status {
ACVMStatus::Solved => Ok(SolveResult::Done),
ACVMStatus::InProgress => Ok(SolveResult::Ok),
ACVMStatus::Failure(error) => {
let call_stack = match &error {
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Resolved(opcode_location),
} => Some(vec![*opcode_location]),
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => {
Some(call_stack.clone())
}
}
},
_ => None,
};

Err(NargoError::ExecutionError(match call_stack {
Some(call_stack) => {
if let Some(assert_message) = get_assert_message(
call_stack.last().expect("Call stacks should not be empty"),
) {
ExecutionError::AssertionFailed(assert_message, call_stack)
} else {
ExecutionError::SolvingError(error)
}
}
None => ExecutionError::SolvingError(error),
}))
}
ACVMStatus::RequiresForeignCall(foreign_call) => {
let foreign_call_result = self.foreign_call_executor.execute(&foreign_call, self.show_output)?;
acvm.resolve_pending_foreign_call(foreign_call_result);
Ok(SolveResult::Ok)
}
}
}

let solved_witness = acvm.finalize();
Ok(solved_witness)
}
fn show_current_vm_status(&self) {
let acvm = self.acvm.as_ref().unwrap();
let ip = acvm.instruction_pointer();
println!("Stopped at opcode {}: {}", ip, acvm.opcodes()[ip]);
self.show_source_code_location(&OpcodeLocation::Acir(ip));
}

fn step_opcode<B: BlackBoxFunctionSolver>(
acvm: &mut ACVM<B>,
assert_messages: &Vec<(OpcodeLocation, String)>,
show_output: bool,
foreign_call_executor: &mut ForeignCallExecutor,
) -> Result<SolveResult, NargoError> {
// Assert messages are not a map due to https://github.com/noir-lang/acvm/issues/522
let get_assert_message = |opcode_location| {
assert_messages
.iter()
.find(|(loc, _)| loc == opcode_location)
.map(|(_, message)| message.clone())
};

let solver_status = acvm.solve_opcode();

match solver_status {
ACVMStatus::Solved => Ok(SolveResult::Done),
ACVMStatus::InProgress => Ok(SolveResult::Ok),
ACVMStatus::Failure(error) => {
let call_stack = match &error {
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Resolved(opcode_location),
} => Some(vec![*opcode_location]),
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => {
Some(call_stack.clone())
}
_ => None,
};

Err(NargoError::ExecutionError(match call_stack {
Some(call_stack) => {
if let Some(assert_message) = get_assert_message(
call_stack.last().expect("Call stacks should not be empty"),
) {
ExecutionError::AssertionFailed(assert_message, call_stack)
} else {
ExecutionError::SolvingError(error)
}
fn show_source_code_location(&self, location: &OpcodeLocation) {
let locations = self.debug_artifact.debug_symbols[0].opcode_location(&location);
match locations {
Some(locations) => {
for loc in locations {
let file = &self.debug_artifact.file_map[&loc.file];
let source = &file.source.as_str();
let start = loc.span.start() as usize;
let end = loc.span.end() as usize;
println!("At {}:{start}-{end}", file.path.as_path().display());
println!("\n{}\n", &source[start..end]);
}
None => ExecutionError::SolvingError(error),
}))
}
ACVMStatus::RequiresForeignCall(foreign_call) => {
let foreign_call_result = foreign_call_executor.execute(&foreign_call, show_output)?;
acvm.resolve_pending_foreign_call(foreign_call_result);
Ok(SolveResult::Ok)
},
None => {}
}
}

fn finalize(&mut self) -> WitnessMap {
self.acvm.take().unwrap().finalize()
}
}

fn show_source_code_location(location: &OpcodeLocation, debug_artifact: &DebugArtifact) {
let locations = debug_artifact.debug_symbols[0].opcode_location(&location);
match locations {
Some(locations) => {
for loc in locations {
let file = &debug_artifact.file_map[&loc.file];
let source = &file.source.as_str();
let start = loc.span.start() as usize;
let end = loc.span.end() as usize;
println!("At {}:{start}-{end}", file.path.as_path().display());
println!("\n{}\n", &source[start..end]);
}
},
None => {}
impl From<reedline_repl_rs::Error> for NargoError {
fn from(_e: reedline_repl_rs::Error) -> Self {
NargoError::CompilationError
}
}

fn show_current_vm_status<B: BlackBoxFunctionSolver> (acvm: &ACVM<B>, debug_artifact: &DebugArtifact) {
let ip = acvm.instruction_pointer();
println!("Stopped at opcode {}: {}", ip, acvm.opcodes()[ip]);
show_source_code_location(&OpcodeLocation::Acir(ip), &debug_artifact);

// impl fmt::Display for NargoError {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// match self {
// NargoError::CompilationError => write!(f, "Compilation Error"),
// NargoError::ExecutionError(e) => write!(f, "Execution Error: {}", e),
// NargoError::ForeignCallError(e) => write!(f, "Foreign call Error: {}", e),
// }
// }
// }

pub fn debug_circuit<B: BlackBoxFunctionSolver>(
blackbox_solver: &B,
circuit: Circuit,
debug_artifact: DebugArtifact,
initial_witness: WitnessMap,
show_output: bool,
) -> Result<WitnessMap, NargoError> {
let opcodes = circuit.opcodes.clone();
let acvm = ACVM::new(blackbox_solver, opcodes, initial_witness);
let foreign_call_executor = ForeignCallExecutor::default();

let repl_context = Rc::new(Mutex::new(ReplContext {
acvm: Some(acvm),
debug_artifact,
foreign_call_executor,
circuit,
show_output,
}));
let mut repl = Repl::new(repl_context.clone())
.with_name("debug")
.with_version(env!["CARGO_PKG_VERSION"])
.with_command(
ReplCommand::new("s")
.about("step to the next opcode"),
step_command,
)
.with_command(
ReplCommand::new("c")
.about("continue execution until the end of the program"),
continue_command,
)
.with_command(
ReplCommand::new("q")
.about("quit the debugger"),
quit_command,
);
repl.run().unwrap();
let solved_witness = repl_context.lock().unwrap().finalize();
Ok(solved_witness)
}

fn read_command() -> Result<Command, io::Error> {
fn step_command<B: BlackBoxFunctionSolver>(_args: ReplArgMatches, context: &mut Rc<Mutex<ReplContext<B>>>) -> Result<Option<String>, NargoError> {
let mut c = context.lock().unwrap();
c.show_current_vm_status();
match c.step_opcode()? {
SolveResult::Done => Ok(Some("Done".to_string())),
SolveResult::Ok => Ok(Some("Ok".to_string())),
}
}

fn continue_command<B: BlackBoxFunctionSolver>(_args: ReplArgMatches, context: &mut Rc<Mutex<ReplContext<B>>>) -> Result<Option<String>, NargoError> {
let mut c = context.lock().unwrap();
c.show_current_vm_status();
println!("(Continuing execution...)");
loop {
let mut line = String::new();
print!(">>> ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut line)?;
if line.is_empty() {
return Ok(Command::Stop);
}
match line.trim() {
"s" => return Ok(Command::Step),
"c" => return Ok(Command::Continue),
"q" => return Ok(Command::Stop),
"" => continue,
_ => println!("ERROR: unknown command")
match c.step_opcode()? {
SolveResult::Done => break,
SolveResult::Ok => {},
}
}
Ok(Some("Ok".to_string()))
}

fn quit_command<B: BlackBoxFunctionSolver>(_args: ReplArgMatches, context: &mut Rc<Mutex<ReplContext<B>>>) -> Result<Option<String>, NargoError> {
context.lock().unwrap().show_current_vm_status();
Err(NargoError::ExecutionError(ExecutionError::Halted))
}
1 change: 0 additions & 1 deletion tooling/nargo_cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ mod new_cmd;
mod prove_cmd;
mod test_cmd;
mod verify_cmd;
mod debug_cmd;

const GIT_HASH: &str = env!("GIT_COMMIT");
const IS_DIRTY: &str = env!("GIT_DIRTY");
Expand Down

0 comments on commit 6105c5c

Please sign in to comment.