forked from noir-lang/noir
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First cut at implementing a debug command for nargo
Allows stepping through the ACIR opcodes, showing the current opcode and mapped source location.
- Loading branch information
Showing
5 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}; | ||
use acvm::BlackBoxFunctionSolver; | ||
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; | ||
use acvm::acir::circuit::OpcodeLocation; | ||
|
||
use crate::artifacts::debug::DebugArtifact; | ||
use crate::errors::ExecutionError; | ||
use crate::NargoError; | ||
|
||
use super::foreign_calls::ForeignCall; | ||
|
||
use std::io::{self, Write}; | ||
|
||
enum SolveResult { | ||
Done, | ||
Ok, | ||
} | ||
|
||
enum Command { | ||
Step, | ||
Continue, | ||
Stop, | ||
} | ||
|
||
pub fn debug_circuit<B: BlackBoxFunctionSolver>( | ||
blackbox_solver: &B, | ||
circuit: Circuit, | ||
debug_artifact: DebugArtifact, | ||
initial_witness: WitnessMap, | ||
show_output: bool, | ||
) -> Result<WitnessMap, NargoError> { | ||
let mut acvm = ACVM::new(blackbox_solver, circuit.opcodes, initial_witness); | ||
|
||
'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)) | ||
} | ||
}; | ||
match command { | ||
Command::Stop => return Err(NargoError::ExecutionError(ExecutionError::Halted)), | ||
Command::Step => { | ||
match step_opcode(&mut acvm, &circuit.assert_messages, show_output)? { | ||
SolveResult::Done => break, | ||
SolveResult::Ok => {}, | ||
} | ||
} | ||
Command::Continue => { | ||
println!("(Continuing execution...)"); | ||
loop { | ||
match step_opcode(&mut acvm, &circuit.assert_messages, show_output)? { | ||
SolveResult::Done => break 'outer, | ||
SolveResult::Ok => {}, | ||
} | ||
} | ||
}, | ||
} | ||
} | ||
|
||
let solved_witness = acvm.finalize(); | ||
Ok(solved_witness) | ||
} | ||
|
||
fn step_opcode<B: BlackBoxFunctionSolver>( | ||
acvm: &mut ACVM<B>, | ||
assert_messages: &Vec<(OpcodeLocation, String)>, | ||
show_output: bool, | ||
) -> 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) | ||
} | ||
} | ||
None => ExecutionError::SolvingError(error), | ||
})) | ||
} | ||
ACVMStatus::RequiresForeignCall(foreign_call) => { | ||
let foreign_call_result = ForeignCall::execute(&foreign_call, show_output)?; | ||
acvm.resolve_pending_foreign_call(foreign_call_result); | ||
Ok(SolveResult::Ok) | ||
} | ||
} | ||
} | ||
|
||
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 => {} | ||
} | ||
} | ||
|
||
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); | ||
} | ||
|
||
fn read_command() -> Result<Command, io::Error> { | ||
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") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
pub use self::execute::execute_circuit; | ||
pub use self::debug::debug_circuit; | ||
pub use self::optimize::{optimize_contract, optimize_program}; | ||
pub use self::test::{run_test, TestStatus}; | ||
|
||
mod execute; | ||
mod debug; | ||
mod foreign_calls; | ||
mod optimize; | ||
mod test; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use acvm::acir::native_types::WitnessMap; | ||
use clap::Args; | ||
|
||
use nargo::constants::PROVER_INPUT_FILE; | ||
use nargo::package::Package; | ||
use nargo::artifacts::debug::DebugArtifact; | ||
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; | ||
use noirc_abi::input_parser::{Format, InputValue}; | ||
use noirc_abi::InputMap; | ||
use noirc_driver::{CompileOptions, CompiledProgram}; | ||
use noirc_frontend::graph::CrateName; | ||
|
||
use super::compile_cmd::compile_bin_package; | ||
use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; | ||
use super::NargoConfig; | ||
use crate::backends::Backend; | ||
use crate::errors::CliError; | ||
|
||
/// Executes a circuit in debug mode | ||
#[derive(Debug, Clone, Args)] | ||
pub(crate) struct DebugCommand { | ||
/// Write the execution witness to named file | ||
witness_name: Option<String>, | ||
|
||
/// The name of the toml file which contains the inputs for the prover | ||
#[clap(long, short, default_value = PROVER_INPUT_FILE)] | ||
prover_name: String, | ||
|
||
/// The name of the package to execute | ||
#[clap(long)] | ||
package: Option<CrateName>, | ||
|
||
#[clap(flatten)] | ||
compile_options: CompileOptions, | ||
} | ||
|
||
pub(crate) fn run( | ||
backend: &Backend, | ||
args: DebugCommand, | ||
config: NargoConfig, | ||
) -> Result<(), CliError> { | ||
let toml_path = get_package_manifest(&config.program_dir)?; | ||
let selection = args.package.map_or(PackageSelection::DefaultOrAll, PackageSelection::Selected); | ||
let workspace = resolve_workspace_from_toml(&toml_path, selection)?; | ||
let target_dir = &workspace.target_directory_path(); | ||
|
||
let (np_language, opcode_support) = backend.get_backend_info()?; | ||
for package in &workspace { | ||
let compiled_program = compile_bin_package( | ||
&workspace, | ||
package, | ||
&args.compile_options, | ||
true, | ||
np_language, | ||
&|opcode| opcode_support.is_opcode_supported(opcode), | ||
)?; | ||
|
||
println!("[{}] Starting debugger", package.name); | ||
let (return_value, solved_witness) = | ||
debug_program_and_decode(compiled_program, package, &args.prover_name)?; | ||
|
||
println!("[{}] Circuit witness successfully solved", package.name); | ||
if let Some(return_value) = return_value { | ||
println!("[{}] Circuit output: {return_value:?}", package.name); | ||
} | ||
if let Some(witness_name) = &args.witness_name { | ||
let witness_path = save_witness_to_dir(solved_witness, witness_name, target_dir)?; | ||
|
||
println!("[{}] Witness saved to {}", package.name, witness_path.display()); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn debug_program_and_decode( | ||
program: CompiledProgram, | ||
package: &Package, | ||
prover_name: &str, | ||
) -> Result<(Option<InputValue>, WitnessMap), CliError> { | ||
// Parse the initial witness values from Prover.toml | ||
let (inputs_map, _) = | ||
read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; | ||
let solved_witness = debug_program(&program, &inputs_map)?; | ||
let public_abi = program.abi.public_abi(); | ||
let (_, return_value) = public_abi.decode(&solved_witness)?; | ||
|
||
Ok((return_value, solved_witness)) | ||
} | ||
|
||
pub(crate) fn debug_program( | ||
compiled_program: &CompiledProgram, | ||
inputs_map: &InputMap, | ||
) -> Result<WitnessMap, CliError> { | ||
#[allow(deprecated)] | ||
let blackbox_solver = barretenberg_blackbox_solver::BarretenbergSolver::new(); | ||
|
||
let initial_witness = compiled_program.abi.encode(inputs_map, None)?; | ||
|
||
let debug_artifact = DebugArtifact { | ||
debug_symbols: vec![compiled_program.debug.clone()], | ||
file_map: compiled_program.file_map.clone(), | ||
}; | ||
|
||
let solved_witness_err = nargo::ops::debug_circuit( | ||
&blackbox_solver, | ||
compiled_program.circuit.clone(), | ||
debug_artifact, | ||
initial_witness, | ||
true, | ||
); | ||
match solved_witness_err { | ||
Ok(solved_witness) => Ok(solved_witness), | ||
Err(err) => { | ||
Err(crate::errors::CliError::NargoError(err)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters